From 11e1b22bf5be695bebc5dad23ea9c2aa268d4be7 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 20:53:35 +0900 Subject: [PATCH 01/18] Move MaximumJudgementOffset to HitObject We want to access this property for computing lifetime --- .../Objects/Drawables/DrawableHoldNote.cs | 2 -- .../Objects/Drawables/DrawableHoldNoteTail.cs | 11 +---------- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 ++ osu.Game.Rulesets.Mania/Objects/TailNote.cs | 9 +++++++++ .../Objects/Drawables/DrawableSpinnerTick.cs | 2 -- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 7 +++++++ .../Objects/Drawables/DrawableDrumRollTick.cs | 2 -- osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs | 2 ++ .../Objects/Drawables/DrawableHitObject.cs | 14 +------------- osu.Game/Rulesets/Objects/HitObject.cs | 8 ++++++++ 11 files changed, 32 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 759d2346e8..8863de5ee3 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private double? releaseTime; - public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; - public DrawableHoldNote() : this(null) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index bf477277c6..20ea962994 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -15,13 +15,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public partial class DrawableHoldNoteTail : DrawableNote { - /// - /// Lenience of release hit windows. This is to make cases where the hold note release - /// is timed alongside presses of other hit objects less awkward. - /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps - /// - private const double release_window_lenience = 1.5; - protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail; protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject; @@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public void UpdateResult() => base.UpdateResult(true); - public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience; - protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); // Factor in the release lenience - timeOffset /= release_window_lenience; + timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE; if (!userTriggered) { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 22fab15c1b..c367886efe 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects /// public TailNote Tail { get; private set; } + public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset; + /// /// The time between ticks of this hold. /// diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index cda8e2fa31..d6dc25079a 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects { public class TailNote : Note { + /// + /// Lenience of release hit windows. This is to make cases where the hold note release + /// is timed alongside presses of other hit objects less awkward. + /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps + /// + public const double RELEASE_WINDOW_LENIENCE = 1.5; + public override Judgement CreateJudgement() => new ManiaJudgement(); + + public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index b9ce07363c..34253e3d4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; } - public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration; - /// /// Apply a judgement result. /// diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0e1fe56cea..ed6f8a9a6a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects double startTime = StartTime + (float)(i + 1) / totalSpins * Duration; AddNested(i < SpinsRequired - ? new SpinnerTick { StartTime = startTime } - : new SpinnerBonusTick { StartTime = startTime }); + ? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration } + : new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index 650d02c675..c890f3771b 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { + /// + /// Duration of the containing this spinner tick. + /// + public double SpinnerDuration { get; set; } + public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => SpinnerDuration; + public class OsuSpinnerTickJudgement : OsuJudgement { public override HitResult MaxResult => HitResult.SmallBonus; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 45e25ee7dc..abecd19545 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece()); - public override double MaximumJudgementOffset => HitObject.HitWindow; - protected override void OnApply() { base.OnApply(); diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 433fdab908..6bcb8674e6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; + public override double MaximumJudgementOffset => HitWindow; + protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime }; public class StrongNestedHit : StrongNestedHitObject diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index be5a7f71e7..43431d7703 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -651,18 +651,6 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } - /// - /// The maximum offset from the end time of at which this can be judged. - /// The time offset of will be clamped to this value during . - /// - /// Defaults to the miss window of . - /// - /// - /// - /// This does not affect the time offset provided to invocations of . - /// - public virtual double MaximumJudgementOffset => HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? 0; - /// /// Applies the of this , notifying responders such as /// the of the . @@ -684,7 +672,7 @@ namespace osu.Game.Rulesets.Objects.Drawables $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); } - Result.TimeOffset = Math.Min(MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); + Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime()); if (Result.HasResult) updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0f79e58201..25f538d211 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -200,6 +200,14 @@ namespace osu.Game.Rulesets.Objects [NotNull] protected virtual HitWindows CreateHitWindows() => new HitWindows(); + /// + /// The maximum offset from the end time of at which this can be judged. + /// + /// Defaults to the miss window. + /// + /// + public virtual double MaximumJudgementOffset => HitWindows?.WindowFor(HitResult.Miss) ?? 0; + public IList CreateSlidingSamples() { var slidingSamples = new List(); From d8f9b7d02f2a4e3b86976b45226f7488ef1639f8 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 19 Jan 2023 21:25:21 +0900 Subject: [PATCH 02/18] Use MaximumJudgementOffset for lifetime --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 3559a1521c..b93a427196 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -232,8 +232,7 @@ namespace osu.Game.Rulesets.UI.Scrolling double computedStartTime = computeDisplayStartTime(entry); // always load the hitobject before its first judgement offset - double judgementOffset = entry.HitObject.HitWindows?.WindowFor(Scoring.HitResult.Miss) ?? 0; - entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime); + entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - entry.HitObject.MaximumJudgementOffset, computedStartTime); } private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null) From 75a1a2ec2f2d136a510f05c2371b6d37fc8270a9 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 22 Jan 2023 03:44:59 +0100 Subject: [PATCH 03/18] Hide `ResumeOverlay` when `OsuModAutopilot` is enabled --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 + osu.Game/Rulesets/Mods/Mod.cs | 3 +++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 +++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 6772cfe0be..99a35f4d69 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; + public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 04d55bc5fe..4f85bf4663 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,6 +107,9 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + [JsonIgnore] + public virtual bool HidesResumeOverlay => false; + /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 71b452c309..38dbb1308f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,7 +230,9 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null + && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) + && Mods.All(mod => !mod.HidesResumeOverlay)) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; From 8b47af6503b5a93e98940d31b96d13b1858361f5 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 24 Jan 2023 00:49:09 +0100 Subject: [PATCH 04/18] Remove `HidesResumeOverlay` and set `ResumeOverlay` to `null` in `OsuModAutopilot` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 3 ++- osu.Game/Rulesets/Mods/Mod.cs | 3 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 ++---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 99a35f4d69..276724c169 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override LocalisableString Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 0.1; - public override bool HidesResumeOverlay => true; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; @@ -55,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + drawableRuleset.ResumeOverlay = null; + // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 4f85bf4663..04d55bc5fe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -107,9 +107,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; - [JsonIgnore] - public virtual bool HidesResumeOverlay => false; - /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 38dbb1308f..403dee5651 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -230,9 +230,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null - && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))) - && Mods.All(mod => !mod.HidesResumeOverlay)) + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; protected set; } + public ResumeOverlay ResumeOverlay { get; set; } /// /// Returns first available provided by a . From e4b84ebd0b3a89e56c07da34195862bccfbbb38c Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:51:39 +0100 Subject: [PATCH 05/18] Add `UseResumeOverlay` and use it for hiding the `ResumeOverlay` --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 4 ++-- osu.Game/Rulesets/UI/DrawableRuleset.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 276724c169..9eb0a46bfb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -54,14 +54,14 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - drawableRuleset.ResumeOverlay = null; - // Grab the input manager to disable the user's cursor, and for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; inputManager.AllowUserCursorMovement = false; // Generate the replay frames the cursor should follow replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast().ToList(); + + drawableRuleset.UseResumeOverlay = false; } } } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 403dee5651..23e8d1fc2d 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -196,6 +196,8 @@ namespace osu.Game.Rulesets.UI if ((ResumeOverlay = CreateResumeOverlay()) != null) { + UseResumeOverlay = true; + AddInternal(CreateInputManager() .WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(ResumeOverlay))); @@ -230,7 +232,7 @@ namespace osu.Game.Rulesets.UI public override void RequestResume(Action continueResume) { - if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + if (ResumeOverlay != null && UseResumeOverlay && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) { ResumeOverlay.GameplayCursor = Cursor; ResumeOverlay.ResumeAction = continueResume; @@ -507,6 +509,12 @@ namespace osu.Game.Rulesets.UI /// public ResumeOverlay ResumeOverlay { get; set; } + /// + /// Whether the should be used to return the user's cursor position to its previous location after a pause. + /// + /// Defaults to true if a ruleset returns a non-null overlay via . + public bool UseResumeOverlay { get; set; } + /// /// Returns first available provided by a . /// From a820c0c8ebdc7d1e6ccb5e6a59d0cb5c50851d51 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 13 Feb 2023 23:55:13 +0100 Subject: [PATCH 06/18] Add `TestSceneInstantResume` --- .../TestSceneInstantResume.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs new file mode 100644 index 0000000000..2bc6386185 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneInstantResume : TestSceneOsuPlayer + { + protected override bool HasCustomSteps => true; + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { new OsuModAutopilot() }; + return new TestPlayer(); + } + + [Test] + public void TestInstantResume() + { + CreateTest(); + + AddStep("press pause", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); + AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); + AddStep("press resume", () => InputManager.PressKey(Key.Escape)); + AddUntilStep("wait for resume", () => !Player.IsResuming); + AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value); + } + } +} From 9e04a36d86c9a19e37cf470aa4c4505fa9ad4ef9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:07:45 +0900 Subject: [PATCH 07/18] Move test to a mod test and add more resilient test logic --- .../TestSceneOsuModAutopilot.cs} | 21 ++++++++----------- osu.Game/Tests/Visual/ModTestScene.cs | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneInstantResume.cs => Mods/TestSceneOsuModAutopilot.cs} (66%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs similarity index 66% rename from osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs rename to osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs index 2bc6386185..37b31d1d1a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneInstantResume.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs @@ -3,26 +3,23 @@ using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Visual; using osuTK.Input; -namespace osu.Game.Rulesets.Osu.Tests +namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneInstantResume : TestSceneOsuPlayer + public partial class TestSceneOsuModAutopilot : OsuModTestScene { - protected override bool HasCustomSteps => true; - - protected override TestPlayer CreatePlayer(Ruleset ruleset) - { - SelectedMods.Value = new[] { new OsuModAutopilot() }; - return new TestPlayer(); - } - [Test] public void TestInstantResume() { - CreateTest(); + CreateModTest(new ModTestData + { + Mod = new OsuModAutopilot(), + PassCondition = () => true, + Autoplay = false, + }); + AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value); AddStep("press pause", () => InputManager.PressKey(Key.Escape)); AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value); AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape)); diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 0559d80384..aa5b506343 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual protected override bool CheckModsAllowFailure() => allowFail; public ModTestPlayer(ModTestData data, bool allowFail) - : base(false, false) + : base(true, false) { this.allowFail = allowFail; currentTestData = data; From 63f349876290a3d5c07768e75116ae132dbbc2ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Feb 2023 15:11:33 +0900 Subject: [PATCH 08/18] Restructure `UseResumeOverlay` to correctly handle a value change before BDL load --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 23e8d1fc2d..c839062875 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,13 +194,16 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if ((ResumeOverlay = CreateResumeOverlay()) != null) + if (UseResumeOverlay) { - UseResumeOverlay = true; - - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); + if ((ResumeOverlay = CreateResumeOverlay()) == null) + UseResumeOverlay = false; + else + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } } applyRulesetMods(Mods, config); @@ -512,8 +515,8 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true if a ruleset returns a non-null overlay via . - public bool UseResumeOverlay { get; set; } + /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + public bool UseResumeOverlay { get; set; } = true; /// /// Returns first available provided by a . From 19e3c5d33c5495cc17180932806db5e3a1c34c14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 13:59:24 +0900 Subject: [PATCH 09/18] Adjust song select background dimming to be more evenly applied --- osu.Game/Screens/Select/SongSelect.cs | 2 ++ osu.Game/Screens/Select/WedgeBackground.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8786821c77..5c4a42852c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -767,6 +767,8 @@ namespace osu.Game.Screens.Select { backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); }); diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index da12c1a67a..fd4b91bf5f 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.5f), + Colour = Color4.Black.Opacity(0.2f), Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From 9ed068c1e67e870f49163620cf95697c5426a108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Feb 2023 14:16:34 +0900 Subject: [PATCH 10/18] Only apply dim changes when background blur is disabled --- osu.Game/Graphics/Containers/UserDimContainer.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 16 +++++++++++++--- osu.Game/Screens/Select/WedgeBackground.cs | 9 +++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs index 25830c9d54..6f6292c3b2 100644 --- a/osu.Game/Graphics/Containers/UserDimContainer.cs +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers /// public const float BREAK_LIGHTEN_AMOUNT = 0.3f; - protected const double BACKGROUND_FADE_DURATION = 800; + public const double BACKGROUND_FADE_DURATION = 800; /// /// Whether or not user-configured settings relating to brightness of elements should be ignored diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5c4a42852c..8546dfeeaa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -148,7 +149,7 @@ namespace osu.Game.Screens.Select if (!this.IsCurrentScreen()) return; - ApplyToBackground(b => b.BlurAmount.Value = e.NewValue ? BACKGROUND_BLUR : 0); + ApplyToBackground(applyBlurToBackground); }); LoadComponentAsync(Carousel = new BeatmapCarousel @@ -194,6 +195,7 @@ namespace osu.Game.Screens.Select { ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, + Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, Child = new WedgeBackground @@ -766,10 +768,10 @@ namespace osu.Game.Screens.Select ApplyToBackground(backgroundModeBeatmap => { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = 0.4f; backgroundModeBeatmap.FadeColour(Color4.White, 250); + + applyBlurToBackground(backgroundModeBeatmap); }); beatmapInfoWedge.Beatmap = beatmap; @@ -787,6 +789,14 @@ namespace osu.Game.Screens.Select } } + private void applyBlurToBackground(BackgroundScreenBeatmap backgroundModeBeatmap) + { + backgroundModeBeatmap.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f; + backgroundModeBeatmap.DimWhenUserSettingsIgnored.Value = configBackgroundBlur.Value ? 0 : 0.4f; + + wedgeBackground.FadeTo(configBackgroundBlur.Value ? 0.5f : 0.2f, UserDimContainer.BACKGROUND_FADE_DURATION, Easing.OutQuint); + } + private readonly WeakReference lastTrack = new WeakReference(null); /// diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index fd4b91bf5f..2e2b43cd70 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -1,14 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; namespace osu.Game.Screens.Select { @@ -22,7 +19,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Size = new Vector2(1, 0.5f), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, @@ -32,7 +29,7 @@ namespace osu.Game.Screens.Select RelativePositionAxes = Axes.Y, Size = new Vector2(1, -0.5f), Position = new Vector2(0, 1), - Colour = Color4.Black.Opacity(0.2f), + Colour = Color4.Black, Shear = new Vector2(-0.15f, 0), EdgeSmoothness = new Vector2(2, 0), }, From ce9ef3bc3c0092b99c904ba89c0a7ce99be6cab4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 15:47:20 +0900 Subject: [PATCH 11/18] Always create `ResumeOverlay`, with `UseResumeOverlay` flag only affecting whether it is displayed or not --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index c839062875..d57aec1271 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -194,16 +194,11 @@ namespace osu.Game.Rulesets.UI Audio = audioContainer; - if (UseResumeOverlay) + if ((ResumeOverlay = CreateResumeOverlay()) != null) { - if ((ResumeOverlay = CreateResumeOverlay()) == null) - UseResumeOverlay = false; - else - { - AddInternal(CreateInputManager() - .WithChild(CreatePlayfieldAdjustmentContainer() - .WithChild(ResumeOverlay))); - } + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); } applyRulesetMods(Mods, config); @@ -515,7 +510,10 @@ namespace osu.Game.Rulesets.UI /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. /// - /// Defaults to true, but will become false if the ruleset fails to return a non-null overlay via . + /// + /// Defaults to true. + /// Even if true, will not have any effect if the ruleset does not have a resume overlay (see ). + /// public bool UseResumeOverlay { get; set; } = true; /// @@ -542,6 +540,11 @@ namespace osu.Game.Rulesets.UI } } + /// + /// Create an optional resume overlay, which is displayed when a player requests to resume gameplay during non-break time. + /// This can be used to force the player to return their hands / cursor to the position they left off, to avoid players + /// using pauses as a means of adjusting their inputs (aka "pause buffering"). + /// protected virtual ResumeOverlay CreateResumeOverlay() => null; /// From 9d02a2ef0e8a17398459403ac71353d4688ad4ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:37:46 +0900 Subject: [PATCH 12/18] Apply NRT to `GamepleSampleTriggerSource` tests --- .../Gameplay/TestSceneGameplaySampleTriggerSource.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e184d50d7c..a9b469fd86 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Audio; @@ -20,10 +18,10 @@ namespace osu.Game.Tests.Visual.Gameplay { public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene { - private TestGameplaySampleTriggerSource sampleTriggerSource; + private TestGameplaySampleTriggerSource sampleTriggerSource = null!; protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - private Beatmap beatmap; + private Beatmap beatmap = null!; protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { @@ -80,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry nextObjectEntry = null; + HitObjectLifetimeEntry? nextObjectEntry = null; AddAssert("no alive objects", () => getNextAliveObject() == null); @@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("hit first hitobject", () => { InputManager.Click(MouseButton.Left); - return nextObjectEntry.Result?.HasResult == true; + return nextObjectEntry?.Result?.HasResult == true; }); AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); @@ -115,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); } - private DrawableHitObject getNextAliveObject() => + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); [Test] From 979c079f8b5f852575e9722303173d28facbd31a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 17:58:32 +0900 Subject: [PATCH 13/18] Refactor `GameplaySampleTriggerSource` test to not be realtime dependent --- .../TestSceneGameplaySampleTriggerSource.cs | 79 +++++++++++++------ 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index a9b469fd86..e7bdf7b9ba 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -1,8 +1,12 @@ // 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.Allocation; +using osu.Framework.Audio; +using osu.Framework.Timing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -12,6 +16,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Storyboards; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay @@ -23,6 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay private Beatmap beatmap = null!; + [Resolved] + private AudioManager audio { get; set; } = null!; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) + => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio); + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { beatmap = new Beatmap @@ -37,12 +48,13 @@ namespace osu.Game.Tests.Visual.Gameplay const double start_offset = 8000; const double spacing = 2000; + // intentionally start objects a bit late so we can test the case of no alive objects. double t = start_offset; + beatmap.HitObjects.AddRange(new[] { new HitCircle { - // intentionally start objects a bit late so we can test the case of no alive objects. StartTime = t += spacing, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) } }, @@ -78,41 +90,64 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestCorrectHitObject() { - HitObjectLifetimeEntry? nextObjectEntry = null; + waitForAliveObjectIndex(null); - AddAssert("no alive objects", () => getNextAliveObject() == null); + seekBeforeIndex(0); + waitForAliveObjectIndex(0); + checkValidObjectIndex(0); - AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]); + AddAssert("first object not hit", () => getNextAliveObject()?.Entry?.Result?.HasResult != true); - AddUntilStep("get next object", () => + AddStep("hit first object", () => { - var nextDrawableObject = getNextAliveObject(); + var next = getNextAliveObject(); - if (nextDrawableObject != null) + if (next != null) { - nextObjectEntry = nextDrawableObject.Entry; - InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre); - return true; + Debug.Assert(next.Entry?.Result?.HasResult != true); + + InputManager.MoveMouseTo(next.ScreenSpaceDrawQuad.Centre); + InputManager.Click(MouseButton.Left); } - - return false; }); - AddUntilStep("hit first hitobject", () => - { - InputManager.Click(MouseButton.Left); - return nextObjectEntry?.Result?.HasResult == true; - }); + AddAssert("first object hit", () => getNextAliveObject()?.Entry?.Result?.HasResult == true); - AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]); + checkValidObjectIndex(1); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]); - AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + // Still object 1 as it's not hit yet. + seekBeforeIndex(1); + waitForAliveObjectIndex(1); + checkValidObjectIndex(1); - AddUntilStep("no alive objects", () => getNextAliveObject() == null); - AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]); + seekBeforeIndex(2); + waitForAliveObjectIndex(2); + checkValidObjectIndex(2); + + seekBeforeIndex(3); + waitForAliveObjectIndex(3); + checkValidObjectIndex(3); + + AddStep("Seek into future", () => Beatmap.Value.Track.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000)); + + waitForAliveObjectIndex(null); + checkValidObjectIndex(3); } + private void seekBeforeIndex(int index) => + AddStep($"seek to just before object {index}", () => Beatmap.Value.Track.Seek(beatmap.HitObjects[index].StartTime - 100)); + + private void waitForAliveObjectIndex(int? index) + { + if (index == null) + AddUntilStep("wait for no alive objects", getNextAliveObject, () => Is.Null); + else + AddUntilStep($"wait for next alive to be {index}", () => getNextAliveObject()?.HitObject, () => Is.EqualTo(beatmap.HitObjects[index.Value])); + } + + private void checkValidObjectIndex(int index) => + AddAssert($"check valid object is {index}", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[index])); + private DrawableHitObject? getNextAliveObject() => Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault(); From 394d368f165431e7bb58bc4d365ee7f7fbb6614b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Feb 2023 18:45:22 +0900 Subject: [PATCH 14/18] Fix song select potentially updating background parameters when not the current screen --- .../Screens/Play/ScreenWithBeatmapBackground.cs | 11 ++++++++--- osu.Game/Screens/Select/SongSelect.cs | 16 ++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index d6c8a0ad6a..a5d1961bd8 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,9 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; +using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; namespace osu.Game.Screens.Play @@ -12,6 +12,11 @@ namespace osu.Game.Screens.Play { protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); - public void ApplyToBackground(Action action) => base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + public void ApplyToBackground(Action action) + { + Debug.Assert(this.IsCurrentScreen()); + + base.ApplyToBackground(b => action.Invoke((BackgroundScreenBeatmap)b)); + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 8546dfeeaa..8a9a20261e 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -765,14 +765,18 @@ namespace osu.Game.Screens.Select /// The working beatmap. private void updateComponentFromBeatmap(WorkingBeatmap beatmap) { - ApplyToBackground(backgroundModeBeatmap => + // If not the current screen, this will be applied in OnResuming. + if (this.IsCurrentScreen()) { - backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.IgnoreUserSettings.Value = true; - backgroundModeBeatmap.FadeColour(Color4.White, 250); + ApplyToBackground(backgroundModeBeatmap => + { + backgroundModeBeatmap.Beatmap = beatmap; + backgroundModeBeatmap.IgnoreUserSettings.Value = true; + backgroundModeBeatmap.FadeColour(Color4.White, 250); - applyBlurToBackground(backgroundModeBeatmap); - }); + applyBlurToBackground(backgroundModeBeatmap); + }); + } beatmapInfoWedge.Beatmap = beatmap; From 5bdc5dfadde8c7b4d32abbd81e2555718f803366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 19:02:51 +0100 Subject: [PATCH 15/18] Add one more assert to keep coverage from previous implementation --- .../Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs index e7bdf7b9ba..6770309a7d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs @@ -91,6 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestCorrectHitObject() { waitForAliveObjectIndex(null); + checkValidObjectIndex(0); seekBeforeIndex(0); waitForAliveObjectIndex(0); From ad5132ed4194380bae37331b2746333a5acead3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 20:47:51 +0100 Subject: [PATCH 16/18] Remove redundant conditional access qualifier It is impossible for the callback passed to `ApplyToBackground()` to receive a null reference. See `OsuScreen.ApplyToBackground()` - if the background to call the callback on were `null`, then an `InvalidOperationException` would be thrown instead. --- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d9062da8aa..4f7e4add32 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -417,7 +417,7 @@ namespace osu.Game.Screens.Play lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) - ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); + ApplyToBackground(b => b.FadeColour(Color4.White, 800, Easing.OutQuint)); } protected virtual void ContentOut() From b8084a15ebc9a5bb278b46c7ea31d478063245e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 16 Feb 2023 21:26:01 +0100 Subject: [PATCH 17/18] Revert `ResumeOverlay` setter accessibility change --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d57aec1271..2f8b101cfc 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -505,7 +505,7 @@ namespace osu.Game.Rulesets.UI /// /// An optional overlay used when resuming gameplay from a paused state. /// - public ResumeOverlay ResumeOverlay { get; set; } + public ResumeOverlay ResumeOverlay { get; protected set; } /// /// Whether the should be used to return the user's cursor position to its previous location after a pause. From 449e5fa6f82d10a9006db2ed5d96ffbe9ebd0a26 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Feb 2023 22:09:52 +0300 Subject: [PATCH 18/18] Rename one more left-over `skinnable` naming --- osu.Game/Skinning/SerialisedDrawableInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/SerialisedDrawableInfo.cs b/osu.Game/Skinning/SerialisedDrawableInfo.cs index 12f6d3ac7e..29078f0d74 100644 --- a/osu.Game/Skinning/SerialisedDrawableInfo.cs +++ b/osu.Game/Skinning/SerialisedDrawableInfo.cs @@ -64,8 +64,8 @@ namespace osu.Game.Skinning Anchor = component.Anchor; Origin = component.Origin; - if (component is ISerialisableDrawable skinnable) - UsesFixedAnchor = skinnable.UsesFixedAnchor; + if (component is ISerialisableDrawable serialisableDrawable) + UsesFixedAnchor = serialisableDrawable.UsesFixedAnchor; foreach (var (_, property) in component.GetSettingsSourceProperties()) {