diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs new file mode 100644 index 0000000000..1eb0975010 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -0,0 +1,84 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests.Mods +{ + public class TestSceneCatchModRelax : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + + [Test] + public void TestModRelax() => CreateModTest(new ModTestData + { + Mod = new CatchModRelax(), + Autoplay = false, + PassCondition = passCondition, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X, + StartTime = 0 + }, + new Fruit + { + X = 0, + StartTime = 250 + }, + new Fruit + { + X = CatchPlayfield.WIDTH, + StartTime = 500 + }, + new JuiceStream + { + X = CatchPlayfield.CENTER_X, + StartTime = 750, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) + } + } + } + }); + + private bool passCondition() + { + var playfield = this.ChildrenOfType().Single(); + + switch (Player.ScoreProcessor.Combo.Value) + { + case 0: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + + case 1: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft); + break; + + case 2: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight); + break; + + case 3: + InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre); + break; + } + + return Player.ScoreProcessor.Combo.Value >= 6; + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 6dab2a0b56..db09b2bc6b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -19,25 +19,43 @@ namespace osu.Game.Rulesets.Catch.Tests { protected override bool Autoplay => true; + private int hyperDashCount; + private bool inHyperDash; + [Test] public void TestHyperDash() { - AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); - AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast. - - AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0); - - for (int i = 0; i < 3; i++) + AddStep("reset count", () => { - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); - AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing); + inHyperDash = false; + hyperDashCount = 0; + + // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur. + Player.DrawableRuleset.FrameStableComponents.OnUpdate += d => + { + var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher; + + if (catcher == null) + return; + + if (catcher.HyperDashing != inHyperDash) + { + inHyperDash = catcher.HyperDashing; + if (catcher.HyperDashing) + hyperDashCount++; + } + }; + }); + + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + + for (int i = 0; i < 9; i++) + { + int count = i + 1; + AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count); } - - AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing); } - private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher; - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 15e6e98f5a..a08c5b6fb1 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -212,6 +212,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; + + // Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins. + // This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible. + // For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size. + halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE; + int lastDirection = 0; double lastExcess = halfCatcherWidth; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index c1d24395e4..1e42c6a240 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override bool OnMouseMove(MouseMoveEvent e) { - catcher.UpdatePosition(e.MousePosition.X / DrawSize.X); + catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH); return base.OnMouseMove(e); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 952ff6b0ce..a30e1b7b47 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable. /// - private const float allowed_catch_range = 0.8f; + public const float ALLOWED_CATCH_RANGE = 0.8f; /// /// The drawable catcher for . @@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The scale of the catcher. internal static float CalculateCatchWidth(Vector2 scale) - => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range; + => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE; /// /// Calculates the width of the area used for attempting catches in gameplay. diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index 7b2913b817..d15682b1eb 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual.Filtering } [Test] - public void TestApplyDrainRateQueries() + public void TestApplyDrainRateQueriesByDrKeyword() { const string query = "dr>2 quite specific dr<:6"; var filterCriteria = new FilterCriteria(); @@ -73,6 +73,20 @@ namespace osu.Game.Tests.NonVisual.Filtering Assert.Less(filterCriteria.DrainRate.Min, 6.1f); } + [Test] + public void TestApplyDrainRateQueriesByHpKeyword() + { + const string query = "hp>2 quite specific hp<=6"; + var filterCriteria = new FilterCriteria(); + FilterQueryParser.ApplyQueries(filterCriteria, query); + Assert.AreEqual("quite specific", filterCriteria.SearchText.Trim()); + Assert.AreEqual(2, filterCriteria.SearchTerms.Length); + Assert.Greater(filterCriteria.DrainRate.Min, 2.0f); + Assert.Less(filterCriteria.DrainRate.Min, 2.1f); + Assert.Greater(filterCriteria.DrainRate.Max, 6.0f); + Assert.Less(filterCriteria.DrainRate.Min, 6.1f); + } + [Test] public void TestApplyBPMQueries() { diff --git a/osu.Game/Screens/Select/FilterQueryParser.cs b/osu.Game/Screens/Select/FilterQueryParser.cs index 89afc729fe..39fa4f777d 100644 --- a/osu.Game/Screens/Select/FilterQueryParser.cs +++ b/osu.Game/Screens/Select/FilterQueryParser.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select internal static class FilterQueryParser { private static readonly Regex query_syntax_regex = new Regex( - @"\b(?stars|ar|dr|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", + @"\b(?stars|ar|dr|hp|cs|divisor|length|objects|bpm|status|creator|artist)(?[=:><]+)(?("".*"")|(\S*))", RegexOptions.Compiled | RegexOptions.IgnoreCase); internal static void ApplyQueries(FilterCriteria criteria, string query) @@ -43,6 +43,7 @@ namespace osu.Game.Screens.Select break; case "dr" when parseFloatWithPoint(value, out var dr): + case "hp" when parseFloatWithPoint(value, out dr): updateCriteriaRange(ref criteria.DrainRate, op, dr, 0.1f / 2); break;