From 78b30a076f8a0a8e8cb6caba99f34c8f5e0d3579 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 15:40:14 +0900 Subject: [PATCH 1/8] Add mania player test scene --- osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs new file mode 100644 index 0000000000..cd25d162d0 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs @@ -0,0 +1,15 @@ +// 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.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestScenePlayer : PlayerTestScene + { + public TestScenePlayer() + : base(new ManiaRuleset()) + { + } + } +} From 42853b5af645e4ead92c2d1ed7d9077c4d924189 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 15:43:58 +0900 Subject: [PATCH 2/8] Separate head/tail notes from hold note class --- .../Objects/Drawables/DrawableHoldNote.cs | 129 +++--------------- .../Objects/Drawables/DrawableHoldNoteHead.cs | 37 +++++ .../Objects/Drawables/DrawableHoldNoteTail.cs | 74 ++++++++++ 3 files changed, 127 insertions(+), 113 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs create mode 100644 osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 87b9633c80..ca3b966d40 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -24,8 +23,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public DrawableNote Head => headContainer.Child; public DrawableNote Tail => tailContainer.Child; - private readonly Container headContainer; - private readonly Container tailContainer; + private readonly Container headContainer; + private readonly Container tailContainer; private readonly Container tickContainer; private readonly BodyPiece bodyPiece; @@ -33,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// - private double? holdStartTime; + internal double? HoldStartTime; /// /// Whether the hold note has been released too early and shouldn't give full score for the release. /// - private bool hasBroken; + internal bool HasBroken; public DrawableHoldNote(HoldNote hitObject) : base(hitObject) @@ -49,8 +48,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, - headContainer = new Container { RelativeSizeAxes = Axes.Both }, - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, + headContainer = new Container { RelativeSizeAxes = Axes.Both }, + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }); AccentColour.BindValueChanged(colour => @@ -65,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables switch (hitObject) { - case DrawableHeadNote head: + case DrawableHoldNoteHead head: headContainer.Child = head; break; - case DrawableTailNote tail: + case DrawableHoldNoteTail tail: tailContainer.Child = tail; break; @@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables switch (hitObject) { case TailNote _: - return new DrawableTailNote(this) + return new DrawableHoldNoteTail(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }; case Note _: - return new DrawableHeadNote(this) + return new DrawableHoldNoteHead(this) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables case HoldNoteTick tick: return new DrawableHoldNoteTick(tick) { - HoldStartTime = () => holdStartTime, + HoldStartTime = () => HoldStartTime, AccentColour = { BindTarget = AccentColour } }; } @@ -146,15 +145,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.UpdateStateTransforms(state); } - protected void BeginHold() + internal void BeginHold() { - holdStartTime = Time.Current; + HoldStartTime = Time.Current; bodyPiece.Hitting = true; } protected void EndHold() { - holdStartTime = null; + HoldStartTime = null; bodyPiece.Hitting = false; } @@ -177,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables public bool OnReleased(ManiaAction action) { // Make sure that the user started holding the key during the hold note - if (!holdStartTime.HasValue) + if (!HoldStartTime.HasValue) return false; if (action != Action.Value) @@ -187,105 +186,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) - hasBroken = true; + HasBroken = true; return true; } - - /// - /// The head note of a hold. - /// - private class DrawableHeadNote : DrawableNote - { - private readonly DrawableHoldNote holdNote; - - public DrawableHeadNote(DrawableHoldNote holdNote) - : base(holdNote.HitObject.Head) - { - this.holdNote = holdNote; - } - - public override bool OnPressed(ManiaAction action) - { - if (!base.OnPressed(action)) - return false; - - // If the key has been released too early, the user should not receive full score for the release - if (Result.Type == HitResult.Miss) - holdNote.hasBroken = true; - - // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held - // The body doesn't handle these early early hits, so we have to explicitly set the holding state here - holdNote.BeginHold(); - - return true; - } - } - - /// - /// The tail note of a hold. - /// - private class DrawableTailNote : 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; - - private readonly DrawableHoldNote holdNote; - - public DrawableTailNote(DrawableHoldNote holdNote) - : base(holdNote.HitObject.Tail) - { - this.holdNote = holdNote; - } - - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - Debug.Assert(HitObject.HitWindows != null); - - // Factor in the release lenience - timeOffset /= release_window_lenience; - - if (!userTriggered) - { - if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); - - return; - } - - var result = HitObject.HitWindows.ResultFor(timeOffset); - if (result == HitResult.None) - return; - - ApplyResult(r => - { - if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect)) - result = HitResult.Good; - - r.Type = result; - }); - } - - public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down - - public override bool OnReleased(ManiaAction action) - { - // Make sure that the user started holding the key during the hold note - if (!holdNote.holdStartTime.HasValue) - return false; - - if (action != Action.Value) - return false; - - UpdateResult(true); - - // Handled by the hold note, which will set holding = false - return false; - } - } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs new file mode 100644 index 0000000000..a1c9a09014 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -0,0 +1,37 @@ +// 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.Scoring; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// The head of a . + /// + public class DrawableHoldNoteHead : DrawableNote + { + private readonly DrawableHoldNote holdNote; + + public DrawableHoldNoteHead(DrawableHoldNote holdNote) + : base(holdNote.HitObject.Head) + { + this.holdNote = holdNote; + } + + public override bool OnPressed(ManiaAction action) + { + if (!base.OnPressed(action)) + return false; + + // If the key has been released too early, the user should not receive full score for the release + if (Result.Type == HitResult.Miss) + holdNote.HasBroken = true; + + // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held + // The body doesn't handle these early early hits, so we have to explicitly set the holding state here + holdNote.BeginHold(); + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs new file mode 100644 index 0000000000..2b23fb1eef --- /dev/null +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Mania.Objects.Drawables +{ + /// + /// The tail of a . + /// + public 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; + + private readonly DrawableHoldNote holdNote; + + public DrawableHoldNoteTail(DrawableHoldNote holdNote) + : base(holdNote.HitObject.Tail) + { + this.holdNote = holdNote; + } + + protected override void CheckForResult(bool userTriggered, double timeOffset) + { + Debug.Assert(HitObject.HitWindows != null); + + // Factor in the release lenience + timeOffset /= release_window_lenience; + + if (!userTriggered) + { + if (!HitObject.HitWindows.CanBeHit(timeOffset)) + ApplyResult(r => r.Type = HitResult.Miss); + + return; + } + + var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) + return; + + ApplyResult(r => + { + if (holdNote.HasBroken && (result == HitResult.Perfect || result == HitResult.Perfect)) + result = HitResult.Good; + + r.Type = result; + }); + } + + public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down + + public override bool OnReleased(ManiaAction action) + { + // Make sure that the user started holding the key during the hold note + if (!holdNote.HoldStartTime.HasValue) + return false; + + if (action != Action.Value) + return false; + + UpdateResult(true); + + // Handled by the hold note, which will set holding = false + return false; + } + } +} From 7ac6f68de8c4b7ce07d5aab461ff6cb9831558ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 17:48:48 +0900 Subject: [PATCH 3/8] Rewrite hold note input handling --- .../HoldNoteNoteSelectionBlueprint.cs | 2 +- .../Objects/Drawables/DrawableHoldNote.cs | 58 +++++++++++-------- .../Objects/Drawables/DrawableHoldNoteHead.cs | 21 +------ .../Objects/Drawables/DrawableHoldNoteTail.cs | 19 ++---- 4 files changed, 41 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs index acce41db6f..4e73883de0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. if (DrawableObject.IsLoaded) { - DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail; + DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail; Anchor = note.Anchor; Origin = note.Origin; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index ca3b966d40..c4a3e23cce 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public override bool DisplayResult => false; - public DrawableNote Head => headContainer.Child; - public DrawableNote Tail => tailContainer.Child; + public DrawableHoldNoteHead Head => headContainer.Child; + public DrawableHoldNoteTail Tail => tailContainer.Child; private readonly Container headContainer; private readonly Container tailContainer; @@ -124,12 +124,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } - protected override void CheckForResult(bool userTriggered, double timeOffset) - { - if (Tail.AllJudged) - ApplyResult(r => r.Type = HitResult.Perfect); - } - protected override void Update() { base.Update(); @@ -145,44 +139,52 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.UpdateStateTransforms(state); } - internal void BeginHold() + protected override void CheckForResult(bool userTriggered, double timeOffset) { - HoldStartTime = Time.Current; - bodyPiece.Hitting = true; - } + if (Tail.AllJudged) + ApplyResult(r => r.Type = HitResult.Perfect); - protected void EndHold() - { - HoldStartTime = null; - bodyPiece.Hitting = false; + if (Tail.Result.Type == HitResult.Miss) + HasBroken = true; } public bool OnPressed(ManiaAction action) { - // Make sure the action happened within the body of the hold note - if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) + if (AllJudged) return false; if (action != Action.Value) return false; - // The user has pressed during the body of the hold note, after the head note and its hit windows have passed - // and within the limited range of the above if-statement. This state will be managed by the head note if the - // user has pressed during the hit windows of the head note. - BeginHold(); + beginHoldAt(Time.Current - Head.HitObject.StartTime); + Head.UpdateResult(); + return true; } + private void beginHoldAt(double timeOffset) + { + if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss)) + return; + + HoldStartTime = Time.Current; + bodyPiece.Hitting = true; + } + public bool OnReleased(ManiaAction action) { - // Make sure that the user started holding the key during the hold note - if (!HoldStartTime.HasValue) + if (AllJudged) return false; if (action != Action.Value) return false; - EndHold(); + // Make sure a hold was started + if (HoldStartTime == null) + return false; + + Tail.UpdateResult(); + endHold(); // If the key has been released too early, the user should not receive full score for the release if (!Tail.IsHit) @@ -190,5 +192,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return true; } + + private void endHold() + { + HoldStartTime = null; + bodyPiece.Hitting = false; + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs index a1c9a09014..a5d03bf765 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs @@ -1,8 +1,6 @@ // 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.Scoring; - namespace osu.Game.Rulesets.Mania.Objects.Drawables { /// @@ -10,28 +8,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteHead : DrawableNote { - private readonly DrawableHoldNote holdNote; - public DrawableHoldNoteHead(DrawableHoldNote holdNote) : base(holdNote.HitObject.Head) { - this.holdNote = holdNote; } - public override bool OnPressed(ManiaAction action) - { - if (!base.OnPressed(action)) - return false; + public void UpdateResult() => base.UpdateResult(true); - // If the key has been released too early, the user should not receive full score for the release - if (Result.Type == HitResult.Miss) - holdNote.HasBroken = true; + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note - // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held - // The body doesn't handle these early early hits, so we have to explicitly set the holding state here - holdNote.BeginHold(); - - return true; - } + public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 2b23fb1eef..a1f34a6db2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables this.holdNote = holdNote; } + public void UpdateResult() => base.UpdateResult(true); + protected override void CheckForResult(bool userTriggered, double timeOffset) { Debug.Assert(HitObject.HitWindows != null); @@ -54,21 +56,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down + public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note - public override bool OnReleased(ManiaAction action) - { - // Make sure that the user started holding the key during the hold note - if (!holdNote.HoldStartTime.HasValue) - return false; - - if (action != Action.Value) - return false; - - UpdateResult(true); - - // Handled by the hold note, which will set holding = false - return false; - } + public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note } } From 7bb4a08f10a534246bd4f5ccce2c23e89044506e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 18:35:40 +0900 Subject: [PATCH 4/8] Add failing tests --- .../TestSceneHoldNoteInput.cs | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs new file mode 100644 index 0000000000..8682604b13 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -0,0 +1,314 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene + { + private const double time_before_head = 250; + private const double time_head = 1500; + private const double time_during_hold_1 = 2500; + private const double time_tail = 4000; + private const double time_after_tail = 5250; + + private List judgementResults; + private bool allJudgedFired; + + /// + /// -----[ ]----- + /// o o + /// + [Test] + public void TestNoInput() + { + performTest(new List + { + new ManiaReplayFrame(time_before_head), + new ManiaReplayFrame(time_after_tail), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Miss); + assertTailJudgement(HitResult.Miss); + assertNoteJudgement(HitResult.Perfect); + } + + /// + /// -----[ ]----- + /// x o + /// + [Test] + public void TestPressTooEarlyAndReleaseAfterTail() + { + performTest(new List + { + new ManiaReplayFrame(time_before_head, ManiaAction.Key1), + new ManiaReplayFrame(time_after_tail, ManiaAction.Key1), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Miss); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// x o + /// + [Test] + public void TestPressTooEarlyAndReleaseAtTail() + { + performTest(new List + { + new ManiaReplayFrame(time_before_head, ManiaAction.Key1), + new ManiaReplayFrame(time_tail), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Miss); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// xo x o + /// + [Test] + public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail() + { + performTest(new List + { + new ManiaReplayFrame(time_before_head, ManiaAction.Key1), + new ManiaReplayFrame(time_before_head + 10), + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_after_tail), + }); + + assertHeadJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// xo x o + /// + [Test] + public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail() + { + performTest(new List + { + new ManiaReplayFrame(time_before_head, ManiaAction.Key1), + new ManiaReplayFrame(time_before_head + 10), + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_tail), + }); + + assertHeadJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Perfect); + } + + /// + /// -----[ ]----- + /// xo o + /// + [Test] + public void TestPressAtStartAndBreak() + { + performTest(new List + { + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_head + 10), + new ManiaReplayFrame(time_after_tail), + }); + + assertHeadJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.Miss); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// xo x o + /// + [Test] + public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail() + { + performTest(new List + { + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_head + 10), + new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1), + new ManiaReplayFrame(time_after_tail), + }); + + assertHeadJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// xo x o o + /// + [Test] + public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail() + { + performTest(new List + { + new ManiaReplayFrame(time_head, ManiaAction.Key1), + new ManiaReplayFrame(time_head + 10), + new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1), + new ManiaReplayFrame(time_tail), + }); + + assertHeadJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Meh); + } + + /// + /// -----[ ]----- + /// x o + /// + [Test] + public void TestPressDuringNoteAndReleaseAfterTail() + { + performTest(new List + { + new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1), + new ManiaReplayFrame(time_after_tail), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Miss); + } + + /// + /// -----[ ]----- + /// x o o + /// + [Test] + public void TestPressDuringNoteAndReleaseAtTail() + { + performTest(new List + { + new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1), + new ManiaReplayFrame(time_tail), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Perfect); + assertTailJudgement(HitResult.Meh); + } + + /// + /// -----[ ]----- + /// xo o + /// + [Test] + public void TestPressAndReleaseAtTail() + { + performTest(new List + { + new ManiaReplayFrame(time_tail, ManiaAction.Key1), + new ManiaReplayFrame(time_tail + 10), + }); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.Miss); + assertTailJudgement(HitResult.Meh); + } + + private void assertHeadJudgement(HitResult result) + => AddAssert($"head judged as{result}", () => judgementResults[0].Type == result); + + private void assertTailJudgement(HitResult result) + => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result); + + private void assertNoteJudgement(HitResult result) + => AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result); + + private void assertTickJudgement(HitResult result) + => AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick + + private ScoreAccessibleReplayPlayer currentPlayer; + + private void performTest(List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + } + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }); + + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + p.ScoreProcessor.AllJudged += () => + { + if (currentPlayer == p) allJudgedFired = true; + }; + }; + + LoadScreen(currentPlayer = p); + allJudgedFired = false; + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for all judged", () => allJudgedFired); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, false, false) + { + } + } + } +} From d6fd1007c46228c1d4df70c7855ad9bd3f3c3d0d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 18:48:14 +0900 Subject: [PATCH 5/8] internal -> public --- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index c4a3e23cce..155adb958b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -32,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// - internal double? HoldStartTime; + public double? HoldStartTime { get; private set; } /// /// Whether the hold note has been released too early and shouldn't give full score for the release. /// - internal bool HasBroken; + public bool HasBroken { get; private set; } public DrawableHoldNote(HoldNote hitObject) : base(hitObject) From 63c96d5a83560e864d62585aa27e9443bd968236 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 18:49:08 +0900 Subject: [PATCH 6/8] Fix tail note not properly capping result --- .../Objects/Drawables/DrawableHoldNoteTail.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index a1f34a6db2..a660144dd1 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -49,8 +49,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables ApplyResult(r => { - if (holdNote.HasBroken && (result == HitResult.Perfect || result == HitResult.Perfect)) - result = HitResult.Good; + // If the head wasn't hit or the hold note was broken, cap the max score to Meh. + if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken)) + result = HitResult.Meh; r.Type = result; }); From d5288760a7e85fd60320dbbc6b91c88737e8cf7d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 23 Dec 2019 18:50:18 +0900 Subject: [PATCH 7/8] Fix test text --- osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 8682604b13..7b0cf40d45 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Mania.Tests } private void assertHeadJudgement(HitResult result) - => AddAssert($"head judged as{result}", () => judgementResults[0].Type == result); + => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result); private void assertTailJudgement(HitResult result) => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result); From 651abd2e119520b9ff7d46f277040722342e8a33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Dec 2019 21:51:18 +0900 Subject: [PATCH 8/8] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index a43c834406..6ff9416e47 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3f64ce8dfd..806aadde84 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c2d4e3ad8e..230ff01cce 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - +