diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs index 4abad98eab..6e42ae7eb5 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs @@ -115,6 +115,48 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AddAssert("all tick offsets are 0", () => JudgementResults.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); } + [Test] + public void TestAtMostOneSwellTickJudgedPerFrame() + { + const double swell_time = 1000; + + Swell swell = new Swell + { + StartTime = swell_time, + Duration = 1000, + RequiredHits = 10 + }; + + List frames = new List + { + new TaikoReplayFrame(1000), + new TaikoReplayFrame(1250, TaikoAction.LeftCentre, TaikoAction.LeftRim), + new TaikoReplayFrame(1251), + new TaikoReplayFrame(1500, TaikoAction.LeftCentre, TaikoAction.LeftRim, TaikoAction.RightCentre, TaikoAction.RightRim), + new TaikoReplayFrame(1501), + new TaikoReplayFrame(2000), + }; + + PerformTest(frames, CreateBeatmap(swell)); + + AssertJudgementCount(11); + + // this is a charitable interpretation of the inputs. + // + // for the frame at time 1250, we only count either one of the input actions - simple. + // + // for the frame at time 1500, we give the user the benefit of the doubt, + // and we ignore actions that wouldn't otherwise cause a hit due to not alternating, + // but we still count one (just one) of the actions that _would_ normally cause a hit. + // this is done as a courtesy to avoid stuff like key chattering after press blocking legitimate inputs. + for (int i = 0; i < 2; i++) + AssertResult(i, HitResult.IgnoreHit); + for (int i = 2; i < swell.RequiredHits; i++) + AssertResult(i, HitResult.IgnoreMiss); + + AssertResult(0, HitResult.IgnoreMiss); + } + /// /// Ensure input is correctly sent to subsequent hits if a swell is fully completed. /// diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 3fa6f4b756..a8b48450e8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Skinning.Default; +using osu.Game.Screens.Play; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -38,6 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private readonly CircularContainer targetRing; private readonly CircularContainer expandingRing; + private double? lastPressHandleTime; + public override bool DisplayResult => false; public DrawableSwell() @@ -140,6 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables UnproxyContent(); lastWasCentre = null; + lastPressHandleTime = null; } protected override void AddNestedHitObject(DrawableHitObject hitObject) @@ -266,6 +270,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ProxyContent(); else UnproxyContent(); + + if ((Clock as IGameplayClock)?.IsRewinding == true) + lastPressHandleTime = null; } private bool? lastWasCentre; @@ -285,7 +292,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (lastWasCentre == isCentre) return false; + // If we've already successfully judged a tick this frame, do not judge more. + // Note that the ordering is important here - this is intentionally placed after the alternating check. + // That is done to prevent accidental double inputs blocking simultaneous but legitimate hits from registering. + if (lastPressHandleTime == Time.Current) + return true; + lastWasCentre = isCentre; + lastPressHandleTime = Time.Current; UpdateResult(true);