From 7442ae283cee27f591c76c80de62c915d190829d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Apr 2022 05:24:00 +0300 Subject: [PATCH 1/3] Tidy up test cases a touch --- .../Mods/TestSceneOsuModAlternate.cs | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index de1f61a0bd..67654c11f2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -16,31 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModAlternate : OsuModTestScene { - [Test] - public void TestInputAtIntro() => CreateModTest(new ModTestData - { - Mod = new OsuModAlternate(), - PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, - Autoplay = false, - Beatmap = new Beatmap - { - HitObjects = new List - { - new HitCircle - { - StartTime = 1000, - Position = new Vector2(100), - }, - }, - }, - ReplayFrames = new List - { - new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), - new OsuReplayFrame(501, new Vector2(200)), - new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton), - } - }); - [Test] public void TestInputAlternating() => CreateModTest(new ModTestData { @@ -116,6 +91,39 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods } }); + /// + /// Ensures alternation is reset before the first hitobject after intro. + /// + [Test] + public void TestInputSingularAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModAlternate(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + ReplayFrames = new List + { + // first press during intro. + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + // press same key at hitobject and ensure it has been hit. + new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton), + } + }); + + /// + /// Ensures alternation is reset after a break. + /// [Test] public void TestInputSingularWithBreak() => CreateModTest(new ModTestData { @@ -144,8 +152,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }, ReplayFrames = new List { + // first press to start alternate lock. new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), + // press same key at second hitobject and ensure it has been hit. new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(2501, new Vector2(100)), } From 723d70be7096b300e86568b937b2f2998f4ded56 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Apr 2022 05:26:19 +0300 Subject: [PATCH 2/3] Change `TestInputSingularWithBreak` to reproduce case of pressing before second object --- .../Mods/TestSceneOsuModAlternate.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 67654c11f2..5e46498aca 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -122,19 +122,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }); /// - /// Ensures alternation is reset after a break. + /// Ensures alternation is reset before the first hitobject after a break. /// [Test] public void TestInputSingularWithBreak() => CreateModTest(new ModTestData { Mod = new OsuModAlternate(), - PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, Autoplay = false, Beatmap = new Beatmap { Breaks = new List { - new BreakPeriod(500, 2250), + new BreakPeriod(500, 2000), }, HitObjects = new List { @@ -146,8 +146,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods new HitCircle { StartTime = 2500, - Position = new Vector2(100), - } + Position = new Vector2(500, 100), + }, + new HitCircle + { + StartTime = 3000, + Position = new Vector2(500, 100), + }, } }, ReplayFrames = new List @@ -155,9 +160,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods // first press to start alternate lock. new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), new OsuReplayFrame(501, new Vector2(100)), + // press same key after break but before hit object. + new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(2251, new Vector2(300, 100)), // press same key at second hitobject and ensure it has been hit. - new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton), - new OsuReplayFrame(2501, new Vector2(100)), + new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(500, 100)), + // press same key at third hitobject and ensure it has been missed. + new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(3001, new Vector2(500, 100)), } }); } From a4ca8bfe75ab0aa19c3aa20275492cdfe4a182fa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Apr 2022 05:26:56 +0300 Subject: [PATCH 3/3] Improve "Alternate" to reset before first hitobject after break --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 46b97dd23b..dfa28a537a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -2,21 +2,24 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; +using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset, IApplicableToPlayer + public class OsuModAlternate : Mod, IApplicableToDrawableRuleset { public override string Name => @"Alternate"; public override string Acronym => @"AL"; @@ -26,9 +29,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private double firstObjectValidJudgementTime; - private IBindable isBreakTime; private const double flash_duration = 1000; + + /// + /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). + /// + /// + /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. + /// + private PeriodTracker nonGameplayPeriods; + private OsuAction? lastActionPressed; private DrawableRuleset ruleset; @@ -39,29 +49,30 @@ namespace osu.Game.Rulesets.Osu.Mods ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - var firstHitObject = ruleset.Objects.FirstOrDefault(); - firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0); + var periods = new List(); + + if (drawableRuleset.Objects.Any()) + { + periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1)); + + foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) + periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); + + static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); + } + + nonGameplayPeriods = new PeriodTracker(periods); gameplayClock = drawableRuleset.FrameStableClock; } - public void ApplyToPlayer(Player player) - { - isBreakTime = player.IsBreakTime.GetBoundCopy(); - isBreakTime.ValueChanged += e => - { - if (e.NewValue) - lastActionPressed = null; - }; - } - private bool checkCorrectAction(OsuAction action) { - if (isBreakTime.Value) - return true; - - if (gameplayClock.CurrentTime < firstObjectValidJudgementTime) + if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) + { + lastActionPressed = null; return true; + } switch (action) {