Skip to content

Commit

Permalink
Merge pull request ppy#24233 from chayleaf/fix-taiko-maps-not-finishing
Browse files Browse the repository at this point in the history
Fix taiko maps sporadically not completing with Hidden mod active
  • Loading branch information
peppy authored Jul 16, 2023
2 parents 2d51aa2 + 416ee0d commit 50e6f0a
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 17 deletions.
5 changes: 4 additions & 1 deletion osu.Game.Rulesets.Taiko.Tests/Judgements/JudgementTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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;
Expand All @@ -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;
Expand All @@ -36,11 +38,12 @@ protected void AssertResult<T>(int index, HitResult expectedResult)
() => Is.EqualTo(expectedResult));
}

protected void PerformTest(List<ReplayFrame> frames, Beatmap<TaikoHitObject>? beatmap = null)
protected void PerformTest(List<ReplayFrame> frames, Beatmap<TaikoHitObject>? beatmap = null, Mod[]? mods = null)
{
AddStep("load player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(beatmap);
SelectedMods.Value = mods ?? Array.Empty<Mod>();
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,25 @@ public void TestHitNoneDrumRoll()
AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}

[Test]
public void TestHitNoneStrongDrumRoll()
{
PerformTest(new List<ReplayFrame>
{
new TaikoReplayFrame(0),
}, CreateBeatmap(createDrumRoll(true)));

AssertJudgementCount(12);

for (int i = 0; i < 5; ++i)
{
AssertResult<DrumRollTick>(i, HitResult.IgnoreMiss);
AssertResult<DrumRollTick.StrongNestedHit>(i, HitResult.IgnoreMiss);
}

AssertResult<DrumRoll>(0, HitResult.IgnoreHit);
}

[Test]
public void TestHitAllStrongDrumRollWithOneKey()
{
Expand Down
57 changes: 57 additions & 0 deletions osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneHitJudgements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
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;
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;

namespace osu.Game.Rulesets.Taiko.Tests.Judgements
{
Expand Down Expand Up @@ -157,5 +161,58 @@ public void TestHighVelocityHit()
AssertJudgementCount(1);
AssertResult<Hit>(0, HitResult.Ok);
}

[Test]
public void TestStrongHitOneKeyWithHidden()
{
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<ReplayFrame>
{
new TaikoReplayFrame(0),
new TaikoReplayFrame(hit_time + hitWindows.WindowFor(HitResult.Ok) - 1, TaikoAction.LeftCentre),
}, beatmap, new Mod[] { new TaikoModHidden() });

AssertJudgementCount(2);
AssertResult<Hit>(0, HitResult.Ok);
AssertResult<Hit.StrongNestedHit>(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<ReplayFrame>
{
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 Mod[] { new TaikoModHidden() });

AssertJudgementCount(2);
AssertResult<Hit>(0, HitResult.Ok);
AssertResult<Hit.StrongNestedHit>(0, HitResult.LargeBonus);
}
}
}
2 changes: 2 additions & 0 deletions osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 0 additions & 8 deletions osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TaikoAction> e) => false;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TaikoAction> e) => false;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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
Expand All @@ -16,5 +17,17 @@ protected DrawableStrongNestedHit(StrongNestedHitObject? nestedHit)
: base(nestedHit)
{
}

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);
}
}
}

0 comments on commit 50e6f0a

Please sign in to comment.