// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; 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 TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene { [Test] public void TestPreviousHitWindowDoesNotExtendPastNextObject() { var objects = new List(); var frames = new List(); for (int i = 0; i < 7; i++) { double time = 1000 + i * 100; objects.Add(new Note { StartTime = time }); // don't hit the first note if (i > 0) { frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1)); frames.Add(new ManiaReplayFrame(time + 11)); } } performTest(objects, frames); addJudgementAssert(objects[0], HitResult.Miss); for (int i = 1; i < 7; i++) { addJudgementAssert(objects[i], HitResult.Perfect); addJudgementOffsetAssert(objects[i], 10); } } [Test] public void TestHoldNoteMissAfterNextObjectStartTime() { var objects = new List { new HoldNote { StartTime = 1000, EndTime = 1010, }, new HoldNote { StartTime = 1020, EndTime = 1030 } }; performTest(objects, new List()); addJudgementAssert(objects[0], HitResult.IgnoreHit); addJudgementAssert(objects[1], HitResult.IgnoreHit); } [Test] public void TestHoldNoteReleasedHitAfterNextObjectStartTime() { var objects = new List { new HoldNote { StartTime = 1000, EndTime = 1010, }, new HoldNote { StartTime = 1020, EndTime = 1030 } }; var frames = new List { new ManiaReplayFrame(1000, ManiaAction.Key1), new ManiaReplayFrame(1030), new ManiaReplayFrame(1040, ManiaAction.Key1), new ManiaReplayFrame(1050) }; performTest(objects, frames); addJudgementAssert(objects[0], HitResult.IgnoreHit); addJudgementAssert("first head", () => ((HoldNote)objects[0]).Head, HitResult.Perfect); addJudgementAssert("first tail", () => ((HoldNote)objects[0]).Tail, HitResult.Perfect); addJudgementAssert(objects[1], HitResult.IgnoreHit); addJudgementAssert("second head", () => ((HoldNote)objects[1]).Head, HitResult.Great); addJudgementAssert("second tail", () => ((HoldNote)objects[1]).Tail, HitResult.Perfect); } private void addJudgementAssert(ManiaHitObject hitObject, HitResult result) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject).Type == result); } private void addJudgementAssert(string name, Func hitObject, HitResult result) { AddAssert($"{name} judgement is {result}", () => judgementResults.Single(r => r.HitObject == hitObject()).Type == result); } private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset) { AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}", () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100)); } private ScoreAccessibleReplayPlayer currentPlayer; private List judgementResults; private void performTest(List hitObjects, List frames) { AddStep("load player", () => { Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition(4)) { HitObjects = hitObjects, BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }, }); Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 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); }; }; LoadScreen(currentPlayer = p); judgementResults = new List(); }); AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); } private class ScoreAccessibleReplayPlayer : ReplayPlayer { public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; protected override bool PauseOnFocusLost => false; public ScoreAccessibleReplayPlayer(Score score) : base(score, new PlayerConfiguration { AllowPause = false, ShowResults = false, }) { } } } }