diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs
index c9aa44eb5a..615044b642 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyHitPolicy.cs
@@ -549,6 +549,8 @@ public void TestInputDoesNotFallThroughOverlappingSliders()
addJudgementOffsetAssert("first slider head", () => ((Slider)hitObjects[0]).HeadCircle, 0);
addJudgementAssert(hitObjects[1], HitResult.Miss);
// the slider head of the first slider prevents the second slider's head from being hit, so the judgement offset should be very late.
+ // this is not strictly done by the hit policy implementation itself (see `OsuModClassic.blockInputToUnderlyingObjects()`),
+ // but we're testing this here anyways to just keep everything related to input handling and note lock in one place.
addJudgementOffsetAssert("second slider head", () => ((Slider)hitObjects[1]).HeadCircle, referenceHitWindows.WindowFor(HitResult.Meh));
addClickActionAssert(0, ClickAction.Hit);
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
index 5dbf23f7ea..1d95f833b0 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
@@ -74,6 +74,10 @@ public void ApplyToDrawableHitObject(DrawableHitObject obj)
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
if (FadeHitCircleEarly.Value && !usingHiddenFading)
applyEarlyFading(head);
+
+ if (ClassicNoteLock.Value)
+ blockInputToUnderlyingObjects(head);
+
break;
case DrawableSliderTail tail:
@@ -83,10 +87,29 @@ public void ApplyToDrawableHitObject(DrawableHitObject obj)
case DrawableHitCircle circle:
if (FadeHitCircleEarly.Value && !usingHiddenFading)
applyEarlyFading(circle);
+
+ if (ClassicNoteLock.Value)
+ blockInputToUnderlyingObjects(circle);
+
break;
}
}
+ ///
+ /// On stable, hitcircles that have already been hit block input from reaching objects that may be underneath them.
+ /// The purpose of this method is to restore that behaviour.
+ /// In order to avoid introducing yet another confusing config option, this behaviour is roped into the general notion of "note lock".
+ ///
+ private static void blockInputToUnderlyingObjects(DrawableHitCircle circle)
+ {
+ var oldHitAction = circle.HitArea.Hit;
+ circle.HitArea.Hit = () =>
+ {
+ oldHitAction?.Invoke();
+ return true;
+ };
+ }
+
private void applyEarlyFading(DrawableHitCircle circle)
{
circle.ApplyCustomUpdateState += (dho, state) =>
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 932f6d3fff..6beed0294d 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -261,7 +261,7 @@ public bool OnPressed(KeyBindingPressEvent e)
case OsuAction.RightButton:
if (IsHovered && (Hit?.Invoke() ?? false))
{
- HitAction = e.Action;
+ HitAction ??= e.Action;
return true;
}