// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; 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.Beatmaps; using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { public class TestCaseSliderInput : TestCaseRateAdjustedBeatmap { public override IReadOnlyList RequiredTypes => new[] { typeof(SliderBall), typeof(DrawableSlider), typeof(DrawableSliderTick), typeof(DrawableRepeatPoint), typeof(DrawableOsuHitObject), typeof(DrawableSliderHead), typeof(DrawableSliderTail), }; [SetUp] public void Setup() => Schedule(() => { allJudgedFired = false; judgementResults = new List(); }); public TestCaseSliderInput() { Beatmap.Value = new TestWorkingBeatmap(new Beatmap { HitObjects = { new Slider { StartTime = time_slider_start, Position = new Vector2(0, 0), Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, new Vector2(25, 0), }, 25), }}, ControlPointInfo = { DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, Ruleset = new OsuRuleset().RulesetInfo }, }); AddUntilStep(() => Beatmap.Value.BeatmapLoaded, "Wait for bm load"); } private const double time_before_slider = 250; private const double time_slider_start = 1500; private const double time_during_slide_1 = 2500; private const double time_during_slide_2 = 3000; private const double time_during_slide_3 = 3500; private const double time_during_slide_4 = 4000; private List judgementResults; private bool allJudgedFired; /// /// Scenario: /// - Press a key before a slider starts /// - Press the other key on the slider head timed correctly while holding the original key /// - Release the latter pressed key /// Expected Result: /// A passing test case will have the cursor lose tracking on replay frame 3. /// [Test] public void TestLeftBeforeSliderThenRight() { AddStep("Invalid key transfer test", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking lost", assertMidSliderJudgementFail); } /// /// Scenario: /// - Press a key on the slider head timed correctly /// - Press the other key in the middle of the slider while holding the original key /// - Release the original key used to hit the slider /// Expected Result: /// A passing test case will have the cursor continue tracking on replay frame 3. /// [Test] public void TestLeftBeforeSliderThenRightThenLettingGoOfLeft() { AddStep("Left to both to right test", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking retained", assertGreatJudge); } /// /// Scenario: /// - Press a key on the slider head timed correctly /// - Press the other key in the middle of the slider while holding the original key /// - Release the new key that was pressed second /// Expected Result: /// A passing test case will have the cursor continue tracking on replay frame 3. /// [Test] public void TestTrackingRetentionLeftRightLeft() { AddStep("Tracking retention test", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking retained", assertGreatJudge); } /// /// Scenario: /// - Press a key before a slider starts /// - Press the other key on the slider head timed correctly while holding the original key /// - Release the key that was held down before the slider started. /// Expected Result: /// A passing test case will have the cursor continue tracking on replay frame 3 /// [Test] public void TestTrackingLeftBeforeSliderToRight() { AddStep("Tracking retention test", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking retained", assertGreatJudge); } /// /// Scenario: /// - Press a key before a slider starts /// - Hold the key down throughout the slider without pressing any other buttons. /// Expected Result: /// A passing test case will have the cursor track the slider, but miss the slider head. /// [Test] public void TestTrackingPreclicked() { AddStep("Tracking retention test", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked); } /// /// Scenario: /// - Press a key before a slider starts /// - Hold the key down after the slider starts /// - Move the cursor away from the slider body /// - Move the cursor back onto the body /// Expected Result: /// A passing test case will have the cursor track the slider, miss the head, miss the ticks where its outside of the body, and resume tracking when the cursor returns. /// [Test] public void TestTrackingReturnMidSlider() { AddStep("Mid-sldier tracking re-acquisition", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking re-acquired", assertMidSliderJudgements); } /// /// Scenario: /// - Press a key before a slider starts /// - Press the other key on the slider head timed correctly while holding the original key /// - Release the key used to hit the slider head /// - While holding the first key, move the cursor away from the slider body /// - Still holding the first key, move the cursor back to the slider body /// Expected Result: /// A passing test case will have the slider not track despite having the cursor return to the slider body. /// [Test] public void TestTrackingReturnMidSliderKeyDownBefore() { AddStep("Key held down before slider, mid-slider tracking re-acquisition", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking lost", assertMidSliderJudgementFail); } /// /// Scenario: /// - Wait for the slider to reach a mid-point /// - Press a key away from the slider body /// - While holding down the key, move into the slider body /// Expected Result: /// A passing test case will have the slider track the cursor after the cursor enters the slider body. /// [Test] public void TestTrackingMidSlider() { AddStep("Mid-slider new tracking acquisition", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking acquired", assertMidSliderJudgements); } /// /// Scenario: /// - Press a key before the slider starts /// - Press another key on the slider head while holding the original key /// - Move out of the slider body while releasing the two pressed keys /// - Move back into the slider body while pressing any key. /// Expected Result: /// A passing test case will have the slider track the cursor after the cursor enters the slider body. /// [Test] public void TestTrackingReleasedKeys() { AddStep("Mid-slider new tracking acquisition", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking acquired", assertMidSliderJudgements); } /// /// Scenario: /// - Press a key on the slider head /// - While holding the key, move outside of the slider body with the cursor /// - Release the key while outside of the slider body /// - Press the key again while outside of the slider body /// - Move back into the slider body while holding the pressed key /// Expected Result: /// A passing test case will have the slider track the cursor after the cursor enters the slider body. /// [Test] public void TestTrackingReleasedValidKey() { AddStep("Mid-slider new tracking acquisition", () => { var frames = new List { new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_2 }, new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, }; performTest(frames); }); waitForJudged(); AddAssert("Tracking acquired", assertMidSliderJudgements); } private void waitForJudged() => AddUntilStep(() => allJudgedFired, "Wait for all judged"); private bool assertGreatJudge() { return judgementResults.Last().Type == HitResult.Great; } private bool assertHeadMissTailTracked() { return judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; } private bool assertMidSliderJudgements() { return judgementResults[judgementResults.Count - 2].Type == HitResult.Great; } private bool assertMidSliderJudgementFail() { return judgementResults[judgementResults.Count - 2].Type == HitResult.Miss; } private void performTest(List frames) { // Empty frame to be added as a workaround for first frame behavior. // If an input exists on the first frame, the input would apply to the entire intro lead-in // Likely requires some discussion regarding how first frame inputs should be handled. frames.Insert(0, new OsuReplayFrame { Position = new Vector2(0, 0), Time = 0, Actions = new List() }); var player = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }) { AllowPause = false, AllowLeadIn = false, AllowResults = false }; LoadComponentAsync(player, p => { Child = p; p.ScoreProcessor.NewJudgement += result => judgementResults.Add(result); p.ScoreProcessor.AllJudged += () => { allJudgedFired = true; }; }); } private class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public ScoreAccessibleReplayPlayer(Score score) : base(score) { } } } }