diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 08ac55508a..a06435583b 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -4,6 +4,7 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
switch (targetComponent.Lookup)
{
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer;
+ var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 264a1bd5ec..25d0573a82 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.Tests/Mods/TestSceneOsuModAutopilot.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
new file mode 100644
index 0000000000..37b31d1d1a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
@@ -0,0 +1,31 @@
+// 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 osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests.Mods
+{
+ public partial class TestSceneOsuModAutopilot : OsuModTestScene
+ {
+ [Test]
+ public void TestInstantResume()
+ {
+ 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));
+ AddStep("press resume", () => InputManager.PressKey(Key.Escape));
+ AddUntilStep("wait for resume", () => !Player.IsResuming);
+ AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 6772cfe0be..9eb0a46bfb 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -60,6 +60,8 @@ namespace osu.Game.Rulesets.Osu.Mods
// 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.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.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 5cd8c00935..94cb8517e9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -1,13 +1,12 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Lists;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -28,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
{
- private ISkin currentBeatmapSkin;
+ private ISkin currentBeatmapSkin = null!;
[Resolved]
- private SkinManager skinManager { get; set; }
+ private SkinManager skinManager { get; set; } = null!;
protected override bool HasCustomSteps => true;
@@ -57,15 +56,15 @@ namespace osu.Game.Tests.Visual.Gameplay
protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource)
{
- var actualComponentsContainer = Player.ChildrenOfType().First(s => s.Target == target)
- .ChildrenOfType().SingleOrDefault();
+ var targetContainer = Player.ChildrenOfType().First(s => s.Target == target);
+ var actualComponentsContainer = targetContainer.ChildrenOfType().SingleOrDefault(c => c.Parent == targetContainer);
if (actualComponentsContainer == null)
return false;
var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
- var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target));
+ var expectedComponentsContainer = expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target)) as Container;
if (expectedComponentsContainer == null)
return false;
@@ -92,7 +91,7 @@ namespace osu.Game.Tests.Visual.Gameplay
return almostEqual(actualInfo, expectedInfo);
}
- private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
+ private static bool almostEqual(SkinnableInfo info, SkinnableInfo? other) =>
other != null
&& info.Type == other.Type
&& info.Anchor == other.Anchor
@@ -102,7 +101,7 @@ namespace osu.Game.Tests.Visual.Gameplay
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
@@ -111,7 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private readonly ISkin beatmapSkin;
- public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
: base(beatmap, storyboard, referenceClock, audio)
{
this.beatmapSkin = beatmapSkin;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
index e184d50d7c..6770309a7d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySampleTriggerSource.cs
@@ -1,10 +1,12 @@
// 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.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;
@@ -14,16 +16,23 @@ 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
{
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!;
+
+ [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)
{
@@ -39,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) }
},
@@ -80,42 +90,66 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestCorrectHitObject()
{
- HitObjectLifetimeEntry nextObjectEntry = null;
+ waitForAliveObjectIndex(null);
+ checkValidObjectIndex(0);
- 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 DrawableHitObject getNextAliveObject() =>
+ 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();
[Test]
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/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 39f0888882..40b51ddcca 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -650,18 +650,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 .
@@ -683,7 +671,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();
diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs
index 71b452c309..2f8b101cfc 100644
--- a/osu.Game/Rulesets/UI/DrawableRuleset.cs
+++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs
@@ -230,7 +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))))
+ if (ResumeOverlay != null && UseResumeOverlay && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre))))
{
ResumeOverlay.GameplayCursor = Cursor;
ResumeOverlay.ResumeAction = continueResume;
@@ -507,6 +507,15 @@ namespace osu.Game.Rulesets.UI
///
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.
+ ///
+ ///
+ /// 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;
+
///
/// Returns first available provided by a .
///
@@ -531,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;
///
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)
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()
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 8786821c77..8a9a20261e 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
@@ -763,12 +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.BlurAmount.Value = configBackgroundBlur.Value ? BACKGROUND_BLUR : 0f;
- backgroundModeBeatmap.FadeColour(Color4.White, 250);
- });
+ ApplyToBackground(backgroundModeBeatmap =>
+ {
+ backgroundModeBeatmap.Beatmap = beatmap;
+ backgroundModeBeatmap.IgnoreUserSettings.Value = true;
+ backgroundModeBeatmap.FadeColour(Color4.White, 250);
+
+ applyBlurToBackground(backgroundModeBeatmap);
+ });
+ }
beatmapInfoWedge.Beatmap = beatmap;
@@ -785,6 +793,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 da12c1a67a..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.5f),
+ 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.5f),
+ Colour = Color4.Black,
Shear = new Vector2(-0.15f, 0),
EdgeSmoothness = new Vector2(2, 0),
},
diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs
index 53c6c1e5ce..71f0888c6f 100644
--- a/osu.Game/Skinning/ArgonSkin.cs
+++ b/osu.Game/Skinning/ArgonSkin.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Skinning
switch (globalLookup.Lookup)
{
case GlobalSkinComponentLookup.LookupType.SongSelect:
- var songSelectComponents = new SkinnableTargetComponentsContainer(_ =>
+ var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{
// do stuff when we need to.
});
@@ -102,7 +102,7 @@ namespace osu.Game.Skinning
return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
diff --git a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs
similarity index 61%
rename from osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
rename to osu.Game/Skinning/DefaultSkinComponentsContainer.cs
index 8c6726c3f4..0d6e6b6ce9 100644
--- a/osu.Game/Skinning/SkinnableTargetComponentsContainer.cs
+++ b/osu.Game/Skinning/DefaultSkinComponentsContainer.cs
@@ -2,39 +2,28 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using Newtonsoft.Json;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
{
///
- /// A container which groups the components of a into a single object.
- /// Optionally also applies a default layout to the components.
+ /// A container which can be used to specify default skin components layouts.
+ /// Handles applying a default layout to the components.
///
- [Serializable]
- public partial class SkinnableTargetComponentsContainer : Container, ISkinnableDrawable
+ public partial class DefaultSkinComponentsContainer : Container
{
- public bool IsEditable => false;
-
- public bool UsesFixedAnchor { get; set; }
-
private readonly Action? applyDefaults;
///
/// Construct a wrapper with defaults that should be applied once.
///
/// A function to apply the default layout.
- public SkinnableTargetComponentsContainer(Action applyDefaults)
- : this()
- {
- this.applyDefaults = applyDefaults;
- }
-
- [JsonConstructor]
- public SkinnableTargetComponentsContainer()
+ public DefaultSkinComponentsContainer(Action applyDefaults)
{
RelativeSizeAxes = Axes.Both;
+
+ this.applyDefaults = applyDefaults;
}
protected override void LoadComplete()
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index b2619fa55b..284a938bce 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -347,7 +347,7 @@ namespace osu.Game.Skinning
switch (target.Lookup)
{
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index 37e98946c9..c55b4e789c 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -11,6 +11,7 @@ using Newtonsoft.Json;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
@@ -174,8 +175,9 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo)
components.Add(i.CreateInstance());
- return new SkinnableTargetComponentsContainer
+ return new Container
{
+ RelativeSizeAxes = Axes.Both,
Children = components,
};
}
diff --git a/osu.Game/Skinning/SkinnableTargetContainer.cs b/osu.Game/Skinning/SkinnableTargetContainer.cs
index df5299d427..b21e51c0cf 100644
--- a/osu.Game/Skinning/SkinnableTargetContainer.cs
+++ b/osu.Game/Skinning/SkinnableTargetContainer.cs
@@ -7,13 +7,14 @@ using System.Linq;
using System.Threading;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Skinning
{
public partial class SkinnableTargetContainer : SkinReloadableDrawable, ISkinnableTarget
{
- private SkinnableTargetComponentsContainer? content;
+ private Container? content;
public GlobalSkinComponentLookup.LookupType Target { get; }
@@ -39,15 +40,16 @@ namespace osu.Game.Skinning
foreach (var i in skinnableInfo)
drawables.Add(i.CreateInstance());
- Reload(new SkinnableTargetComponentsContainer
+ Reload(new Container
{
+ RelativeSizeAxes = Axes.Both,
Children = drawables,
});
}
- public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as SkinnableTargetComponentsContainer);
+ public void Reload() => Reload(CurrentSkin.GetDrawableComponent(new GlobalSkinComponentLookup(Target)) as Container);
- public void Reload(SkinnableTargetComponentsContainer? componentsContainer)
+ public void Reload(Container? componentsContainer)
{
ClearInternal();
components.Clear();
diff --git a/osu.Game/Skinning/TrianglesSkin.cs b/osu.Game/Skinning/TrianglesSkin.cs
index 62ef94691b..7de2b124e7 100644
--- a/osu.Game/Skinning/TrianglesSkin.cs
+++ b/osu.Game/Skinning/TrianglesSkin.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Skinning
switch (target.Lookup)
{
case GlobalSkinComponentLookup.LookupType.SongSelect:
- var songSelectComponents = new SkinnableTargetComponentsContainer(_ =>
+ var songSelectComponents = new DefaultSkinComponentsContainer(_ =>
{
// do stuff when we need to.
});
@@ -80,7 +80,7 @@ namespace osu.Game.Skinning
return songSelectComponents;
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
- var skinnableTargetWrapper = new SkinnableTargetComponentsContainer(container =>
+ var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
{
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
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;