// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Timing; using osu.Game.Configuration; using osu.Game.Input; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Mods { public partial class TestSceneOsuModTouchDevice : RateAdjustedBeatmapTestScene { [Resolved] private SessionStatics statics { get; set; } = null!; private ScoreAccessibleSoloPlayer currentPlayer = null!; private readonly ManualClock manualClock = new ManualClock { Rate = 1 }; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(manualClock), Audio); [BackgroundDependencyLoader] private void load() { Add(new TouchInputInterceptor()); } public override void SetUpSteps() { AddStep("reset static", () => statics.SetValue(Static.TouchInputActive, false)); base.SetUpSteps(); } [Test] public void TestUserAlreadyHasTouchDeviceActive() { loadPlayer(); AddStep("set up touchscreen user", () => { currentPlayer.Score.ScoreInfo.Mods = currentPlayer.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); statics.SetValue(Static.TouchInputActive, true); }); AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("touch circle", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] public void TestTouchActivePriorToPlayerLoad() { AddStep("set touch input active", () => statics.SetValue(Static.TouchInputActive, true)); loadPlayer(); AddUntilStep("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] public void TestTouchDuringBreak() { loadPlayer(); AddStep("seek to 2000", () => currentPlayer.GameplayClockContainer.Seek(2000)); AddUntilStep("wait until 2000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(2000)); AddUntilStep("wait until break entered", () => currentPlayer.IsBreakTime.Value); AddStep("touch playfield", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); } [Test] public void TestTouchMiss() { loadPlayer(); // ensure mouse is active (and that it's not suppressed due to touches in previous tests) AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); AddStep("seek to 200", () => currentPlayer.GameplayClockContainer.Seek(200)); AddUntilStep("wait until 200", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(200)); AddStep("touch playfield", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } [Test] public void TestIncompatibleModActive() { loadPlayer(); // this is only a veneer of enabling autopilot as having it actually active from the start is annoying to make happen // given the tests' structure. AddStep("enable autopilot", () => currentPlayer.Score.ScoreInfo.Mods = new Mod[] { new OsuModAutopilot() }); AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("touch playfield", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); } [Test] public void TestSecondObjectTouched() { loadPlayer(); // ensure mouse is active (and that it's not suppressed due to touches in previous tests) AddStep("click mouse", () => InputManager.Click(MouseButton.Left)); AddStep("seek to 0", () => currentPlayer.GameplayClockContainer.Seek(0)); AddUntilStep("wait until 0", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(0)); AddStep("click circle", () => { InputManager.MoveMouseTo(currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.Click(MouseButton.Left); }); AddAssert("touch device mod not activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.None.InstanceOf()); AddStep("seek to 5000", () => currentPlayer.GameplayClockContainer.Seek(5000)); AddUntilStep("wait until 5000", () => currentPlayer.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(5000)); AddStep("touch playfield", () => { var touch = new Touch(TouchSource.Touch1, currentPlayer.DrawableRuleset.Playfield.ScreenSpaceDrawQuad.Centre); InputManager.BeginTouch(touch); InputManager.EndTouch(touch); }); AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } private void loadPlayer() { AddStep("load player", () => { Beatmap.Value = CreateWorkingBeatmap(new OsuBeatmap { HitObjects = { new HitCircle { Position = OsuPlayfield.BASE_SIZE / 2, StartTime = 0, }, new HitCircle { Position = OsuPlayfield.BASE_SIZE / 2, StartTime = 5000, }, }, Breaks = { new BreakPeriod(2000, 3000) } }); var p = new ScoreAccessibleSoloPlayer(); LoadScreen(currentPlayer = p); }); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); } private partial class ScoreAccessibleSoloPlayer : SoloPlayer { public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; protected override bool PauseOnFocusLost => false; public ScoreAccessibleSoloPlayer() : base(new PlayerConfiguration { AllowPause = false, ShowResults = false, }) { } } } }