From 1ca0223c7136b83e6f81a9ab9202e6917a1a506e Mon Sep 17 00:00:00 2001 From: mrowswares <83023433+mrowswares@users.noreply.github.com> Date: Sun, 29 Aug 2021 17:19:26 +0100 Subject: [PATCH 01/72] remove straintime & speed skill caps, implement basic doubletap cheese detection --- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- .../Preprocessing/OsuDifficultyHitObject.cs | 2 +- .../Difficulty/Skills/Speed.cs | 20 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index e47f82fb39..cfd74a4174 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new Aim(mods), - new Speed(mods) + new Speed(mods, beatmap, clockRate) }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index fa6c5c4d9c..bc97172dbf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(); // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure - StrainTime = Math.Max(50, DeltaTime); + StrainTime = DeltaTime; } private void setDistances() diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index f0eb199e5f..7bc16485eb 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; @@ -26,12 +27,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected override double DifficultyMultiplier => 1.04; private const double min_speed_bonus = 75; // ~200BPM - private const double max_speed_bonus = 45; // ~330BPM private const double speed_balancing_factor = 40; + private double greatWindow; - public Speed(Mod[] mods) + public Speed(Mod[] mods, IBeatmap beatmap, double clockRate) : base(mods) { + greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; + Console.WriteLine(greatWindow); } protected override double StrainValueOf(DifficultyHitObject current) @@ -42,12 +45,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuCurrent = (OsuDifficultyHitObject)current; double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); - double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime); + double deltaTime = current.DeltaTime; double speedBonus = 1.0; if (deltaTime < min_speed_bonus) speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); + // Doubletap detection + if (Previous.Count > 0) + { + var osuPrevious = (OsuDifficultyHitObject)Previous[0]; + if ( (osuPrevious.DeltaTime / osuCurrent.DeltaTime) >= 3 && osuCurrent.DeltaTime <= (2 * greatWindow)) + { + //Console.WriteLine( osuCurrent.StartTime / 1000.0); + return 0; + } + } + double angleBonus = 1.0; if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) From a190038c33fec8b62784d4c64ae72aef28e15477 Mon Sep 17 00:00:00 2001 From: mrowswares <83023433+mrowswares@users.noreply.github.com> Date: Sun, 29 Aug 2021 20:16:13 +0100 Subject: [PATCH 02/72] remove writelines --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 7bc16485eb..7cc5447888 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills : base(mods) { greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; - Console.WriteLine(greatWindow); } protected override double StrainValueOf(DifficultyHitObject current) @@ -57,7 +56,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuPrevious = (OsuDifficultyHitObject)Previous[0]; if ( (osuPrevious.DeltaTime / osuCurrent.DeltaTime) >= 3 && osuCurrent.DeltaTime <= (2 * greatWindow)) { - //Console.WriteLine( osuCurrent.StartTime / 1000.0); return 0; } } From 711baa12bafc7bfa3186b545b421ee760dbcc653 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 2 Sep 2021 16:31:31 +0100 Subject: [PATCH 03/72] emu's doubletap cheese nerf --- .../Difficulty/Skills/Speed.cs | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 7cc5447888..8beef524de 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public Speed(Mod[] mods, IBeatmap beatmap, double clockRate) : base(mods) { - greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; + //greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; } protected override double StrainValueOf(DifficultyHitObject current) @@ -46,20 +46,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); double deltaTime = current.DeltaTime; + // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) + if (Previous.Count > 0) + { + deltaTime = Math.Max(Previous[0].DeltaTime, deltaTime); + } + double speedBonus = 1.0; if (deltaTime < min_speed_bonus) speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); - // Doubletap detection - if (Previous.Count > 0) - { - var osuPrevious = (OsuDifficultyHitObject)Previous[0]; - if ( (osuPrevious.DeltaTime / osuCurrent.DeltaTime) >= 3 && osuCurrent.DeltaTime <= (2 * greatWindow)) - { - return 0; - } - } - double angleBonus = 1.0; if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) @@ -76,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime; + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / deltaTime; } } } From 3e98c71ece670f63e7522a6deaf5740681d548d5 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 2 Sep 2021 16:48:34 +0100 Subject: [PATCH 04/72] cap deltatime to hitwindow sorta --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 8beef524de..3ce0d050d7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public Speed(Mod[] mods, IBeatmap beatmap, double clockRate) : base(mods) { - //greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; + greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; } protected override double StrainValueOf(DifficultyHitObject current) @@ -52,6 +52,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills deltaTime = Math.Max(Previous[0].DeltaTime, deltaTime); } + // Cap deltatime to the OD 300 hitwindow. + // 0.77 is derived from making sure 260bpm OD8 streams aren't nerfed + var hitWindowNerfRaw = deltaTime / (greatWindow * 2 * 0.77); + var hitWindowNerf = Math.Clamp(hitWindowNerfRaw, 0.85, 1); + double speedBonus = 1.0; if (deltaTime < min_speed_bonus) speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); @@ -72,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / deltaTime; + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / (deltaTime / hitWindowNerf); } } } From d9cc497801fb37f45644f058805a05f51626a59b Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 2 Sep 2021 17:02:23 +0100 Subject: [PATCH 05/72] refactoring --- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index cfd74a4174..74ede287b0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { new Aim(mods), - new Speed(mods, beatmap, clockRate) + new Speed(mods, beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, clockRate), }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 3ce0d050d7..76803cbdf8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -30,10 +30,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private const double speed_balancing_factor = 40; private double greatWindow; - public Speed(Mod[] mods, IBeatmap beatmap, double clockRate) + public Speed(Mod[] mods, float od, double clockRate) : base(mods) { - greatWindow = (79 - (beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty * 6) + 0.5) / clockRate; + greatWindow = (79 - (od * 6) + 0.5) / clockRate; } protected override double StrainValueOf(DifficultyHitObject current) @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Cap deltatime to the OD 300 hitwindow. // 0.77 is derived from making sure 260bpm OD8 streams aren't nerfed - var hitWindowNerfRaw = deltaTime / (greatWindow * 2 * 0.77); - var hitWindowNerf = Math.Clamp(hitWindowNerfRaw, 0.85, 1); + var hitWindowNerf = deltaTime / (greatWindow * 2 * 0.77); + deltaTime /= Math.Clamp(hitWindowNerf, 0.92, 1); double speedBonus = 1.0; if (deltaTime < min_speed_bonus) @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / (deltaTime / hitWindowNerf); + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / deltaTime; } } } From 0d60076f3411d03937f9a1b3bcaa170f5ae30730 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 2 Sep 2021 17:14:23 +0100 Subject: [PATCH 06/72] fix doubletap cheese detect (base on hitwindow) --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 76803cbdf8..6cd32c798d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double deltaTime = current.DeltaTime; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (Previous.Count > 0) + if (Previous.Count > 0 && deltaTime <= greatWindow * 2) { deltaTime = Math.Max(Previous[0].DeltaTime, deltaTime); } From 57a2ba9aa80e5432cfe0fcaa31618f85bf97e4ad Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Thu, 2 Sep 2021 18:29:55 +0100 Subject: [PATCH 07/72] remove "straintime" --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 8 -------- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 +++--- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index bc97172dbf..609ad4c995 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -32,11 +32,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double? Angle { get; private set; } - /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms. - /// - public readonly double StrainTime; - private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; @@ -47,9 +42,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastObject = (OsuHitObject)lastObject; setDistances(); - - // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure - StrainTime = DeltaTime; } private void setDistances() diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 16a18cbcb9..63daea1ade 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills Math.Max(osuPrevious.JumpDistance - scale, 0) * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) * Math.Max(osuCurrent.JumpDistance - scale, 0)); - result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); + result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.DeltaTime); } } @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); return Math.Max( - result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime + result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.DeltaTime, timing_threshold), + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.DeltaTime ); } From 0beef9c1e7ed296401b347a42811029b593379f0 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 3 Sep 2021 02:20:22 +0100 Subject: [PATCH 08/72] made double cheese detection stricter --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 6cd32c798d..c4230fc9a4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double deltaTime = current.DeltaTime; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (Previous.Count > 0 && deltaTime <= greatWindow * 2) + if (Previous.Count > 0 && deltaTime <= greatWindow) { deltaTime = Math.Max(Previous[0].DeltaTime, deltaTime); } From bf87a4b2d3802cfdd17b04f412fc9c9f9ab98799 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 3 Sep 2021 02:39:21 +0100 Subject: [PATCH 09/72] interpolate the doubletap cheese nerf instead --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index c4230fc9a4..836e926cf1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; +using osu.Framework.Utils; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -47,9 +47,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double deltaTime = current.DeltaTime; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (Previous.Count > 0 && deltaTime <= greatWindow) + double deltaTimeThreshold = greatWindow * 2; + + if (Previous.Count > 0 && deltaTime < deltaTimeThreshold && Previous[0].DeltaTime > deltaTime) { - deltaTime = Math.Max(Previous[0].DeltaTime, deltaTime); + double closenessToZero = Math.Min(1, deltaTime / deltaTimeThreshold); + deltaTime = Interpolation.Lerp(Previous[0].DeltaTime, deltaTime, closenessToZero); } // Cap deltatime to the OD 300 hitwindow. From 8654a0af05798ec1a8db0c8af9e9e737b523c724 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Fri, 3 Sep 2021 03:01:25 +0100 Subject: [PATCH 10/72] remove unnecessary min & renamed variable so its more descriptive --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 836e926cf1..fb24476493 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -51,8 +51,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (Previous.Count > 0 && deltaTime < deltaTimeThreshold && Previous[0].DeltaTime > deltaTime) { - double closenessToZero = Math.Min(1, deltaTime / deltaTimeThreshold); - deltaTime = Interpolation.Lerp(Previous[0].DeltaTime, deltaTime, closenessToZero); + double speedWindowRatio = deltaTime / deltaTimeThreshold; + deltaTime = Interpolation.Lerp(Previous[0].DeltaTime, deltaTime, speedWindowRatio); } // Cap deltatime to the OD 300 hitwindow. From 3fce3f620f5903f2aee9ee7566273a887d48be62 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sat, 4 Sep 2021 16:56:15 +0100 Subject: [PATCH 11/72] use OsuHitWindows, amend comment --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index fb24476493..e69f15188d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Osu.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -33,7 +35,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills public Speed(Mod[] mods, float od, double clockRate) : base(mods) { - greatWindow = (79 - (od * 6) + 0.5) / clockRate; + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(od); + greatWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; } protected override double StrainValueOf(DifficultyHitObject current) @@ -56,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } // Cap deltatime to the OD 300 hitwindow. - // 0.77 is derived from making sure 260bpm OD8 streams aren't nerfed + // 0.77 is derived from making sure 260bpm OD8 streams aren't nerfed harshly var hitWindowNerf = deltaTime / (greatWindow * 2 * 0.77); deltaTime /= Math.Clamp(hitWindowNerf, 0.92, 1); From e9f7258f2b3110d4845699602e6dbdab49713895 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 13 Sep 2021 14:50:40 +0100 Subject: [PATCH 12/72] adjust hitwindow nerf to be harsher --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index e69f15188d..2640d4ac41 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } // Cap deltatime to the OD 300 hitwindow. - // 0.77 is derived from making sure 260bpm OD8 streams aren't nerfed harshly - var hitWindowNerf = deltaTime / (greatWindow * 2 * 0.77); + // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly + var hitWindowNerf = deltaTime / (greatWindow * 2 * 0.93); deltaTime /= Math.Clamp(hitWindowNerf, 0.92, 1); double speedBonus = 1.0; From 8796e45f63447ec2de4c89f4d03af0b384ec264a Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Tue, 14 Sep 2021 15:22:03 +0100 Subject: [PATCH 13/72] prevent 2B objects from dividing by zero --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 63daea1ade..7467feb009 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return Math.Max( result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.DeltaTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.DeltaTime + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / Math.Max(osuCurrent.DeltaTime, 1) ); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 2640d4ac41..39acbf4027 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / deltaTime; + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / Math.Max(deltaTime, 1); } } } From 6d254fba0ac2001d559f5debb8ca1c953d60c23e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:27:18 +0100 Subject: [PATCH 14/72] digestify speed return --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 39acbf4027..19f9e3f849 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -84,7 +84,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / Math.Max(deltaTime, 1); + return (1 + (speedBonus - 1) * 0.75) + * angleBonus + * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) + / Math.Max(deltaTime, 1); } } } From 49658b6f82d097777705f6eaa670bd0fc4339be6 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:29:30 +0100 Subject: [PATCH 15/72] set greatWindow to readonly --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 19f9e3f849..93d3227649 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private const double min_speed_bonus = 75; // ~200BPM private const double speed_balancing_factor = 40; - private double greatWindow; + + private readonly double greatWindow; public Speed(Mod[] mods, float od, double clockRate) : base(mods) From a0bd73c3562211320410bbd63d157af0143ba377 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 10:52:50 +0100 Subject: [PATCH 16/72] refactor hit window calc --- .../Difficulty/OsuDifficultyCalculator.cs | 24 ++++++++++++------- .../Difficulty/Skills/Speed.cs | 6 ++--- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 74ede287b0..9107d8234f 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; @@ -21,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty public class OsuDifficultyCalculator : DifficultyCalculator { private const double difficulty_multiplier = 0.0675; + private double hitWindowGreat; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) @@ -36,11 +38,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; - HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); - - // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future - double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; @@ -79,11 +76,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty } } - protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] + protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { - new Aim(mods), - new Speed(mods, beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, clockRate), - }; + HitWindows hitWindows = new OsuHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + + // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future + hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; + + return new Skill[] + { + new Aim(mods), + new Speed(mods, hitWindowGreat), + }; + } protected override Mod[] DifficultyAdjustmentMods => new Mod[] { diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 93d3227649..8f0034ef79 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -33,12 +33,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills private readonly double greatWindow; - public Speed(Mod[] mods, float od, double clockRate) + public Speed(Mod[] mods, double hitWindowGreat) : base(mods) { - HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(od); - greatWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; + greatWindow = hitWindowGreat; } protected override double StrainValueOf(DifficultyHitObject current) From 3a16ec277a8fdac95f890599d4d7db56491fae70 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 11:12:36 +0100 Subject: [PATCH 17/72] refactor speed window ratios --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 8f0034ef79..89e5c39449 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -49,19 +49,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); double deltaTime = current.DeltaTime; - // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - double deltaTimeThreshold = greatWindow * 2; + double greatWindowFull = greatWindow * 2; + double speedWindowRatio = deltaTime / greatWindowFull; - if (Previous.Count > 0 && deltaTime < deltaTimeThreshold && Previous[0].DeltaTime > deltaTime) + // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) + if (Previous.Count > 0 && deltaTime < greatWindowFull && Previous[0].DeltaTime > deltaTime) { - double speedWindowRatio = deltaTime / deltaTimeThreshold; + deltaTime = Interpolation.Lerp(Previous[0].DeltaTime, deltaTime, speedWindowRatio); } // Cap deltatime to the OD 300 hitwindow. - // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly - var hitWindowNerf = deltaTime / (greatWindow * 2 * 0.93); - deltaTime /= Math.Clamp(hitWindowNerf, 0.92, 1); + // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, + deltaTime /= Math.Clamp(speedWindowRatio * (1/0.93), 0.92, 1); double speedBonus = 1.0; if (deltaTime < min_speed_bonus) From 4017598af0420a71ab769b4dde2b24ecd5adf512 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 11:15:05 +0100 Subject: [PATCH 18/72] simplify algebra down --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 89e5c39449..aefebbe669 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, - deltaTime /= Math.Clamp(speedWindowRatio * (1/0.93), 0.92, 1); + deltaTime /= Math.Clamp(speedWindowRatio / 0.93, 0.92, 1); double speedBonus = 1.0; if (deltaTime < min_speed_bonus) From 7f6722e43fcd4aa19a5e3d65a189959714d64d5c Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 11:24:48 +0100 Subject: [PATCH 19/72] throw math.max(N, 1) into straintime --- .../Preprocessing/OsuDifficultyHitObject.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 6 +++--- .../Difficulty/Skills/Speed.cs | 17 ++++++++--------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 609ad4c995..65efe65129 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 1ms to account for simultaneous s. + /// + public double StrainTime { get; private set; } + /// /// Normalized distance from the end position of the previous to the start position of this . /// @@ -42,6 +47,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastObject = (OsuHitObject)lastObject; setDistances(); + + // Capped to 1ms to prevent difficulty calculation breaking from simulatenous objects. + StrainTime = Math.Max(DeltaTime, 1); } private void setDistances() diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 7467feb009..16a18cbcb9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills Math.Max(osuPrevious.JumpDistance - scale, 0) * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) * Math.Max(osuCurrent.JumpDistance - scale, 0)); - result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.DeltaTime); + result = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); } } @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); return Math.Max( - result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.DeltaTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / Math.Max(osuCurrent.DeltaTime, 1) + result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime ); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index aefebbe669..b2eacc5c6a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -47,25 +47,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuCurrent = (OsuDifficultyHitObject)current; double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); - double deltaTime = current.DeltaTime; + double strainTime = osuCurrent.StrainTime; double greatWindowFull = greatWindow * 2; - double speedWindowRatio = deltaTime / greatWindowFull; + double speedWindowRatio = strainTime / greatWindowFull; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (Previous.Count > 0 && deltaTime < greatWindowFull && Previous[0].DeltaTime > deltaTime) + if (Previous.Count > 0 && strainTime < greatWindowFull && (Previous[0] as OsuDifficultyHitObject).StrainTime > strainTime) { - - deltaTime = Interpolation.Lerp(Previous[0].DeltaTime, deltaTime, speedWindowRatio); + strainTime = Interpolation.Lerp(Previous[0].DeltaTime, strainTime, speedWindowRatio); } // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, - deltaTime /= Math.Clamp(speedWindowRatio / 0.93, 0.92, 1); + strainTime /= Math.Clamp(speedWindowRatio / 0.93, 0.92, 1); double speedBonus = 1.0; - if (deltaTime < min_speed_bonus) - speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); + if (strainTime < min_speed_bonus) + speedBonus = 1 + Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); double angleBonus = 1.0; @@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) - / Math.Max(deltaTime, 1); + / strainTime; } } } From 2fe0681310498718bea523b550109743c511a0d9 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 12:03:47 +0100 Subject: [PATCH 20/72] elaborate comment --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b2eacc5c6a..5b509b9edc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } // Cap deltatime to the OD 300 hitwindow. - // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, + // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. strainTime /= Math.Clamp(speedWindowRatio / 0.93, 0.92, 1); double speedBonus = 1.0; From cf63a45f32dd10d7be11d959f8667a8b2ba544e4 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 12:36:15 +0100 Subject: [PATCH 21/72] swap speedwindowratio in cap so values are correct --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 5b509b9edc..78c3db96a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. - strainTime /= Math.Clamp(speedWindowRatio / 0.93, 0.92, 1); + strainTime /= Math.Clamp((strainTime / greatWindowFull) / 0.93, 0.92, 1); double speedBonus = 1.0; if (strainTime < min_speed_bonus) From 463b92fcca42f0aa0e14307f21a7d60e46c7e080 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 12:41:29 +0100 Subject: [PATCH 22/72] remove unused strings --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 1 - osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 9107d8234f..743494abac 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.CodeAnalysis.CSharp.Syntax; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty.Preprocessing; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 78c3db96a1..a117570e61 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,8 +7,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Osu.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { From 2c3e7bfd2dceddb7d8400c40a3ec5bcad2c436c7 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 15:27:36 +0100 Subject: [PATCH 23/72] moved 2b straintime cap up to 25ms --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 65efe65129..8e8f9bc06e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 1ms to account for simultaneous s. + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms to account for simultaneous s. /// public double StrainTime { get; private set; } @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing setDistances(); - // Capped to 1ms to prevent difficulty calculation breaking from simulatenous objects. - StrainTime = Math.Max(DeltaTime, 1); + // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. + StrainTime = Math.Max(DeltaTime, 25); } private void setDistances() From 2637c063a951b675edf4abd3d815e9c9ae33ebf9 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 15 Sep 2021 15:40:26 +0100 Subject: [PATCH 24/72] forgot a deltatime --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index a117570e61..3cb0e3506b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) if (Previous.Count > 0 && strainTime < greatWindowFull && (Previous[0] as OsuDifficultyHitObject).StrainTime > strainTime) { - strainTime = Interpolation.Lerp(Previous[0].DeltaTime, strainTime, speedWindowRatio); + strainTime = Interpolation.Lerp((Previous[0] as OsuDifficultyHitObject).StrainTime, strainTime, speedWindowRatio); } // Cap deltatime to the OD 300 hitwindow. From 7976442aec419492c30fa0b121c3ce9e00c33a9a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Sep 2021 14:20:42 +0900 Subject: [PATCH 25/72] Fix CI issues --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 3cb0e3506b..9364b11048 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -43,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return 0; var osuCurrent = (OsuDifficultyHitObject)current; + var osuPrevious = Previous.Count > 0 ? (OsuDifficultyHitObject)Previous[0] : null; double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); double strainTime = osuCurrent.StrainTime; @@ -51,10 +52,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double speedWindowRatio = strainTime / greatWindowFull; // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between) - if (Previous.Count > 0 && strainTime < greatWindowFull && (Previous[0] as OsuDifficultyHitObject).StrainTime > strainTime) - { - strainTime = Interpolation.Lerp((Previous[0] as OsuDifficultyHitObject).StrainTime, strainTime, speedWindowRatio); - } + if (osuPrevious != null && strainTime < greatWindowFull && osuPrevious.StrainTime > strainTime) + strainTime = Interpolation.Lerp(osuPrevious.StrainTime, strainTime, speedWindowRatio); // Cap deltatime to the OD 300 hitwindow. // 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap. @@ -81,9 +80,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } return (1 + (speedBonus - 1) * 0.75) - * angleBonus - * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) - / strainTime; + * angleBonus + * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) + / strainTime; } } } From 2c071a4d223481f3a14e770eaedcdd14f5415a3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Sep 2021 14:40:29 +0900 Subject: [PATCH 26/72] Add test coverage of pausing with a large audio offset --- .../Visual/Gameplay/TestScenePause.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index bddc7ab731..324953eb9f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -3,10 +3,12 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; @@ -39,6 +41,45 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(true); } + [Test] + public void TestPauseWithLargeOffset() + { + double lastTime; + bool alwaysGoingForward = true; + + AddStep("force large offset", () => + { + var offset = (BindableDouble)LocalConfig.GetBindable(OsuSetting.AudioOffset); + + // use a large negative offset to avoid triggering a fail from forwards seeking. + offset.MinValue = -5000; + offset.Value = -5000; + }); + + AddStep("add time forward check hook", () => + { + lastTime = double.MinValue; + alwaysGoingForward = true; + + Player.OnUpdate += _ => + { + double currentTime = Player.GameplayClockContainer.CurrentTime; + alwaysGoingForward &= currentTime >= lastTime; + lastTime = currentTime; + }; + }); + + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resumeAndConfirm(); + + AddAssert("time didn't go backwards", () => alwaysGoingForward); + + AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0)); + } + [Test] public void TestPauseResume() { From 5f27f1c099c0b1e369cf01662bcf758de91ae42d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Sep 2021 15:39:03 +0900 Subject: [PATCH 27/72] Avoid accounting for the pause pitch adjust effect when "fixing" hardware offset adjustments Bit of an unfortunate one. Because we are applying the pitch adjustment to the lowest level (`Track`), it's hard to filter out in parent clock calculations. Tried a few solutions but this feels the best. Note that we can't just undo the `pauseFreqAdjust` adjustment as it will div-by-zero. Closes https://github.com/ppy/osu/issues/14773. --- .../Play/MasterGameplayClockContainer.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 3fbb55872b..aa46522dec 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -158,10 +158,10 @@ namespace osu.Game.Screens.Play { // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - platformOffsetClock = new HardwareCorrectionOffsetClock(source) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; + platformOffsetClock = new HardwareCorrectionOffsetClock(source, pauseFreqAdjust) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 15 : 0 }; // the final usable gameplay clock with user-set offsets applied. - userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); + userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock, pauseFreqAdjust); return masterGameplayClock = new MasterGameplayClock(userOffsetClock); } @@ -216,11 +216,25 @@ namespace osu.Game.Screens.Play { // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1); + public override double CurrentTime => base.CurrentTime + offsetAdjust; - public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) - : base(source, processSource) + private readonly BindableDouble pauseRateAdjust; + + private double offsetAdjust; + + public HardwareCorrectionOffsetClock(IClock source, BindableDouble pauseRateAdjust) + : base(source) { + this.pauseRateAdjust = pauseRateAdjust; + } + + public override void ProcessFrame() + { + base.ProcessFrame(); + + // changing this during the pause transform effect will cause a potentially large offset to be suddenly applied as we approach zero rate. + if (pauseRateAdjust.Value == 1) + offsetAdjust = Offset * (Rate - 1); } } From 45caeb84d38eaa4404e6c3dc2bd1ce465ebe3131 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Sep 2021 16:28:47 +0900 Subject: [PATCH 28/72] Fix incorrect type specification when restoring bindable value --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 324953eb9f..04676f656f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("time didn't go backwards", () => alwaysGoingForward); - AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0)); + AddStep("reset offset", () => LocalConfig.SetValue(OsuSetting.AudioOffset, 0.0)); } [Test] From 56e80a07060e959948e109433f93aab43e501fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Sep 2021 16:41:30 +0200 Subject: [PATCH 29/72] Add rectangular position snap grid --- .../TestSceneRectangularPositionSnapGrid.cs | 103 ++++++++++++++++++ .../Components/RectangularPositionSnapGrid.cs | 101 +++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs new file mode 100644 index 0000000000..ec267bf752 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneRectangularPositionSnapGrid : OsuManualInputManagerTestScene + { + private Container content; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Gray + }, + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }); + } + + private static readonly object[][] test_cases = + { + new object[] { new Vector2(0, 0), new Vector2(10, 10) }, + new object[] { new Vector2(240, 180), new Vector2(10, 15) }, + new object[] { new Vector2(160, 120), new Vector2(30, 20) }, + new object[] { new Vector2(480, 360), new Vector2(100, 100) }, + }; + + [TestCaseSource(nameof(test_cases))] + public void TestRectangularGrid(Vector2 position, Vector2 spacing) + { + RectangularPositionSnapGrid grid = null; + + AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position, spacing) + { + RelativeSizeAxes = Axes.Both + }); + + AddStep("add snapping cursor", () => Add(new SnappingCursorContainer + { + RelativeSizeAxes = Axes.Both, + GetSnapPosition = pos => grid.GetSnappedPosition(grid.ToLocalSpace(pos)) + })); + } + + private class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs new file mode 100644 index 0000000000..f243c027e3 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public class RectangularPositionSnapGrid : CompositeDrawable + { + /// + /// The position of the origin of this in local coordinates. + /// + public Vector2 StartPosition { get; } + + /// + /// The spacing between grid lines of this . + /// + public Vector2 Spacing { get; } + + private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); + + public RectangularPositionSnapGrid(Vector2 startPosition, Vector2 spacing) + { + StartPosition = startPosition; + Spacing = spacing; + + AddLayout(gridCache); + } + + protected override void Update() + { + base.Update(); + + if (!gridCache.IsValid) + { + ClearInternal(); + createContent(); + gridCache.Validate(); + } + } + + private void createContent() + { + var drawSize = DrawSize; + + generateGridLines(Direction.Horizontal, StartPosition.Y, 0, -Spacing.Y); + generateGridLines(Direction.Horizontal, StartPosition.Y, drawSize.Y, Spacing.Y); + + generateGridLines(Direction.Vertical, StartPosition.X, 0, -Spacing.X); + generateGridLines(Direction.Vertical, StartPosition.X, drawSize.X, Spacing.X); + } + + private void generateGridLines(Direction direction, float startPosition, float endPosition, float step) + { + int index = 0; + float currentPosition = startPosition; + + while ((endPosition - currentPosition) * Math.Sign(step) > 0) + { + var gridLine = new Box + { + Colour = Colour4.White, + Alpha = index == 0 ? 0.3f : 0.1f, + EdgeSmoothness = new Vector2(0.2f) + }; + + if (direction == Direction.Horizontal) + { + gridLine.RelativeSizeAxes = Axes.X; + gridLine.Height = 1; + gridLine.Y = currentPosition; + } + else + { + gridLine.RelativeSizeAxes = Axes.Y; + gridLine.Width = 1; + gridLine.X = currentPosition; + } + + AddInternal(gridLine); + + index += 1; + currentPosition = startPosition + index * step; + } + } + + public Vector2 GetSnappedPosition(Vector2 original) + { + Vector2 relativeToStart = original - StartPosition; + Vector2 offset = Vector2.Divide(relativeToStart, Spacing); + Vector2 roundedOffset = new Vector2(MathF.Round(offset.X), MathF.Round(offset.Y)); + + return StartPosition + Vector2.Multiply(roundedOffset, Spacing); + } + } +} From e1738025d439733586b88ea2b08d12a976f6fbd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Sep 2021 17:48:29 +0200 Subject: [PATCH 30/72] Add basic integration of rectangular grid to osu! composer --- .../Edit/OsuHitObjectComposer.cs | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 806b7e6051..491ad655fa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -42,10 +43,12 @@ namespace osu.Game.Rulesets.Osu.Edit }; private readonly Bindable distanceSnapToggle = new Bindable(); + private readonly Bindable gridSnapToggle = new Bindable(); protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] { - new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) + new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }), + new TernaryButton(gridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th }) }); private BindableList selectedHitObjects; @@ -63,6 +66,10 @@ namespace osu.Game.Rulesets.Osu.Edit PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners } }, distanceSnapGridContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + rectangularPositionSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both } @@ -73,7 +80,21 @@ namespace osu.Game.Rulesets.Osu.Edit placementObject = EditorBeatmap.PlacementObject.GetBoundCopy(); placementObject.ValueChanged += _ => updateDistanceSnapGrid(); - distanceSnapToggle.ValueChanged += _ => updateDistanceSnapGrid(); + distanceSnapToggle.ValueChanged += _ => + { + updateDistanceSnapGrid(); + + if (distanceSnapToggle.Value == TernaryState.True) + gridSnapToggle.Value = TernaryState.False; + }; + + gridSnapToggle.ValueChanged += _ => + { + updateRectangularPositionSnapGrid(); + + if (gridSnapToggle.Value == TernaryState.True) + distanceSnapToggle.Value = TernaryState.False; + }; // we may be entering the screen with a selection already active updateDistanceSnapGrid(); @@ -122,13 +143,19 @@ namespace osu.Game.Rulesets.Osu.Edit if (positionSnap.ScreenSpacePosition != screenSpacePosition) return positionSnap; - // will be null if distance snap is disabled or not feasible for the current time value. - if (distanceSnapGrid == null) - return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); + if (distanceSnapGrid != null) + { + (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + } - (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + if (rectangularPositionSnapGrid != null) + { + Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + } - return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); + return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); } private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult snapResult) @@ -272,5 +299,21 @@ namespace osu.Game.Rulesets.Osu.Edit return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject); } + + private Container rectangularPositionSnapGridContainer; + private RectangularPositionSnapGrid rectangularPositionSnapGrid; + + private void updateRectangularPositionSnapGrid() + { + rectangularPositionSnapGridContainer.Clear(); + + if (gridSnapToggle.Value == TernaryState.True) + { + rectangularPositionSnapGridContainer.Add(rectangularPositionSnapGrid = new RectangularPositionSnapGrid(OsuPlayfield.BASE_SIZE / 2, new Vector2(16)) + { + RelativeSizeAxes = Axes.Both + }); + } + } } } From c403e628ddae77c9c3173604e8c9832dd09b8a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Sep 2021 17:58:32 +0200 Subject: [PATCH 31/72] Add test coverage for distance/rectangular grid exclusivity --- .../Editor/TestSceneOsuEditorGrids.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs new file mode 100644 index 0000000000..007b27b2e7 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -0,0 +1,34 @@ +// 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.Testing; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Visual; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneOsuEditorGrids : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestGridExclusivity() + { + AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + + AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid hidden", () => !this.ChildrenOfType().Any()); + } + } +} From 4e094b2127d614fc3d1943351bad9de1a239b83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 19 Sep 2021 18:45:22 +0200 Subject: [PATCH 32/72] Implement grid size toggling matching stable --- .../Editor/TestSceneOsuEditorGrids.cs | 28 +++++++++-- .../Edit/OsuHitObjectComposer.cs | 3 +- .../Edit/OsuRectangularPositionSnapGrid.cs | 50 +++++++++++++++++++ .../TestSceneRectangularPositionSnapGrid.cs | 5 +- .../Components/RectangularPositionSnapGrid.cs | 18 +++++-- 5 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 007b27b2e7..81e44a57db 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -5,8 +5,8 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; +using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests.Editor @@ -24,11 +24,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); - AddUntilStep("rectangular grid hidden", () => !this.ChildrenOfType().Any()); + AddUntilStep("rectangular grid hidden", () => !this.ChildrenOfType().Any()); } + + [Test] + public void TestGridSizeToggling() + { + AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); + AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + gridSizeIs(4); + + nextGridSizeIs(8); + nextGridSizeIs(16); + nextGridSizeIs(32); + nextGridSizeIs(4); + } + + private void nextGridSizeIs(int size) + { + AddStep("toggle to next grid size", () => InputManager.Key(Key.G)); + gridSizeIs(size); + } + + private void gridSizeIs(int size) + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size)); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 491ad655fa..a2ee646341 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -309,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (gridSnapToggle.Value == TernaryState.True) { - rectangularPositionSnapGridContainer.Add(rectangularPositionSnapGrid = new RectangularPositionSnapGrid(OsuPlayfield.BASE_SIZE / 2, new Vector2(16)) + rectangularPositionSnapGridContainer.Add(rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid(EditorBeatmap.BeatmapInfo.GridSize) { RelativeSizeAxes = Axes.Both }); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs new file mode 100644 index 0000000000..2faaab75e4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid + { + private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; + + private int currentGridSizeIndex; + + public OsuRectangularPositionSnapGrid(int gridSize) + : base(OsuPlayfield.BASE_SIZE / 2) + { + var gridSizeIndex = Array.IndexOf(grid_sizes, gridSize); + if (gridSizeIndex > 0) + currentGridSizeIndex = gridSizeIndex; + updateSpacing(); + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Key == Key.G) + { + nextGridSize(); + return true; + } + + return false; + } + + private void nextGridSize() + { + currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; + updateSpacing(); + } + + private void updateSpacing() + { + Spacing = new Vector2(grid_sizes[currentGridSizeIndex]); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs index ec267bf752..85a98eca47 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneRectangularPositionSnapGrid.cs @@ -49,9 +49,10 @@ namespace osu.Game.Tests.Visual.Editing { RectangularPositionSnapGrid grid = null; - AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position, spacing) + AddStep("create grid", () => Child = grid = new RectangularPositionSnapGrid(position) { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Spacing = spacing }); AddStep("add snapping cursor", () => Add(new SnappingCursorContainer diff --git a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs index f243c027e3..95b4b2fe53 100644 --- a/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/RectangularPositionSnapGrid.cs @@ -17,17 +17,29 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public Vector2 StartPosition { get; } + private Vector2 spacing = Vector2.One; + /// /// The spacing between grid lines of this . /// - public Vector2 Spacing { get; } + public Vector2 Spacing + { + get => spacing; + set + { + if (spacing.X <= 0 || spacing.Y <= 0) + throw new ArgumentException("Grid spacing must be positive."); + + spacing = value; + gridCache.Invalidate(); + } + } private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - public RectangularPositionSnapGrid(Vector2 startPosition, Vector2 spacing) + public RectangularPositionSnapGrid(Vector2 startPosition) { StartPosition = startPosition; - Spacing = spacing; AddLayout(gridCache); } From 0d58530dbea69e72ef12a8bc1c0fac6fe60f5397 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 01:48:14 +0900 Subject: [PATCH 33/72] Reduce overhead of `ColumnHasObject` calls by storing column usage separately --- .../Beatmaps/Patterns/Pattern.cs | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index f095a0ffce..817d2b60b6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns @@ -14,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns { private readonly List hitObjects = new List(); + private readonly HashSet containedColumns = new HashSet(); + /// /// All the hit objects contained in this pattern. /// @@ -24,34 +25,42 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// /// The column index. /// Whether the column with index contains a hit object. - public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column); + public bool ColumnHasObject(int column) => containedColumns.Contains(column); /// /// Amount of columns taken up by hit objects in this pattern. /// - public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count(); + public int ColumnWithObjects => containedColumns.Count; /// /// Adds a hit object to this pattern. /// /// The hit object to add. - public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject); + public void Add(ManiaHitObject hitObject) + { + hitObjects.Add(hitObject); + containedColumns.Add(hitObject.Column); + } /// /// Copies hit object from another pattern to this one. /// /// The other pattern. - public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects); + public void Add(Pattern other) + { + hitObjects.AddRange(other.HitObjects); + + foreach (var h in other.hitObjects) + containedColumns.Add(h.Column); + } /// /// Clears this pattern, removing all hit objects. /// - public void Clear() => hitObjects.Clear(); - - /// - /// Removes a hit object from this pattern. - /// - /// The hit object to remove. - public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject); + public void Clear() + { + hitObjects.Clear(); + containedColumns.Clear(); + } } } From 16e60eed56d8586ef69df2eeeb1e5b2fd6b2836f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 01:48:33 +0900 Subject: [PATCH 34/72] Reduce `NestedHitObject` enumerator overhead This was especially bad due to it allocating on any and every start time change, even the first (see usage in `HitObject.ctor`). --- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 9feaa55051..82d76252d2 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream. foreach (var hitObject in beatmap.HitObjects - .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj }) + .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects.AsEnumerable() : new[] { obj }) .Cast() .OrderBy(x => x.StartTime)) { diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c4b9e6e1ad..ae0cb895bc 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -3,11 +3,12 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Extensions.ListExtensions; +using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Objects private readonly List nestedHitObjects = new List(); [JsonIgnore] - public IReadOnlyList NestedHitObjects => nestedHitObjects; + public SlimReadOnlyListWrapper NestedHitObjects => nestedHitObjects.AsSlimReadOnly(); public HitObject() { @@ -91,7 +92,7 @@ namespace osu.Game.Rulesets.Objects { double offset = time.NewValue - time.OldValue; - foreach (var nested in NestedHitObjects) + foreach (var nested in nestedHitObjects) nested.StartTime += offset; }; } @@ -122,11 +123,14 @@ namespace osu.Game.Rulesets.Objects if (this is IHasComboInformation hasCombo) { - foreach (var n in NestedHitObjects.OfType()) + foreach (HitObject hitObject in nestedHitObjects) { - n.ComboIndexBindable.BindTo(hasCombo.ComboIndexBindable); - n.ComboIndexWithOffsetsBindable.BindTo(hasCombo.ComboIndexWithOffsetsBindable); - n.IndexInCurrentComboBindable.BindTo(hasCombo.IndexInCurrentComboBindable); + if (hitObject is IHasComboInformation n) + { + n.ComboIndexBindable.BindTo(hasCombo.ComboIndexBindable); + n.ComboIndexWithOffsetsBindable.BindTo(hasCombo.ComboIndexWithOffsetsBindable); + n.IndexInCurrentComboBindable.BindTo(hasCombo.IndexInCurrentComboBindable); + } } } From 03291e389776777c85db8899d5b64328a548b689 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 02:14:20 +0900 Subject: [PATCH 35/72] Avoid LINQ overhead in `PatternGenerator.isValid` --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index fb58d805a9..e643b82271 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { lowerBound ??= RandomStart; upperBound ??= TotalColumns; - nextColumn ??= (_ => GetRandomColumn(lowerBound, upperBound)); + nextColumn ??= _ => GetRandomColumn(lowerBound, upperBound); // Check for the initial column if (isValid(initialColumn)) @@ -176,7 +176,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return initialColumn; - bool isValid(int column) => validation?.Invoke(column) != false && !patterns.Any(p => p.ColumnHasObject(column)); + bool isValid(int column) + { + if (validation?.Invoke(column) == false) + return false; + + foreach (var p in patterns) + { + if (p.ColumnHasObject(column)) + return false; + } + + return true; + } } /// From d96d1b3e47fb57691b5bb72c60e30ea6b847a310 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 14:47:47 +0900 Subject: [PATCH 36/72] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4859510e6c..7a739e6f2a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0460de6d72..2bed1b1564 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 51ca381b63..b547e98419 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 98f1c1cc292d5103523b8e1a87b284ae01f5bbfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 16:02:02 +0900 Subject: [PATCH 37/72] Avoid allocating list storage in `Pattern` until first usage Patterns can often be constructed only to never be used. --- .../Beatmaps/Patterns/Pattern.cs | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index 817d2b60b6..8c42e47dbc 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Mania.Objects; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns @@ -11,26 +12,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// internal class Pattern { - private readonly List hitObjects = new List(); - - private readonly HashSet containedColumns = new HashSet(); + private List hitObjects; + private HashSet containedColumns; /// /// All the hit objects contained in this pattern. /// - public IEnumerable HitObjects => hitObjects; + public IEnumerable HitObjects => hitObjects ?? Enumerable.Empty(); /// /// Check whether a column of this patterns contains a hit object. /// /// The column index. /// Whether the column with index contains a hit object. - public bool ColumnHasObject(int column) => containedColumns.Contains(column); + public bool ColumnHasObject(int column) => containedColumns?.Contains(column) ?? false; /// /// Amount of columns taken up by hit objects in this pattern. /// - public int ColumnWithObjects => containedColumns.Count; + public int ColumnWithObjects => containedColumns?.Count ?? 0; /// /// Adds a hit object to this pattern. @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// The hit object to add. public void Add(ManiaHitObject hitObject) { + prepareStorage(); + hitObjects.Add(hitObject); containedColumns.Add(hitObject.Column); } @@ -48,10 +50,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// The other pattern. public void Add(Pattern other) { - hitObjects.AddRange(other.HitObjects); + prepareStorage(); - foreach (var h in other.hitObjects) - containedColumns.Add(h.Column); + if (other.hitObjects != null) + { + hitObjects.AddRange(other.hitObjects); + + foreach (var h in other.hitObjects) + containedColumns.Add(h.Column); + } } /// @@ -59,8 +66,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// public void Clear() { - hitObjects.Clear(); - containedColumns.Clear(); + hitObjects?.Clear(); + containedColumns?.Clear(); + } + + private void prepareStorage() + { + hitObjects ??= new List(); + containedColumns ??= new HashSet(); } } } From cdef6d0cf56a633c77d9b28ca2166d19eeb52a51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Sep 2021 16:43:15 +0900 Subject: [PATCH 38/72] Add key binding support for grid mode cycle --- .../Edit/OsuRectangularPositionSnapGrid.cs | 32 +++++++++++-------- .../Input/Bindings/GlobalActionContainer.cs | 6 +++- .../GlobalActionKeyBindingStrings.cs | 5 +++ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs index 2faaab75e4..da18d3175e 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -2,15 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid + public class OsuRectangularPositionSnapGrid : RectangularPositionSnapGrid, IKeyBindingHandler { private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; @@ -25,17 +26,6 @@ namespace osu.Game.Rulesets.Osu.Edit updateSpacing(); } - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Key == Key.G) - { - nextGridSize(); - return true; - } - - return false; - } - private void nextGridSize() { currentGridSizeIndex = (currentGridSizeIndex + 1) % grid_sizes.Length; @@ -46,5 +36,21 @@ namespace osu.Game.Rulesets.Osu.Edit { Spacing = new Vector2(grid_sizes[currentGridSizeIndex]); } + + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case GlobalAction.EditorCycleGridDisplayMode: + nextGridSize(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index f62131e2d7..9fd7caadd0 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -75,6 +75,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode), new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), + new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), }; public IEnumerable InGameKeyBindings => new[] @@ -284,6 +285,9 @@ namespace osu.Game.Input.Bindings SeekReplayBackward, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleChatFocus))] - ToggleChatFocus + ToggleChatFocus, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] + EditorCycleGridDisplayMode } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 14159f0d34..06f1b094bf 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -164,6 +164,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode"); + /// + /// "Cycle grid display mode" + /// + public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// /// "Hold for HUD" /// From b5af01f4561757857e5863b1b306efb11f1049f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Sep 2021 20:13:06 +0200 Subject: [PATCH 39/72] Always show rectangular grid in osu! composer --- .../Edit/OsuHitObjectComposer.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a2ee646341..3e4711db58 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit { RelativeSizeAxes = Axes.Both }, - rectangularPositionSnapGridContainer = new Container + rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid(EditorBeatmap.BeatmapInfo.GridSize) { RelativeSizeAxes = Axes.Both } @@ -89,8 +89,6 @@ namespace osu.Game.Rulesets.Osu.Edit gridSnapToggle.ValueChanged += _ => { - updateRectangularPositionSnapGrid(); - if (gridSnapToggle.Value == TernaryState.True) distanceSnapToggle.Value = TernaryState.False; }; @@ -111,6 +109,8 @@ namespace osu.Game.Rulesets.Osu.Edit private readonly Cached distanceSnapGridCache = new Cached(); private double? lastDistanceSnapGridTime; + private RectangularPositionSnapGrid rectangularPositionSnapGrid; + protected override void Update() { base.Update(); @@ -298,21 +298,5 @@ namespace osu.Game.Rulesets.Osu.Edit return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject); } - - private Container rectangularPositionSnapGridContainer; - private RectangularPositionSnapGrid rectangularPositionSnapGrid; - - private void updateRectangularPositionSnapGrid() - { - rectangularPositionSnapGridContainer.Clear(); - - if (gridSnapToggle.Value == TernaryState.True) - { - rectangularPositionSnapGridContainer.Add(rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid(EditorBeatmap.BeatmapInfo.GridSize) - { - RelativeSizeAxes = Axes.Both - }); - } - } } } From 52542374e8bb07b5e2e76a408adb1139313b9b62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Sep 2021 20:14:28 +0200 Subject: [PATCH 40/72] Fix rectangular grid snap being always active --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 3e4711db58..027334ba8b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -42,12 +42,12 @@ namespace osu.Game.Rulesets.Osu.Edit }; private readonly Bindable distanceSnapToggle = new Bindable(); - private readonly Bindable gridSnapToggle = new Bindable(); + private readonly Bindable rectangularGridSnapToggle = new Bindable(); protected override IEnumerable CreateTernaryButtons() => base.CreateTernaryButtons().Concat(new[] { new TernaryButton(distanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }), - new TernaryButton(gridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th }) + new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th }) }); private BindableList selectedHitObjects; @@ -84,12 +84,12 @@ namespace osu.Game.Rulesets.Osu.Edit updateDistanceSnapGrid(); if (distanceSnapToggle.Value == TernaryState.True) - gridSnapToggle.Value = TernaryState.False; + rectangularGridSnapToggle.Value = TernaryState.False; }; - gridSnapToggle.ValueChanged += _ => + rectangularGridSnapToggle.ValueChanged += _ => { - if (gridSnapToggle.Value == TernaryState.True) + if (rectangularGridSnapToggle.Value == TernaryState.True) distanceSnapToggle.Value = TernaryState.False; }; @@ -142,13 +142,13 @@ namespace osu.Game.Rulesets.Osu.Edit if (positionSnap.ScreenSpacePosition != screenSpacePosition) return positionSnap; - if (distanceSnapGrid != null) + if (distanceSnapToggle.Value == TernaryState.True && distanceSnapGrid != null) { (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); } - if (rectangularPositionSnapGrid != null) + if (rectangularGridSnapToggle.Value == TernaryState.True) { Vector2 pos = rectangularPositionSnapGrid.GetSnappedPosition(rectangularPositionSnapGrid.ToLocalSpace(screenSpacePosition)); return new SnapResult(rectangularPositionSnapGrid.ToScreenSpace(pos), null, PlayfieldAtScreenSpacePosition(screenSpacePosition)); From fe21577f113d89d2d648442c53f19dfcf5fb657a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Sep 2021 20:32:34 +0200 Subject: [PATCH 41/72] Adjust grid snap in line with new logic --- .../Editor/TestSceneOsuEditorGrids.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 81e44a57db..77aac54929 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -4,7 +4,9 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Tests.Visual; using osuTK; using osuTK.Input; @@ -21,14 +23,33 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); + rectangularGridActive(false); AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("distance snap grid hidden", () => !this.ChildrenOfType().Any()); - AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); + rectangularGridActive(true); AddStep("enable distance snap grid", () => InputManager.Key(Key.T)); + AddStep("select second object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.ElementAt(1))); AddUntilStep("distance snap grid visible", () => this.ChildrenOfType().Any()); - AddUntilStep("rectangular grid hidden", () => !this.ChildrenOfType().Any()); + rectangularGridActive(false); + } + + private void rectangularGridActive(bool active) + { + AddStep("choose placement tool", () => InputManager.Key(Key.Number2)); + AddStep("move cursor to (1, 1)", () => + { + var composer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(composer.ToScreenSpace(new Vector2(1, 1))); + }); + + if (active) + AddAssert("placement blueprint at (0, 0)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(0, 0))); + else + AddAssert("placement blueprint at (1, 1)", () => Precision.AlmostEquals(Editor.ChildrenOfType().Single().HitObject.Position, new Vector2(1, 1))); + + AddStep("choose selection tool", () => InputManager.Key(Key.Number1)); } [Test] From 0d7dac03f439c1333ef2572ae8590728bfa1934b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Sep 2021 20:34:22 +0200 Subject: [PATCH 42/72] Start with largest grid size --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 77aac54929..00813dee3a 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -57,12 +57,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(4); + gridSizeIs(32); + nextGridSizeIs(4); nextGridSizeIs(8); nextGridSizeIs(16); nextGridSizeIs(32); - nextGridSizeIs(4); } private void nextGridSizeIs(int size) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs index da18d3175e..74cb49bedc 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Edit { private static readonly int[] grid_sizes = { 4, 8, 16, 32 }; - private int currentGridSizeIndex; + private int currentGridSizeIndex = grid_sizes.Length - 1; public OsuRectangularPositionSnapGrid(int gridSize) : base(OsuPlayfield.BASE_SIZE / 2) From d15bd5a15e7c293dfa5a58a0049c0b74de024a37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Sep 2021 20:39:39 +0200 Subject: [PATCH 43/72] Store grid size back to beatmap on change --- .../Editor/TestSceneOsuEditorGrids.cs | 3 ++- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/OsuRectangularPositionSnapGrid.cs | 19 ++++++++++++++++--- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 00813dee3a..a24cb6526d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor } private void gridSizeIs(int size) - => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size)); + => AddAssert($"grid size is {size}", () => this.ChildrenOfType().Single().Spacing == new Vector2(size) + && EditorBeatmap.BeatmapInfo.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 027334ba8b..1e84ec80e1 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit { RelativeSizeAxes = Axes.Both }, - rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid(EditorBeatmap.BeatmapInfo.GridSize) + rectangularPositionSnapGrid = new OsuRectangularPositionSnapGrid { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs index 74cb49bedc..b93585af09 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; @@ -17,10 +19,18 @@ namespace osu.Game.Rulesets.Osu.Edit private int currentGridSizeIndex = grid_sizes.Length - 1; - public OsuRectangularPositionSnapGrid(int gridSize) + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + public OsuRectangularPositionSnapGrid() : base(OsuPlayfield.BASE_SIZE / 2) { - var gridSizeIndex = Array.IndexOf(grid_sizes, gridSize); + } + + [BackgroundDependencyLoader] + private void load() + { + var gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); if (gridSizeIndex > 0) currentGridSizeIndex = gridSizeIndex; updateSpacing(); @@ -34,7 +44,10 @@ namespace osu.Game.Rulesets.Osu.Edit private void updateSpacing() { - Spacing = new Vector2(grid_sizes[currentGridSizeIndex]); + int gridSize = grid_sizes[currentGridSizeIndex]; + + editorBeatmap.BeatmapInfo.GridSize = gridSize; + Spacing = new Vector2(gridSize); } public bool OnPressed(KeyBindingPressEvent e) From 93e33fa94d666130e9628980acfcd98c6c5536e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Sep 2021 14:08:54 +0900 Subject: [PATCH 44/72] Use `true` comparison rather than null coalesce fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index 8c42e47dbc..828f2ec393 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// /// The column index. /// Whether the column with index contains a hit object. - public bool ColumnHasObject(int column) => containedColumns?.Contains(column) ?? false; + public bool ColumnHasObject(int column) => containedColumns?.Contains(column) == true; /// /// Amount of columns taken up by hit objects in this pattern. From aa0d32b3b109bdc62bc8f7596baef3bce774cfa8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Sep 2021 14:23:44 +0900 Subject: [PATCH 45/72] Retarget `master` repos in diffcalc CI runs --- .github/workflows/diffcalc.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 842522ae87..3c57d971ae 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -78,8 +78,7 @@ jobs: - name: Checkout osu (master) uses: actions/checkout@v2 with: - repository: peppy/osu - ref: 'diffcalc-optimisations' + repository: ppy/osu path: 'master/osu' - name: Checkout osu (pr) uses: actions/checkout@v2 @@ -89,14 +88,12 @@ jobs: - name: Checkout osu-difficulty-calculator (master) uses: actions/checkout@v2 with: - repository: peppy/osu-difficulty-calculator - ref: 'bypass-attrib-row-insert' + repository: ppy/osu-difficulty-calculator path: 'master/osu-difficulty-calculator' - name: Checkout osu-difficulty-calculator (pr) uses: actions/checkout@v2 with: - repository: peppy/osu-difficulty-calculator - ref: 'bypass-attrib-row-insert' + repository: ppy/osu-difficulty-calculator path: 'pr/osu-difficulty-calculator' - name: Install .NET 5.0.x From 5c7fe5dde0fdc861f48d1741c01d8312e52ce3b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Sep 2021 14:23:44 +0900 Subject: [PATCH 46/72] Retarget `master` repos in diffcalc CI runs --- .github/workflows/diffcalc.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 842522ae87..3c57d971ae 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -78,8 +78,7 @@ jobs: - name: Checkout osu (master) uses: actions/checkout@v2 with: - repository: peppy/osu - ref: 'diffcalc-optimisations' + repository: ppy/osu path: 'master/osu' - name: Checkout osu (pr) uses: actions/checkout@v2 @@ -89,14 +88,12 @@ jobs: - name: Checkout osu-difficulty-calculator (master) uses: actions/checkout@v2 with: - repository: peppy/osu-difficulty-calculator - ref: 'bypass-attrib-row-insert' + repository: ppy/osu-difficulty-calculator path: 'master/osu-difficulty-calculator' - name: Checkout osu-difficulty-calculator (pr) uses: actions/checkout@v2 with: - repository: peppy/osu-difficulty-calculator - ref: 'bypass-attrib-row-insert' + repository: ppy/osu-difficulty-calculator path: 'pr/osu-difficulty-calculator' - name: Install .NET 5.0.x From 59d6a718d66eb39d81a3ad067354d1cbabb1c4b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Sep 2021 20:09:34 +0900 Subject: [PATCH 47/72] Fix value not being loaded from beatmap in case of most dense grid setting --- osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs index b93585af09..b8ff92bd37 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuRectangularPositionSnapGrid.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit private void load() { var gridSizeIndex = Array.IndexOf(grid_sizes, editorBeatmap.BeatmapInfo.GridSize); - if (gridSizeIndex > 0) + if (gridSizeIndex >= 0) currentGridSizeIndex = gridSizeIndex; updateSpacing(); } From 4cdce69f7e7c7b365c386f580def615723749658 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Sep 2021 23:45:03 +0900 Subject: [PATCH 48/72] Update test to match test beamap data --- osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index a24cb6526d..47b2d3a098 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -57,12 +57,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor { AddStep("enable rectangular grid", () => InputManager.Key(Key.Y)); AddUntilStep("rectangular grid visible", () => this.ChildrenOfType().Any()); - gridSizeIs(32); + gridSizeIs(4); - nextGridSizeIs(4); nextGridSizeIs(8); nextGridSizeIs(16); nextGridSizeIs(32); + nextGridSizeIs(4); } private void nextGridSizeIs(int size) From 77660d904820d2e632054a19ba1f1ec2ce36d5e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Sep 2021 01:13:47 +0900 Subject: [PATCH 49/72] Update diffcalc action to checkout the correct upstream branch --- .github/workflows/diffcalc.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 3c57d971ae..9e6d1c9f71 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -74,16 +74,23 @@ jobs: mkdir -p $GITHUB_WORKSPACE/master/ mkdir -p $GITHUB_WORKSPACE/pr/ + - name: Get upstream branch # https://akaimo.hatenablog.jp/entry/2020/05/16/101251 + id: upstreambranch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" + # Checkout osu - name: Checkout osu (master) uses: actions/checkout@v2 with: - repository: ppy/osu path: 'master/osu' - name: Checkout osu (pr) uses: actions/checkout@v2 with: path: 'pr/osu' + ref: ${{ steps.upstreambranch.outputs.branchname }} - name: Checkout osu-difficulty-calculator (master) uses: actions/checkout@v2 From d22fcc14fc3cfb095a17a392cee666bb7a8ba5ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Sep 2021 01:28:17 +0900 Subject: [PATCH 50/72] Also grab correct repository path --- .github/workflows/diffcalc.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 9e6d1c9f71..35fd45320c 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -80,6 +80,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" + echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" # Checkout osu - name: Checkout osu (master) @@ -90,6 +91,7 @@ jobs: uses: actions/checkout@v2 with: path: 'pr/osu' + repository: $${{ steps.upstreambranch.outputs.repo }} ref: ${{ steps.upstreambranch.outputs.branchname }} - name: Checkout osu-difficulty-calculator (master) From 29d69b2b83742246fddee1a208128bb53d5de913 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 22 Sep 2021 01:29:56 +0900 Subject: [PATCH 51/72] Remove extra $ --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 35fd45320c..bc2626d3d6 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -91,7 +91,7 @@ jobs: uses: actions/checkout@v2 with: path: 'pr/osu' - repository: $${{ steps.upstreambranch.outputs.repo }} + repository: ${{ steps.upstreambranch.outputs.repo }} ref: ${{ steps.upstreambranch.outputs.branchname }} - name: Checkout osu-difficulty-calculator (master) From dcadf3b81dd7c012ffac4749778eb1a3cb081160 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Sep 2021 19:18:51 +0900 Subject: [PATCH 52/72] Add failing test coverage of some dialogs being held on to --- .../UserInterface/TestSceneDialogOverlay.cs | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index f5cba2c900..405461eec8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -24,9 +24,10 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestBasic() { - TestPopupDialog dialog = null; + TestPopupDialog firstDialog = null; + TestPopupDialog secondDialog = null; - AddStep("dialog #1", () => overlay.Push(dialog = new TestPopupDialog + AddStep("dialog #1", () => overlay.Push(firstDialog = new TestPopupDialog { Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", @@ -46,9 +47,9 @@ namespace osu.Game.Tests.Visual.UserInterface }, })); - AddAssert("first dialog displayed", () => overlay.CurrentDialog == dialog); + AddAssert("first dialog displayed", () => overlay.CurrentDialog == firstDialog); - AddStep("dialog #2", () => overlay.Push(dialog = new TestPopupDialog + AddStep("dialog #2", () => overlay.Push(secondDialog = new TestPopupDialog { Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", @@ -82,30 +83,33 @@ namespace osu.Game.Tests.Visual.UserInterface }, })); - AddAssert("second dialog displayed", () => overlay.CurrentDialog == dialog); + AddAssert("second dialog displayed", () => overlay.CurrentDialog == secondDialog); + AddAssert("first dialog is not part of hierarchy", () => firstDialog.Parent == null); } [Test] public void TestDismissBeforePush() { + TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { - overlay.Push(new TestPopupDialog + overlay.Push(testDialog = new TestPopupDialog { State = { Value = Visibility.Hidden } }); }); AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); + AddAssert("dialog is not part of hierarchy", () => testDialog.Parent == null); } [Test] public void TestDismissBeforePushViaButtonPress() { + TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { - TestPopupDialog dialog; - overlay.Push(dialog = new TestPopupDialog + overlay.Push(testDialog = new TestPopupDialog { Buttons = new PopupDialogButton[] { @@ -113,10 +117,11 @@ namespace osu.Game.Tests.Visual.UserInterface }, }); - dialog.PerformOkAction(); + testDialog.PerformOkAction(); }); AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); + AddAssert("dialog is not part of hierarchy", () => testDialog.Parent == null); } private class TestPopupDialog : PopupDialog From 6b698047ab7dc0f937bcb61669ed326e990be51d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 23 Sep 2021 19:19:03 +0900 Subject: [PATCH 53/72] Fix `DialogOverlay` potentially not expiring dialogs as soon as it should --- osu.Game/Overlays/DialogOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index d5d31343f2..f051e09c08 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -49,6 +49,8 @@ namespace osu.Game.Overlays Show(); } + public override bool IsPresent => dialogContainer.Children.Count > 0; + protected override bool BlockNonPositionalInput => true; private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) From ad6c4e38784cfaee12b7b4f232b8d5e673eca293 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 24 Sep 2021 12:46:46 +0900 Subject: [PATCH 54/72] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 4859510e6c..967405cd2e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e6afbe383a..61ae6aa69e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 51ca381b63..e032926a9c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 9b9c30c8a13bfa6b724f691d5c2b11dd76078a68 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 24 Sep 2021 21:57:52 -0700 Subject: [PATCH 55/72] Fix text overflowing on dropdown headers --- .../Graphics/UserInterface/OsuDropdown.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 42f628a75a..fe88e6f78a 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -274,21 +274,40 @@ namespace osu.Game.Graphics.UserInterface CornerRadius = corner_radius; Height = 40; - Foreground.Children = new Drawable[] + Foreground.Child = new GridContainer { - Text = new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + RowDimensions = new[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + new Dimension(GridSizeMode.AutoSize), }, - Icon = new SpriteIcon + ColumnDimensions = new[] { - Icon = FontAwesome.Solid.ChevronDown, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 5 }, - Size = new Vector2(12), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), }, + Content = new[] + { + new Drawable[] + { + Text = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Truncate = true, + }, + Icon = new SpriteIcon + { + Icon = FontAwesome.Solid.ChevronDown, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Horizontal = 5 }, + Size = new Vector2(12), + }, + } + } }; AddInternal(new HoverClickSounds()); From 4aadff3fd795badade0f0c7816046d1180ff7de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 15:08:43 +0200 Subject: [PATCH 56/72] Add failing test for incorrect composer selection --- .../Editing/TestSceneBlueprintSelection.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs index 976bf93c15..0dff6d9dd9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs @@ -7,9 +7,13 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Beatmaps; using osuTK; @@ -66,5 +70,36 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("selection is unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == firstSlider); } + + [Test] + public void TestOverlappingObjectsWithSameStartTime() + { + AddStep("add overlapping circles", () => + { + EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2)); + EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(-10, -20))); + EditorBeatmap.Add(createHitCircle(50, OsuPlayfield.BASE_SIZE / 2 + new Vector2(10, -20))); + }); + + AddStep("click at centre of playfield", () => + { + var hitObjectContainer = Editor.ChildrenOfType().Single(); + var centre = hitObjectContainer.ToScreenSpace(OsuPlayfield.BASE_SIZE / 2); + InputManager.MoveMouseTo(centre); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("frontmost object selected", () => + { + var hasCombo = Editor.ChildrenOfType().Single(b => b.IsSelected).Item as IHasComboInformation; + return hasCombo?.IndexInCurrentCombo == 0; + }); + } + + private HitCircle createHitCircle(double startTime, Vector2 position) => new HitCircle + { + StartTime = startTime, + Position = position, + }; } } From 0057400bb761750d6d52815388e0b809a90d7e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 15:09:30 +0200 Subject: [PATCH 57/72] Rename test scene to reflect its new purpose --- ...SceneBlueprintSelection.cs => TestSceneBlueprintOrdering.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Editing/{TestSceneBlueprintSelection.cs => TestSceneBlueprintOrdering.cs} (98%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs similarity index 98% rename from osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs rename to osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs index 0dff6d9dd9..955069f509 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs @@ -21,7 +21,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneBlueprintSelection : EditorTestScene + public class TestSceneBlueprintOrdering : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); From ce70d1082d68d938250509856c1bd6b0f9a4f1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 15:29:00 +0200 Subject: [PATCH 58/72] Add failing test for "ghost timeline blueprint" --- .../Editing/TestSceneBlueprintOrdering.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs index 955069f509..5d8a6dabd7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneBlueprintOrdering.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Input; @@ -96,6 +97,32 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestPlacementOfConcurrentObjectWithDuration() + { + AddStep("seek to timing point", () => EditorClock.Seek(2170)); + AddStep("add hit circle", () => EditorBeatmap.Add(createHitCircle(2170, Vector2.Zero))); + + AddStep("choose spinner placement tool", () => + { + InputManager.Key(Key.Number4); + var hitObjectContainer = Editor.ChildrenOfType().Single(); + InputManager.MoveMouseTo(hitObjectContainer.ScreenSpaceDrawQuad.Centre); + }); + + AddStep("begin placing spinner", () => + { + InputManager.Click(MouseButton.Left); + }); + AddStep("end placing spinner", () => + { + EditorClock.Seek(2500); + InputManager.Click(MouseButton.Right); + }); + + AddAssert("two timeline blueprints present", () => Editor.ChildrenOfType().Count() == 2); + } + private HitCircle createHitCircle(double startTime, Vector2 position) => new HitCircle { StartTime = startTime, From a86b9893acd65219aef8dd0c737125f35f32a5a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 15:48:56 +0200 Subject: [PATCH 59/72] Always re-sort blueprints before adding/removing one --- .../HitObjectOrderedSelectionContainer.cs | 66 +++++++------------ 1 file changed, 24 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index 4078661a26..eebaa25af1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -1,8 +1,7 @@ // 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 osu.Framework.Bindables; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; @@ -15,53 +14,28 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public sealed class HitObjectOrderedSelectionContainer : Container> { + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + editorBeatmap.HitObjectUpdated += hitObjectUpdated; + } + + private void hitObjectUpdated(HitObject _) => SortInternal(); + public override void Add(SelectionBlueprint drawable) { + SortInternal(); base.Add(drawable); - bindStartTime(drawable); } public override bool Remove(SelectionBlueprint drawable) { - if (!base.Remove(drawable)) - return false; - - unbindStartTime(drawable); - return true; - } - - public override void Clear(bool disposeChildren) - { - base.Clear(disposeChildren); - unbindAllStartTimes(); - } - - private readonly Dictionary, IBindable> startTimeMap = new Dictionary, IBindable>(); - - private void bindStartTime(SelectionBlueprint blueprint) - { - var bindable = blueprint.Item.StartTimeBindable.GetBoundCopy(); - - bindable.BindValueChanged(_ => - { - if (LoadState >= LoadState.Ready) - SortInternal(); - }); - - startTimeMap[blueprint] = bindable; - } - - private void unbindStartTime(SelectionBlueprint blueprint) - { - startTimeMap[blueprint].UnbindAll(); - startTimeMap.Remove(blueprint); - } - - private void unbindAllStartTimes() - { - foreach (var kvp in startTimeMap) - kvp.Value.UnbindAll(); - startTimeMap.Clear(); + SortInternal(); + return base.Remove(drawable); } protected override int Compare(Drawable x, Drawable y) @@ -79,5 +53,13 @@ namespace osu.Game.Screens.Edit.Compose.Components return i == 0 ? CompareReverseChildID(y, x) : i; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorBeatmap != null) + editorBeatmap.HitObjectUpdated -= hitObjectUpdated; + } } } From 6dc3e66c930518c72889319a5c618a23148c51a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 15:52:25 +0200 Subject: [PATCH 60/72] Include combo information when determining ordering if available --- .../HitObjectOrderedSelectionContainer.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index eebaa25af1..e8286c5128 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Screens.Edit.Compose.Components { @@ -45,13 +46,23 @@ namespace osu.Game.Screens.Edit.Compose.Components // Put earlier blueprints towards the end of the list, so they handle input first int i = yObj.Item.StartTime.CompareTo(xObj.Item.StartTime); - if (i != 0) return i; // Fall back to end time if the start time is equal. i = yObj.Item.GetEndTime().CompareTo(xObj.Item.GetEndTime()); + if (i != 0) return i; - return i == 0 ? CompareReverseChildID(y, x) : i; + // As a final fallback, use combo information if available. + if (xObj.Item is IHasComboInformation xHasCombo && yObj.Item is IHasComboInformation yHasCombo) + { + i = yHasCombo.ComboIndex.CompareTo(xHasCombo.ComboIndex); + if (i != 0) return i; + + i = yHasCombo.IndexInCurrentCombo.CompareTo(xHasCombo.IndexInCurrentCombo); + if (i != 0) return i; + } + + return CompareReverseChildID(y, x); } protected override void Dispose(bool isDisposing) From 7caa0a81ec5525f5bbcd3599737e7b14aa2057da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 17:22:58 +0200 Subject: [PATCH 61/72] Rename `TestScene{Editor -> Composer}Selection` In line with an upcoming split in functionality between the composer blueprint container and the timeline blueprint container. --- ...ceneEditorSelection.cs => TestSceneComposerSelection.cs} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename osu.Game.Tests/Visual/Editing/{TestSceneEditorSelection.cs => TestSceneComposerSelection.cs} (98%) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs similarity index 98% rename from osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs rename to osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index 4c4a87972f..a2a7b72283 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -20,14 +20,14 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { - public class TestSceneEditorSelection : EditorTestScene + public class TestSceneComposerSelection : EditorTestScene { protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); - private EditorBlueprintContainer blueprintContainer - => Editor.ChildrenOfType().First(); + private ComposeBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); private void moveMouseToObject(Func targetFunc) { From 0de7db5840573d818f89a5607f43e0c68404f234 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 17:41:20 +0200 Subject: [PATCH 62/72] Add test coverage for timeline selection logic --- .../Editing/TestSceneTimelineSelection.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs new file mode 100644 index 0000000000..0a1ab522a1 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -0,0 +1,163 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneTimelineSelection : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false); + + private TimelineBlueprintContainer blueprintContainer + => Editor.ChildrenOfType().First(); + + private void moveMouseToObject(Func targetFunc) + { + AddStep("move mouse to object", () => + { + var pos = blueprintContainer.SelectionBlueprints + .First(s => s.Item == targetFunc()) + .ChildrenOfType() + .First().ScreenSpaceDrawQuad.Centre; + + InputManager.MoveMouseTo(pos); + }); + } + + [Test] + public void TestNudgeSelection() + { + HitCircle[] addedObjects = null; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, + })); + + AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects)); + + AddStep("nudge forwards", () => InputManager.Key(Key.K)); + AddAssert("objects moved forwards in time", () => addedObjects[0].StartTime > 100); + + AddStep("nudge backwards", () => InputManager.Key(Key.J)); + AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); + } + + [Test] + public void TestBasicSelect() + { + var addedObject = new HitCircle { StartTime = 100 }; + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(() => addedObject); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + var addedObject2 = new HitCircle + { + StartTime = 200, + Position = new Vector2(100), + }; + + AddStep("add one more hitobject", () => EditorBeatmap.Add(addedObject2)); + AddAssert("selection unchanged", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + moveMouseToObject(() => addedObject2); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject2); + } + + [Test] + public void TestMultiSelect() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[0]); + + AddStep("hold control", () => InputManager.PressKey(Key.ControlLeft)); + + moveMouseToObject(() => addedObjects[1]); + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); + + moveMouseToObject(() => addedObjects[2]); + AddStep("click third", () => InputManager.Click(MouseButton.Left)); + AddAssert("3 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 3 && EditorBeatmap.SelectedHitObjects.Contains(addedObjects[2])); + + moveMouseToObject(() => addedObjects[1]); + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + AddAssert("2 hitobjects selected", () => EditorBeatmap.SelectedHitObjects.Count == 2 && !EditorBeatmap.SelectedHitObjects.Contains(addedObjects[1])); + + AddStep("release control", () => InputManager.ReleaseKey(Key.ControlLeft)); + } + + [Test] + public void TestBasicDeselect() + { + var addedObject = new HitCircle { StartTime = 100 }; + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(() => addedObject); + AddStep("left click", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + + AddStep("click away", () => + { + InputManager.MoveMouseTo(Editor.ChildrenOfType().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("selection lost", () => EditorBeatmap.SelectedHitObjects.Count == 0); + } + + [Test] + public void TestQuickDelete() + { + var addedObject = new HitCircle + { + StartTime = 0, + }; + + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(() => addedObject); + + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); + } + } +} From 81d160c85a0e75b5994abcbda27a9ea9fde28bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 17:45:47 +0200 Subject: [PATCH 63/72] Add test covering expected UX of range selection --- .../Editing/TestSceneTimelineSelection.cs | 68 +++++++++++++++++++ .../Timeline/TimelineSelectionHandler.cs | 33 +++++++++ 2 files changed, 101 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index 0a1ab522a1..f4703b79f0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -159,5 +160,72 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count == 0); } + + [Test] + public void TestRangeSelect() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, + new HitCircle { StartTime = 500, Position = new Vector2(400) }, + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + moveMouseToObject(() => addedObjects[1]); + AddStep("click second", () => InputManager.Click(MouseButton.Left)); + + AddAssert("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObjects[1]); + + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + + moveMouseToObject(() => addedObjects[3]); + AddStep("click fourth", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Skip(1).Take(3)); + + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Take(2)); + + AddStep("clear selection", () => EditorBeatmap.SelectedHitObjects.Clear()); + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); + + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Take(1)); + + AddStep("hold ctrl", () => InputManager.PressKey(Key.ControlLeft)); + moveMouseToObject(() => addedObjects[2]); + AddStep("click third", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(new[] { addedObjects[0], addedObjects[2] }); + + AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft)); + moveMouseToObject(() => addedObjects[4]); + AddStep("click fifth", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Except(new[] { addedObjects[1] })); + + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects); + + AddStep("clear selection", () => EditorBeatmap.SelectedHitObjects.Clear()); + moveMouseToObject(() => addedObjects[0]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Take(1)); + + moveMouseToObject(() => addedObjects[1]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Take(2)); + + moveMouseToObject(() => addedObjects[2]); + AddStep("click first", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Take(3)); + } + + private void assertSelectionIs(IEnumerable hitObjects) + => AddAssert("correct hitobjects selected", () => EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).SequenceEqual(hitObjects)); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index c43e554b85..ff385b0ab3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -1,13 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { @@ -62,5 +65,35 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline EditorBeatmap.Update(h); }); } + + internal override bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + { + if (e.ShiftPressed && e.Button == MouseButton.Left && SelectedItems.Any()) + { + handleRangeSelection(blueprint); + return true; + } + + return base.MouseDownSelectionRequested(blueprint, e); + } + + private void handleRangeSelection(SelectionBlueprint blueprint) + { + var clickedObject = blueprint.Item; + double rangeStart = clickedObject.StartTime; + double rangeEnd = clickedObject.GetEndTime(); + + foreach (var selectedObject in SelectedItems) + { + rangeStart = Math.Min(rangeStart, selectedObject.StartTime); + rangeEnd = Math.Max(rangeEnd, selectedObject.GetEndTime()); + } + + EditorBeatmap.SelectedHitObjects.Clear(); + EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects.Where(obj => isInRange(obj, rangeStart, rangeEnd))); + + bool isInRange(HitObject hitObject, double start, double end) + => hitObject.StartTime >= start && hitObject.GetEndTime() <= end; + } } } From d3203f83dd538efdee901ac8eefca79694e3291b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 26 Sep 2021 19:07:38 +0200 Subject: [PATCH 64/72] Add implementation of range selection --- .../Compose/Components/SelectionHandler.cs | 2 +- .../Timeline/TimelineSelectionHandler.cs | 48 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 44eb062db8..ee35b6a47c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -191,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The blueprint. /// The mouse event responsible for selection. /// Whether a selection was performed. - internal bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) + internal virtual bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (e.ShiftPressed && e.Button == MouseButton.Right) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index ff385b0ab3..ad770b40b5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -2,7 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -66,31 +69,58 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }); } + /// + /// The "pivot" object, used in range selection mode. + /// When in range selection, the range to select is determined by the pivot object + /// (last existing object interacted with prior to holding down Shift) + /// and by the object clicked last when Shift was pressed. + /// + [CanBeNull] + private HitObject pivot; + internal override bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { if (e.ShiftPressed && e.Button == MouseButton.Left && SelectedItems.Any()) { - handleRangeSelection(blueprint); + handleRangeSelection(blueprint, e.ControlPressed); return true; } - return base.MouseDownSelectionRequested(blueprint, e); + bool result = base.MouseDownSelectionRequested(blueprint, e); + // ensure that the object wasn't removed by the base implementation before making it the new pivot. + if (EditorBeatmap.HitObjects.Contains(blueprint.Item)) + pivot = blueprint.Item; + return result; } - private void handleRangeSelection(SelectionBlueprint blueprint) + /// + /// Handles a request for range selection (triggered when Shift is held down). + /// + /// The blueprint which was clicked in range selection mode. + /// + /// Whether the selection should be cumulative. + /// In cumulative mode, consecutive range selections will shift the pivot (which usually stays fixed for the duration of a range selection) + /// and will never deselect an object that was previously selected. + /// + private void handleRangeSelection(SelectionBlueprint blueprint, bool cumulative) { var clickedObject = blueprint.Item; - double rangeStart = clickedObject.StartTime; - double rangeEnd = clickedObject.GetEndTime(); - foreach (var selectedObject in SelectedItems) + Debug.Assert(pivot != null); + + double rangeStart = Math.Min(clickedObject.StartTime, pivot.StartTime); + double rangeEnd = Math.Max(clickedObject.GetEndTime(), pivot.GetEndTime()); + + var newSelection = new HashSet(EditorBeatmap.HitObjects.Where(obj => isInRange(obj, rangeStart, rangeEnd))); + + if (cumulative) { - rangeStart = Math.Min(rangeStart, selectedObject.StartTime); - rangeEnd = Math.Max(rangeEnd, selectedObject.GetEndTime()); + pivot = clickedObject; + newSelection.UnionWith(EditorBeatmap.SelectedHitObjects); } EditorBeatmap.SelectedHitObjects.Clear(); - EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects.Where(obj => isInRange(obj, rangeStart, rangeEnd))); + EditorBeatmap.SelectedHitObjects.AddRange(newSelection); bool isInRange(HitObject hitObject, double start, double end) => hitObject.StartTime >= start && hitObject.GetEndTime() <= end; From df850924262e726c5b4c5d71a159314dad55e769 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Sep 2021 14:24:17 +0900 Subject: [PATCH 65/72] Resolve inner items early in process and rename variable --- .../HitObjectOrderedSelectionContainer.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs index e8286c5128..3fc26fa974 100644 --- a/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/HitObjectOrderedSelectionContainer.cs @@ -41,25 +41,25 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override int Compare(Drawable x, Drawable y) { - var xObj = (SelectionBlueprint)x; - var yObj = (SelectionBlueprint)y; + var xObj = ((SelectionBlueprint)x).Item; + var yObj = ((SelectionBlueprint)y).Item; // Put earlier blueprints towards the end of the list, so they handle input first - int i = yObj.Item.StartTime.CompareTo(xObj.Item.StartTime); - if (i != 0) return i; + int result = yObj.StartTime.CompareTo(xObj.StartTime); + if (result != 0) return result; // Fall back to end time if the start time is equal. - i = yObj.Item.GetEndTime().CompareTo(xObj.Item.GetEndTime()); - if (i != 0) return i; + result = yObj.GetEndTime().CompareTo(xObj.GetEndTime()); + if (result != 0) return result; // As a final fallback, use combo information if available. - if (xObj.Item is IHasComboInformation xHasCombo && yObj.Item is IHasComboInformation yHasCombo) + if (xObj is IHasComboInformation xHasCombo && yObj is IHasComboInformation yHasCombo) { - i = yHasCombo.ComboIndex.CompareTo(xHasCombo.ComboIndex); - if (i != 0) return i; + result = yHasCombo.ComboIndex.CompareTo(xHasCombo.ComboIndex); + if (result != 0) return result; - i = yHasCombo.IndexInCurrentCombo.CompareTo(xHasCombo.IndexInCurrentCombo); - if (i != 0) return i; + result = yHasCombo.IndexInCurrentCombo.CompareTo(xHasCombo.IndexInCurrentCombo); + if (result != 0) return result; } return CompareReverseChildID(y, x); From d4310f5d9a709b49f97f85c17a1afa7f6d80ecda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Sep 2021 17:32:40 +0900 Subject: [PATCH 66/72] Move database connection string operations local In line with framework changes in https://github.com/ppy/osu-framework/pull/4793. --- .../Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 3 ++- osu.Game/Database/DatabaseContextFactory.cs | 8 +++++--- osu.Game/IO/WrappedStorage.cs | 5 ----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 6ae7f7481e..3dd34f6c2f 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -13,6 +13,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Threading; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using SharpCompress.Compressors; @@ -160,7 +161,7 @@ namespace osu.Game.Beatmaps try { - using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online"))) + using (var db = new SqliteConnection(DatabaseContextFactory.CreateDatabaseConnectionString("online.db", storage))) { db.Open(); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index d402195f29..94fa967d72 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -13,7 +13,7 @@ namespace osu.Game.Database { private readonly Storage storage; - private const string database_name = @"client"; + private const string database_name = @"client.db"; private ThreadLocal threadContexts; @@ -139,7 +139,7 @@ namespace osu.Game.Database threadContexts = new ThreadLocal(CreateContext, true); } - protected virtual OsuDbContext CreateContext() => new OsuDbContext(storage.GetDatabaseConnectionString(database_name)) + protected virtual OsuDbContext CreateContext() => new OsuDbContext(CreateDatabaseConnectionString(database_name, storage)) { Database = { AutoTransactionsEnabled = false } }; @@ -152,7 +152,7 @@ namespace osu.Game.Database try { - storage.DeleteDatabase(database_name); + storage.Delete(database_name); } catch { @@ -171,5 +171,7 @@ namespace osu.Game.Database recycleThreadContexts(); } + + public static string CreateDatabaseConnectionString(string filename, Storage storage) => string.Concat("Data Source=", storage.GetFullPath($@"{filename}", true)); } } diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 5b2549d2ee..b9ccc907d9 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -70,11 +70,6 @@ namespace osu.Game.IO public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) => UnderlyingStorage.GetStream(MutatePath(path), access, mode); - public override string GetDatabaseConnectionString(string name) => - UnderlyingStorage.GetDatabaseConnectionString(MutatePath(name)); - - public override void DeleteDatabase(string name) => UnderlyingStorage.DeleteDatabase(MutatePath(name)); - public override void OpenPathInNativeExplorer(string path) => UnderlyingStorage.OpenPathInNativeExplorer(MutatePath(path)); public override Storage GetStorageForDirectory(string path) From bcdbffbca0168c295f313691ccdde3b0b250c5dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 27 Sep 2021 20:56:54 +0900 Subject: [PATCH 67/72] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 967405cd2e..27df6cf296 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 61ae6aa69e..363bf7f517 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index e032926a9c..4b8b47f982 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 1a1fc00b44198cb0e2cfd26b36a9c935f07e01fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Sep 2021 20:43:08 +0200 Subject: [PATCH 68/72] Add failing test case --- .../Editing/TestSceneTimelineSelection.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs index f4703b79f0..2544b6c2a1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineSelection.cs @@ -223,6 +223,41 @@ namespace osu.Game.Tests.Visual.Editing moveMouseToObject(() => addedObjects[2]); AddStep("click first", () => InputManager.Click(MouseButton.Left)); assertSelectionIs(addedObjects.Take(3)); + + AddStep("release keys", () => + { + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + } + + [Test] + public void TestRangeSelectAfterExternalSelection() + { + var addedObjects = new[] + { + new HitCircle { StartTime = 100 }, + new HitCircle { StartTime = 200, Position = new Vector2(100) }, + new HitCircle { StartTime = 300, Position = new Vector2(200) }, + new HitCircle { StartTime = 400, Position = new Vector2(300) }, + }; + + AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects)); + + AddStep("select all without mouse", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects)); + assertSelectionIs(addedObjects); + + AddStep("hold down shift", () => InputManager.PressKey(Key.ShiftLeft)); + + moveMouseToObject(() => addedObjects[1]); + AddStep("click second object", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects); + + moveMouseToObject(() => addedObjects[3]); + AddStep("click fourth object", () => InputManager.Click(MouseButton.Left)); + assertSelectionIs(addedObjects.Skip(1)); + + AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft)); } private void assertSelectionIs(IEnumerable hitObjects) From ca6cbca04a1bbbd14536110e068f79c2f06537e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Sep 2021 20:54:01 +0200 Subject: [PATCH 69/72] Fix range selection crashing after non-mouse selection --- .../Compose/Components/Timeline/TimelineSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs index ad770b40b5..845a671e2c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineSelectionHandler.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline internal override bool MouseDownSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e) { - if (e.ShiftPressed && e.Button == MouseButton.Left && SelectedItems.Any()) + if (e.ShiftPressed && e.Button == MouseButton.Left && pivot != null) { handleRangeSelection(blueprint, e.ControlPressed); return true; From 7a0499ad07bc19449981cbb5c4983934deda8658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Sep 2021 21:45:26 +0200 Subject: [PATCH 70/72] Fix repeat arrow texture not falling back to default legacy skin --- .../Skinning/Legacy/LegacyReverseArrow.cs | 12 +++--------- .../Skinning/Legacy/OsuLegacySkinTransformer.cs | 2 +- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index fd7bfe7e60..f605b36122 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -13,26 +13,20 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyReverseArrow : CompositeDrawable { - private ISkin skin { get; } - [Resolved(canBeNull: true)] private DrawableHitObject drawableHitObject { get; set; } private Drawable proxy; - public LegacyReverseArrow(ISkin skin) - { - this.skin = skin; - } - [BackgroundDependencyLoader] - private void load() + private void load(ISkinSource skinSource) { AutoSizeAxes = Axes.Both; string lookupName = new OsuSkinComponent(OsuSkinComponents.ReverseArrow).LookupName; - InternalChild = skin.GetAnimation(lookupName, true, true) ?? Empty(); + var skin = skinSource.FindProvider(skin => skin.GetTexture(lookupName) != null); + InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty(); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs index 8a24e36420..ff9f6f0e07 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/OsuLegacySkinTransformer.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy case OsuSkinComponents.ReverseArrow: if (hasHitCircle.Value) - return new LegacyReverseArrow(this); + return new LegacyReverseArrow(); return null; From 5bd09a4a30ec343a7aa78224522041a8121021ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 27 Sep 2021 22:21:14 +0200 Subject: [PATCH 71/72] Rename inner lambda parameter --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs index f605b36122..7a071b5a03 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyReverseArrow.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy string lookupName = new OsuSkinComponent(OsuSkinComponents.ReverseArrow).LookupName; - var skin = skinSource.FindProvider(skin => skin.GetTexture(lookupName) != null); + var skin = skinSource.FindProvider(s => s.GetTexture(lookupName) != null); InternalChild = skin?.GetAnimation(lookupName, true, true) ?? Empty(); } From dcf87e97c282ce1d3580087a3d900b905bfe0a95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Sep 2021 14:03:41 +0900 Subject: [PATCH 72/72] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 27df6cf296..8fad10d247 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 363bf7f517..ba118c5240 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4b8b47f982..37931d0c38 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - +