diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs new file mode 100644 index 0000000000..a7416671f6 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoteLock.cs @@ -0,0 +1,180 @@ +// 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 System.Linq; +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.Osu.Objects; +using osu.Game.Rulesets.Osu.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; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneNoteLock : RateAdjustedBeatmapTestScene + { + private const double time_first_circle = 1500; + private const double time_second_circle = 1600; + + private static readonly Vector2 position_first_circle = Vector2.Zero; + private static readonly Vector2 position_second_circle = new Vector2(80); + + /// + /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Miss); + } + + /// + /// Tests clicking the second circle at the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAtFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Miss); + } + + /// + /// Tests clicking the second circle after the first hitobject's start time, while the first hitobject HAS NOT been judged. + /// + [Test] + public void TestClickSecondCircleAfterFirstCircleTime() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle + 100, Position = position_second_circle, Actions = { OsuAction.LeftButton } } + }); + + addJudgementAssert(HitResult.Miss, HitResult.Great); + } + + /// + /// Tests clicking the second circle before the first hitobject's start time, while the first hitobject HAS been judged. + /// + [Test] + public void TestClickSecondCircleBeforeFirstCircleTimeWithFirstCircleJudged() + { + performTest(new List + { + new OsuReplayFrame { Time = time_first_circle - 200, Position = position_first_circle, Actions = { OsuAction.LeftButton } }, + new OsuReplayFrame { Time = time_first_circle - 100, Position = position_second_circle, Actions = { OsuAction.RightButton } } + }); + + addJudgementAssert(HitResult.Great, HitResult.Great); + } + + private void addJudgementAssert(HitResult firstCircle, HitResult secondCircle) + { + AddAssert($"first circle judgement is {firstCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_first_circle).Type == firstCircle); + AddAssert($"second circle judgement is {secondCircle}", () => judgementResults.Single(r => r.HitObject.StartTime == time_second_circle).Type == secondCircle); + } + + private ScoreAccessibleReplayPlayer currentPlayer; + private List judgementResults; + private bool allJudgedFired; + + private void performTest(List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = + { + new TestHitCircle + { + StartTime = time_first_circle, + Position = position_first_circle + }, + new TestHitCircle + { + StartTime = time_second_circle, + Position = position_second_circle + } + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Ruleset = new OsuRuleset().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 TestHitCircle : HitCircle + { + protected override HitWindows CreateHitWindows() => new TestHitWindows(); + } + + private class TestHitWindows : HitWindows + { + private static readonly DifficultyRange[] ranges = + { + new DifficultyRange(HitResult.Great, 500, 500, 500), + new DifficultyRange(HitResult.Miss, 1000, 1000, 1000), + }; + + public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss; + + protected override DifficultyRange[] GetRanges() => ranges; + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, false, false) + { + } + } + } +}