mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 23:52:57 +08:00
Merge pull request #8800 from iiSaLMaN/allow-cancelling-completion
Fix results screen pushed after rewinding in-between push delay
This commit is contained in:
commit
af32f51116
@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private const double time_after_tail = 5250;
|
private const double time_after_tail = 5250;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -354,7 +354,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames)
|
private void performTest(List<OsuHitObject> hitObjects, List<ReplayFrame> frames)
|
||||||
{
|
{
|
||||||
@ -380,20 +379,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestHitCircle : HitCircle
|
private class TestHitCircle : HitCircle
|
||||||
|
@ -47,7 +47,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private const double time_slider_end = 4000;
|
private const double time_slider_end = 4000;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scenario:
|
/// Scenario:
|
||||||
@ -375,20 +374,15 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestZeroTickTimeOffsets()
|
public void TestZeroTickTimeOffsets()
|
||||||
{
|
{
|
||||||
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
|
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
|
AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Screens;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneCompletionCancellation : PlayerTestScene
|
||||||
|
{
|
||||||
|
private Track track;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audio { get; set; }
|
||||||
|
|
||||||
|
private int resultsDisplayWaitCount =>
|
||||||
|
(int)((Screens.Play.Player.RESULTS_DISPLAY_DELAY / TimePerAction) * 2);
|
||||||
|
|
||||||
|
protected override bool AllowFail => false;
|
||||||
|
|
||||||
|
public TestSceneCompletionCancellation()
|
||||||
|
: base(new OsuRuleset())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
// Ensure track has actually running before attempting to seek
|
||||||
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCancelCompletionOnRewind()
|
||||||
|
{
|
||||||
|
complete();
|
||||||
|
cancel();
|
||||||
|
|
||||||
|
checkNoRanking();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReCompleteAfterCancellation()
|
||||||
|
{
|
||||||
|
complete();
|
||||||
|
cancel();
|
||||||
|
complete();
|
||||||
|
|
||||||
|
AddUntilStep("attempted to push ranking", () => ((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests whether can still pause after cancelling completion by reverting <see cref="IScreen.ValidForResume"/> back to true.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCanPauseAfterCancellation()
|
||||||
|
{
|
||||||
|
complete();
|
||||||
|
cancel();
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddAssert("paused successfully", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||||
|
|
||||||
|
checkNoRanking();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void complete()
|
||||||
|
{
|
||||||
|
AddStep("seek to completion", () => track.Seek(5000));
|
||||||
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancel()
|
||||||
|
{
|
||||||
|
AddStep("rewind to cancel", () => track.Seek(4000));
|
||||||
|
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNoRanking()
|
||||||
|
{
|
||||||
|
// wait to ensure there was no attempt of pushing the results screen.
|
||||||
|
AddWaitStep("wait", resultsDisplayWaitCount);
|
||||||
|
AddAssert("no attempt to push ranking", () => !((FakeRankingPushPlayer)Player).GotoRankingInvoked);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
{
|
||||||
|
var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||||
|
track = working.Track;
|
||||||
|
return working;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap();
|
||||||
|
|
||||||
|
for (int i = 1; i <= 19; i++)
|
||||||
|
{
|
||||||
|
beatmap.HitObjects.Add(new HitCircle
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
StartTime = i * 250,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeRankingPushPlayer();
|
||||||
|
|
||||||
|
public class FakeRankingPushPlayer : TestPlayer
|
||||||
|
{
|
||||||
|
public bool GotoRankingInvoked;
|
||||||
|
|
||||||
|
public FakeRankingPushPlayer()
|
||||||
|
: base(true, true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void GotoRanking()
|
||||||
|
{
|
||||||
|
GotoRankingInvoked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -12,11 +13,6 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
public abstract class JudgementProcessor : Component
|
public abstract class JudgementProcessor : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Invoked when all <see cref="HitObject"/>s have been judged by this <see cref="JudgementProcessor"/>.
|
|
||||||
/// </summary>
|
|
||||||
public event Action AllJudged;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
|
/// Invoked when a new judgement has occurred. This occurs after the judgement has been processed by this <see cref="JudgementProcessor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -32,10 +28,12 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int JudgedHits { get; private set; }
|
public int JudgedHits { get; private set; }
|
||||||
|
|
||||||
|
private readonly BindableBool hasCompleted = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether all <see cref="Judgement"/>s have been processed.
|
/// Whether all <see cref="Judgement"/>s have been processed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasCompleted => JudgedHits == MaxHits;
|
public IBindable<bool> HasCompleted => hasCompleted;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Applies a <see cref="IBeatmap"/> to this <see cref="ScoreProcessor"/>.
|
/// Applies a <see cref="IBeatmap"/> to this <see cref="ScoreProcessor"/>.
|
||||||
@ -60,8 +58,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
|
|
||||||
NewJudgement?.Invoke(result);
|
NewJudgement?.Invoke(result);
|
||||||
|
|
||||||
if (HasCompleted)
|
updateHasCompleted();
|
||||||
AllJudged?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -72,6 +69,8 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
{
|
{
|
||||||
JudgedHits--;
|
JudgedHits--;
|
||||||
|
|
||||||
|
updateHasCompleted();
|
||||||
|
|
||||||
RevertResultInternal(result);
|
RevertResultInternal(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,5 +133,7 @@ namespace osu.Game.Rulesets.Scoring
|
|||||||
ApplyResult(result);
|
ApplyResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateHasCompleted() => hasCompleted.Value = JudgedHits == MaxHits;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|
||||||
|| Clock.CurrentTime < gameplayStartTime
|
|| Clock.CurrentTime < gameplayStartTime
|
||||||
|| scoreProcessor?.HasCompleted == true;
|
|| scoreProcessor?.HasCompleted.Value == true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BreakPeriod getCurrentBreak()
|
private BreakPeriod getCurrentBreak()
|
||||||
|
@ -37,6 +37,11 @@ namespace osu.Game.Screens.Play
|
|||||||
[Cached]
|
[Cached]
|
||||||
public class Player : ScreenWithBeatmapBackground
|
public class Player : ScreenWithBeatmapBackground
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The delay upon completion of the beatmap before displaying the results screen.
|
||||||
|
/// </summary>
|
||||||
|
public const double RESULTS_DISPLAY_DELAY = 1000.0;
|
||||||
|
|
||||||
public override bool AllowBackButton => false; // handled by HoldForMenuButton
|
public override bool AllowBackButton => false; // handled by HoldForMenuButton
|
||||||
|
|
||||||
protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value);
|
||||||
@ -197,7 +202,7 @@ namespace osu.Game.Screens.Play
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Bind the judgement processors to ourselves
|
// Bind the judgement processors to ourselves
|
||||||
ScoreProcessor.AllJudged += onCompletion;
|
ScoreProcessor.HasCompleted.ValueChanged += updateCompletionState;
|
||||||
HealthProcessor.Failed += onFail;
|
HealthProcessor.Failed += onFail;
|
||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToScoreProcessor>())
|
||||||
@ -412,22 +417,33 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
private ScheduledDelegate completionProgressDelegate;
|
private ScheduledDelegate completionProgressDelegate;
|
||||||
|
|
||||||
private void onCompletion()
|
private void updateCompletionState(ValueChangedEvent<bool> completionState)
|
||||||
{
|
{
|
||||||
// screen may be in the exiting transition phase.
|
// screen may be in the exiting transition phase.
|
||||||
if (!this.IsCurrentScreen())
|
if (!this.IsCurrentScreen())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!completionState.NewValue)
|
||||||
|
{
|
||||||
|
completionProgressDelegate?.Cancel();
|
||||||
|
completionProgressDelegate = null;
|
||||||
|
ValidForResume = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionProgressDelegate != null)
|
||||||
|
throw new InvalidOperationException($"{nameof(updateCompletionState)} was fired more than once");
|
||||||
|
|
||||||
// Only show the completion screen if the player hasn't failed
|
// Only show the completion screen if the player hasn't failed
|
||||||
if (HealthProcessor.HasFailed || completionProgressDelegate != null)
|
if (HealthProcessor.HasFailed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ValidForResume = false;
|
ValidForResume = false;
|
||||||
|
|
||||||
if (!showResults) return;
|
if (!showResults) return;
|
||||||
|
|
||||||
using (BeginDelayedSequence(1000))
|
using (BeginDelayedSequence(RESULTS_DISPLAY_DELAY))
|
||||||
scheduleGotoRanking();
|
completionProgressDelegate = Schedule(GotoRanking);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual ScoreInfo CreateScore()
|
protected virtual ScoreInfo CreateScore()
|
||||||
@ -679,12 +695,6 @@ namespace osu.Game.Screens.Play
|
|||||||
storyboardReplacesBackground.Value = false;
|
storyboardReplacesBackground.Value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleGotoRanking()
|
|
||||||
{
|
|
||||||
completionProgressDelegate?.Cancel();
|
|
||||||
completionProgressDelegate = Schedule(GotoRanking);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
public bool CheckFailed(bool failed)
|
public bool CheckFailed(bool failed)
|
||||||
{
|
{
|
||||||
if (!failed)
|
if (!failed)
|
||||||
return ScoreProcessor.HasCompleted && !HealthProcessor.HasFailed;
|
return ScoreProcessor.HasCompleted.Value && !HealthProcessor.HasFailed;
|
||||||
|
|
||||||
return HealthProcessor.HasFailed;
|
return HealthProcessor.HasFailed;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user