1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-13 17:27:48 +08:00

Merge pull request #25097 from peppy/results-screen-progress-fast

Show results immediately if user hits "back" key after finishing gameplay
This commit is contained in:
Bartłomiej Dach 2023-10-13 11:54:34 +02:00 committed by GitHub
commit 5f51b8a7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 78 additions and 39 deletions

View File

@ -72,12 +72,12 @@ namespace osu.Game.Tests.Visual.Gameplay
}
[Test]
public void TestStoryboardExitDuringOutroStillExits()
public void TestStoryboardExitDuringOutroProgressesToResults()
{
CreateTest();
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("player exited", () => !Player.IsCurrentScreen() && Player.GetChildScreen() == null);
AddUntilStep("reached results screen", () => Stack.CurrentScreen is ResultsScreen);
}
[TestCase(false)]
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false));
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("player exited", () => Stack.CurrentScreen == null);
AddUntilStep("reached results screen", () => Stack.CurrentScreen is ResultsScreen);
}
[Test]

View File

@ -215,6 +215,24 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("retry count is 1", () => player.RestartCount == 1);
}
[Test]
public void TestRetryImmediatelyAfterCompletion()
{
var getOriginalPlayer = playToCompletion();
AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType<HotkeyRetryOverlay>().First().Action());
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
}
[Test]
public void TestExitImmediatelyAfterCompletion()
{
var player = playToCompletion();
AddStep("attempt to exit", () => player().ChildrenOfType<HotkeyExitOverlay>().First().Action());
AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen);
}
[Test]
public void TestRetryFromResults()
{
@ -778,6 +796,13 @@ namespace osu.Game.Tests.Visual.Navigation
}
private Func<Player> playToResults()
{
var player = playToCompletion();
AddUntilStep("wait for results", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true);
return player;
}
private Func<Player> playToCompletion()
{
Player player = null;
@ -803,7 +828,8 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning);
AddStep("seek to near end", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000));
AddUntilStep("wait for pass", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true);
AddUntilStep("wait for complete", () => player.GameplayState.HasPassed);
return () => player;
}

View File

@ -279,8 +279,10 @@ namespace osu.Game.Screens.Play
{
if (!this.IsCurrentScreen()) return;
fadeOut(true);
PerformExit(false);
if (PerformExit(false))
// The hotkey overlay dims the screen.
// If the operation succeeds, we want to make sure we stay dimmed to keep continuity.
fadeOut(true);
},
},
});
@ -298,8 +300,10 @@ namespace osu.Game.Screens.Play
{
if (!this.IsCurrentScreen()) return;
fadeOut(true);
Restart(true);
if (Restart(true))
// The hotkey overlay dims the screen.
// If the operation succeeds, we want to make sure we stay dimmed to keep continuity.
fadeOut(true);
},
},
});
@ -565,20 +569,9 @@ namespace osu.Game.Screens.Play
/// Whether the pause or fail dialog should be shown before performing an exit.
/// If <see langword="true"/> and a dialog is not yet displayed, the exit will be blocked and the relevant dialog will display instead.
/// </param>
protected void PerformExit(bool showDialogFirst)
/// <returns>Whether this call resulted in a final exit.</returns>
protected bool PerformExit(bool showDialogFirst)
{
// there is a chance that an exit request occurs after the transition to results has already started.
// even in such a case, the user has shown intent, so forcefully return to this screen (to proceed with the upwards exit process).
if (!this.IsCurrentScreen())
{
ValidForResume = false;
// in the potential case that this instance has already been exited, this is required to avoid a crash.
if (this.GetChildScreen() != null)
this.MakeCurrent();
return;
}
bool pauseOrFailDialogVisible =
PauseOverlay.State.Value == Visibility.Visible || FailOverlay.State.Value == Visibility.Visible;
@ -588,7 +581,7 @@ namespace osu.Game.Screens.Play
if (ValidForResume && GameplayState.HasFailed)
{
failAnimationContainer.FinishTransforms(true);
return;
return false;
}
// even if this call has requested a dialog, there is a chance the current player mode doesn't support pausing.
@ -597,21 +590,32 @@ namespace osu.Game.Screens.Play
// in the case a dialog needs to be shown, attempt to pause and show it.
// this may fail (see internal checks in Pause()) but the fail cases are temporary, so don't fall through to Exit().
Pause();
return;
return false;
}
}
// if an exit has been requested, cancel any pending completion (the user has shown intention to exit).
resultsDisplayDelegate?.Cancel();
// Matching osu!stable behaviour, if the results screen is pending and the user requests an exit,
// show the results instead.
if (GameplayState.HasPassed && !isRestarting)
{
progressToResults(false);
return false;
}
// import current score if possible.
prepareAndImportScoreAsync();
// The actual exit is performed if
// - the pause / fail dialog was not requested
// - the pause / fail dialog was requested but is already displayed (user showing intention to exit).
// - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance.
this.Exit();
// Screen may not be current if a restart has been performed.
if (this.IsCurrentScreen())
{
// The actual exit is performed if
// - the pause / fail dialog was not requested
// - the pause / fail dialog was requested but is already displayed (user showing intention to exit).
// - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance.
this.Exit();
}
return true;
}
private void performUserRequestedSkip()
@ -660,10 +664,11 @@ namespace osu.Game.Screens.Play
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>
/// </summary>
/// <param name="quickRestart">Whether a quick restart was requested (skipping intro etc.).</param>
public void Restart(bool quickRestart = false)
/// <returns>Whether this call resulted in a restart.</returns>
public bool Restart(bool quickRestart = false)
{
if (!Configuration.AllowRestart)
return;
return false;
isRestarting = true;
@ -673,7 +678,7 @@ namespace osu.Game.Screens.Play
RestartRequested?.Invoke(quickRestart);
PerformExit(false);
return PerformExit(false);
}
/// <summary>
@ -729,9 +734,6 @@ namespace osu.Game.Screens.Play
// is no chance that a user could return to the (already completed) Player instance from a child screen.
ValidForResume = false;
if (!Configuration.ShowResults)
return;
bool storyboardStillRunning = DimmableStoryboard.ContentDisplayed && !DimmableStoryboard.HasStoryboardEnded.Value;
// If the current beatmap has a storyboard, this method will be called again on storyboard completion.
@ -754,10 +756,16 @@ namespace osu.Game.Screens.Play
/// <param name="withDelay">Whether a minimum delay (<see cref="RESULTS_DISPLAY_DELAY"/>) should be added before the screen is displayed.</param>
private void progressToResults(bool withDelay)
{
resultsDisplayDelegate?.Cancel();
if (!Configuration.ShowResults)
return;
// Setting this early in the process means that even if something were to go wrong in the order of events following, there
// is no chance that a user could return to the (already completed) Player instance from a child screen.
ValidForResume = false;
double delay = withDelay ? RESULTS_DISPLAY_DELAY : 0;
resultsDisplayDelegate?.Cancel();
resultsDisplayDelegate = new ScheduledDelegate(() =>
{
if (prepareScoreForDisplayTask == null)
@ -1200,8 +1208,11 @@ namespace osu.Game.Screens.Play
float fadeOutDuration = instant ? 0 : 250;
this.FadeOut(fadeOutDuration);
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
storyboardReplacesBackground.Value = false;
if (this.IsCurrentScreen())
{
ApplyToBackground(b => b.IgnoreUserSettings.Value = true);
storyboardReplacesBackground.Value = false;
}
}
#endregion

View File

@ -414,6 +414,8 @@ namespace osu.Game.Screens.Play
quickRestart = quickRestartRequested;
hideOverlays = true;
ValidForResume = true;
this.MakeCurrent();
}
private void contentIn()