diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 1d572772eb..160af47a6d 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -14,6 +14,7 @@ using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Screens.Edit.GameplayTest; +using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osuTK.Graphics; using osuTK.Input; @@ -150,6 +151,35 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); } + [Test] + public void TestSharedClockState() + { + AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000)); + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + + GameplayClockContainer gameplayClockContainer = null; + AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType().First()); + AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning); + AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000); + + double timeAtPlayerExit = 0; + AddWaitStep("wait some", 5); + AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime); + + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit); + } + public override void TearDownSteps() { base.TearDownSteps(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs index 36fc6812bd..7167d3120a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -31,12 +31,18 @@ namespace osu.Game.Tests.Visual.Gameplay { AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); + + // The pause screen and fail animation both ramp frequency. + // This tests to ensure that it doesn't reset during that handoff. + AddAssert("frequency only ever decreased", () => !((FailPlayer)Player).FrequencyIncreased); } private class FailPlayer : TestPlayer { public new FailOverlay FailOverlay => base.FailOverlay; + public bool FrequencyIncreased { get; private set; } + public FailPlayer() : base(false, false) { @@ -47,6 +53,19 @@ namespace osu.Game.Tests.Visual.Gameplay base.LoadComplete(); HealthProcessor.FailConditions += (_, __) => true; } + + private double lastFrequency = double.MaxValue; + + protected override void Update() + { + base.Update(); + + double freq = Beatmap.Value.Track.AggregateFrequency.Value; + + FrequencyIncreased |= freq > lastFrequency; + + lastFrequency = freq; + } } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ab24391bb4..2a7e2c9cef 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -325,6 +325,19 @@ namespace osu.Game.Screens.Edit /// public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + /// + /// Creates an instance representing the current state of the editor. + /// + /// + /// The next beatmap to be shown, in the case of difficulty switch. + /// indicates that the beatmap will not be changing. + /// + public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState + { + Time = clock.CurrentTimeAccurate, + ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty + }; + /// /// Restore the editor to a provided state. /// @@ -780,11 +793,7 @@ namespace osu.Game.Screens.Edit return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty); } - protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState - { - Time = clock.CurrentTimeAccurate, - ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? Clipboard.Content.Value : string.Empty - }); + protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); private void cancelExit() { @@ -807,7 +816,7 @@ namespace osu.Game.Screens.Edit pushEditorPlayer(); } - void pushEditorPlayer() => this.Push(new EditorPlayerLoader()); + void pushEditorPlayer() => this.Push(new EditorPlayerLoader(this)); } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 9856ad62bb..f49603c754 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Screens.Play; @@ -10,14 +11,22 @@ namespace osu.Game.Screens.Edit.GameplayTest { public class EditorPlayer : Player { - public EditorPlayer() - : base(new PlayerConfiguration { ShowResults = false }) - { - } + private readonly Editor editor; + private readonly EditorState editorState; [Resolved] private MusicController musicController { get; set; } + public EditorPlayer(Editor editor) + : base(new PlayerConfiguration { ShowResults = false }) + { + this.editor = editor; + editorState = editor.GetState(); + } + + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) + => new MasterGameplayClockContainer(beatmap, editorState.Time, true); + protected override void LoadComplete() { base.LoadComplete(); @@ -35,9 +44,22 @@ namespace osu.Game.Screens.Edit.GameplayTest protected override bool CheckModsAllowFailure() => false; // never fail. + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + // finish alpha transforms on entering to avoid gameplay starting in a half-hidden state. + // the finish calls are purposefully not propagated to children to avoid messing up their state. + FinishTransforms(); + GameplayClockContainer.FinishTransforms(false, nameof(Alpha)); + } + public override bool OnExiting(IScreen next) { musicController.Stop(); + + editorState.Time = GameplayClockContainer.CurrentTime; + editor.RestoreState(editorState); return base.OnExiting(next); } } diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs index 610fff70f2..addc79ba61 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayerLoader.cs @@ -14,8 +14,8 @@ namespace osu.Game.Screens.Edit.GameplayTest [Resolved] private OsuLogo osuLogo { get; set; } - public EditorPlayerLoader() - : base(() => new EditorPlayer()) + public EditorPlayerLoader(Editor editor) + : base(() => new EditorPlayer(editor)) { } diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index f3676baf80..193e1e4129 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -107,7 +107,8 @@ namespace osu.Game.Screens.Play this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => { - RemoveFilters(); + // Don't reset frequency as the pause screen may appear post transform, causing a second frequency sweep. + RemoveFilters(false); OnComplete?.Invoke(); }); @@ -137,15 +138,16 @@ namespace osu.Game.Screens.Play Content.FadeColour(Color4.Gray, duration); } - public void RemoveFilters() + public void RemoveFilters(bool resetTrackFrequency = true) { + if (resetTrackFrequency) + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + if (filters.Parent == null) return; RemoveInternal(filters); filters.Dispose(); - - track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); } protected override void Update()