From 7f957d3fbef45d745d9ebc545eefa6050fa3b84f Mon Sep 17 00:00:00 2001 From: chayleaf Date: Sat, 15 Jul 2023 20:24:40 +0700 Subject: [PATCH 1/8] Fix some taiko maps not finishing in some conditions I don't know how to reproduce this issue in a test, so no tests for now. Nonetheless, this fixes the issue for me at least on one map: https://osu.ppy.sh/beatmapsets/1899665#taiko/3915653 This workaround is similar to #16475 (the test from that commit got eventually removed for some reason). --- .../Objects/Drawables/DrawableStrongNestedHit.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 9b410d1871da..eb31708e20b8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -16,5 +16,13 @@ protected DrawableStrongNestedHit(StrongNestedHitObject? nestedHit) : base(nestedHit) { } + + public override void OnKilled() + { + base.OnKilled(); + + if (!Judged) + ApplyResult(r => r.Type = r.Judgement.MinResult); + } } } From 3e91d308254d63125e47a3a92f6d52bc9c2d551e Mon Sep 17 00:00:00 2001 From: chayleaf Date: Sat, 15 Jul 2023 22:33:16 +0700 Subject: [PATCH 2/8] move StrongNestedHit OnKilled to DrawableStrongNestedHit --- .../Objects/Drawables/DrawableDrumRoll.cs | 8 -------- .../Objects/Drawables/DrawableDrumRollTick.cs | 8 -------- .../Objects/Drawables/DrawableStrongNestedHit.cs | 3 ++- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 005d2ab1ac46..2bf0c04adff9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -195,14 +195,6 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public override void OnKilled() - { - base.OnKilled(); - - if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged) - ApplyResult(r => r.Type = r.Judgement.MinResult); - } - public override bool OnPressed(KeyBindingPressEvent e) => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index abecd19545f8..c900165d34f4 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -108,14 +108,6 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public override void OnKilled() - { - base.OnKilled(); - - if (Time.Current > ParentHitObject.HitObject.GetEndTime() && !Judged) - ApplyResult(r => r.Type = r.Judgement.MinResult); - } - public override bool OnPressed(KeyBindingPressEvent e) => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index eb31708e20b8..4d430987b9f6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -21,7 +22,7 @@ public override void OnKilled() { base.OnKilled(); - if (!Judged) + if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) ApplyResult(r => r.Type = r.Judgement.MinResult); } } From 542916f8570eb757870ab3d612edff9612c1bfee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jul 2023 18:15:42 +0200 Subject: [PATCH 3/8] Bring back test coverage for fail case from #16475 It was inadvertently dropped during refactoring in b185194d07f0ff1e6ebe7eafb796a85491fab118. --- .../Judgements/TestSceneDrumRollJudgements.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs index 21f2b8f1bea2..2f9f5e0a374a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneDrumRollJudgements.cs @@ -75,6 +75,25 @@ public void TestHitNoneDrumRoll() AssertResult(0, HitResult.IgnoreHit); } + [Test] + public void TestHitNoneStrongDrumRoll() + { + PerformTest(new List + { + new TaikoReplayFrame(0), + }, CreateBeatmap(createDrumRoll(true))); + + AssertJudgementCount(12); + + for (int i = 0; i < 5; ++i) + { + AssertResult(i, HitResult.IgnoreMiss); + AssertResult(i, HitResult.IgnoreMiss); + } + + AssertResult(0, HitResult.IgnoreHit); + } + [Test] public void TestHitAllStrongDrumRollWithOneKey() { From 24d63a4d96c99dc0ff55e50929a3f7b96166e5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jul 2023 18:17:45 +0200 Subject: [PATCH 4/8] Add test coverage for failing hit judgement with HD active --- .../Judgements/JudgementTest.cs | 5 +++- .../Judgements/TestSceneHitJudgements.cs | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs index eb2d96ec5143..f3e37736b2d5 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -10,6 +11,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; @@ -36,11 +38,12 @@ protected void AssertResult(int index, HitResult expectedResult) () => Is.EqualTo(expectedResult)); } - protected void PerformTest(List frames, Beatmap? beatmap = null) + protected void PerformTest(List frames, Beatmap? beatmap = null, Mod[]? mods = null) { AddStep("load player", () => { Beatmap.Value = CreateWorkingBeatmap(beatmap); + SelectedMods.Value = mods ?? Array.Empty(); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs index 3bf94eb62ee6..b9e767e625f8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs @@ -6,8 +6,10 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Rulesets.Taiko.Scoring; namespace osu.Game.Rulesets.Taiko.Tests.Judgements { @@ -157,5 +159,31 @@ public void TestHighVelocityHit() AssertJudgementCount(1); AssertResult(0, HitResult.Ok); } + + [Test] + public void TestStrongHitWithHidden() + { + const double hit_time = 1000; + + var beatmap = CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + IsStrong = true + }); + + var hitWindows = new TaikoHitWindows(); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre), + }, beatmap, new[] { new TaikoModHidden() }); + + AssertJudgementCount(2); + AssertResult(0, HitResult.Ok); + AssertResult(0, HitResult.IgnoreMiss); + } } } From 9e960894c2b03029a0d40d20a7c0cf9ef451ce5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jul 2023 18:22:04 +0200 Subject: [PATCH 5/8] Add inline commentary about `OnKilled()` override --- .../Objects/Drawables/DrawableStrongNestedHit.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 4d430987b9f6..724d59edcd4f 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -22,6 +22,10 @@ public override void OnKilled() { base.OnKilled(); + // usually, the strong nested hit isn't judged itself, it is judged by its parent object. + // however, in rare cases (see: drum rolls, hits with hidden active), + // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. + // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) ApplyResult(r => r.Type = r.Judgement.MinResult); } From 955aa70e46835d6200a4fabb5ba18d5bda1b5787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jul 2023 18:43:31 +0200 Subject: [PATCH 6/8] Add failing test case for hitting nested hit past parent end time --- .../Judgements/TestSceneHitJudgements.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs index b9e767e625f8..5e9d4bcf1416 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Taiko.Scoring; @@ -161,7 +162,7 @@ public void TestHighVelocityHit() } [Test] - public void TestStrongHitWithHidden() + public void TestStrongHitOneKeyWithHidden() { const double hit_time = 1000; @@ -185,5 +186,32 @@ public void TestStrongHitWithHidden() AssertResult(0, HitResult.Ok); AssertResult(0, HitResult.IgnoreMiss); } + + [Test] + public void TestStrongHitTwoKeysWithHidden() + { + const double hit_time = 1000; + + var beatmap = CreateBeatmap(new Hit + { + Type = HitType.Centre, + StartTime = hit_time, + IsStrong = true + }); + + var hitWindows = new TaikoHitWindows(); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); + + PerformTest(new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre), + new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) + DrawableHit.StrongNestedHit.SECOND_HIT_WINDOW - 2, TaikoAction.LeftCentre, TaikoAction.RightCentre), + }, beatmap, new[] { new TaikoModHidden() }); + + AssertJudgementCount(2); + AssertResult(0, HitResult.Ok); + AssertResult(0, HitResult.LargeBonus); + } } } From 041e81889209c0a568a42b38227b2cedf9c2d3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 15 Jul 2023 18:44:47 +0200 Subject: [PATCH 7/8] Fix nested hits not being hittable if cut off by parent object ending --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 4708ef9bf06b..2c3b4a8d184d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -62,6 +62,8 @@ protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, hitObject.LifetimeEnd = state == ArmedState.Idle || !hitObject.AllJudged ? hitObject.HitObject.GetEndTime() + hitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) : hitObject.HitStateUpdateTime; + // extend the lifetime end of the object in order to allow its nested strong hit (if any) to be judged. + hitObject.LifetimeEnd += DrawableHit.StrongNestedHit.SECOND_HIT_WINDOW; } break; From 416ee0d99cb0ddb344ec6183fb15278f45295b2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 16 Jul 2023 12:48:58 +0900 Subject: [PATCH 8/8] Fix covariant array spec in new test --- .../Judgements/TestSceneHitJudgements.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs index 5e9d4bcf1416..32966905c9b2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Mods; @@ -180,7 +181,7 @@ public void TestStrongHitOneKeyWithHidden() { new TaikoReplayFrame(0), new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre), - }, beatmap, new[] { new TaikoModHidden() }); + }, beatmap, new Mod[] { new TaikoModHidden() }); AssertJudgementCount(2); AssertResult(0, HitResult.Ok); @@ -207,7 +208,7 @@ public void TestStrongHitTwoKeysWithHidden() new TaikoReplayFrame(0), new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre), new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) + DrawableHit.StrongNestedHit.SECOND_HIT_WINDOW - 2, TaikoAction.LeftCentre, TaikoAction.RightCentre), - }, beatmap, new[] { new TaikoModHidden() }); + }, beatmap, new Mod[] { new TaikoModHidden() }); AssertJudgementCount(2); AssertResult(0, HitResult.Ok);