From d6b9f385c17e3cc78ee19962a5cfed2db7fff6d7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 27 Nov 2021 23:58:08 -0800 Subject: [PATCH 001/174] Add failing play button by touch input test --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 6cb171974a..959e118fac 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -13,12 +13,14 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Beatmaps { @@ -293,5 +295,22 @@ namespace osu.Game.Tests.Visual.Beatmaps BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); } + + [Test] + public void TestPlayButtonByTouchInput() + { + AddStep("create cards", () => Child = createContent(OverlayColourScheme.Blue, beatmapSetInfo => new BeatmapCardNormal(beatmapSetInfo))); + + // mimics touch input + AddStep("touch play button area on first card", () => + { + InputManager.MoveMouseTo(firstCard().ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("first card is playing", () => firstCard().ChildrenOfType().Single().Playing.Value); + + BeatmapCardNormal firstCard() => this.ChildrenOfType().First(); + } } } From 1107e267e3fd4f181c6bff925eaf1ed564d0dd1c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 10 Jun 2022 14:04:03 -0700 Subject: [PATCH 002/174] Fix beatmap card play button not working with touch inputs when not hovered --- osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f7bab26666..51e690d156 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -43,6 +43,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons Anchor = Origin = Anchor.Centre; + // needed for touch input to work when card is not hovered/expanded + AlwaysPresent = true; + Children = new Drawable[] { icon = new SpriteIcon From c6ac60c0b5d5c88cb5d7fa611858507fa7f3c734 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 13:07:10 +0200 Subject: [PATCH 003/174] Enhance target angle calculation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 48 +++++++++++++++---- .../Utils/OsuHitObjectGenerationUtils.cs | 33 +++++++++++++ 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 4f83154728..475d5a2a67 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -37,23 +37,51 @@ namespace osu.Game.Rulesets.Osu.Mods var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); - float rateOfChangeMultiplier = 0; + float sequenceOffset = 0; + bool flowDirection = false; - foreach (var positionInfo in positionInfos) + for (int i = 0; i < positionInfos.Count; i++) { - // rateOfChangeMultiplier only changes every 5 iterations in a combo - // to prevent shaky-line-shaped streams - if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) - rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; + bool invertFlow = false; - if (positionInfo == positionInfos.First()) + if (i == 0 || + (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && (i <= 2 || !positionInfos[i - 3].HitObject.NewCombo)) || + OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { - positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); - positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.02f); + + if (rng.NextDouble() < 0.6) + invertFlow = true; + } + + if (i == 0) + { + positionInfos[i].DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); + positionInfos[i].RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { - positionInfo.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f)); + float flowChangeOffset = 0; + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); + + if (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && rng.NextDouble() < 0.6) + { + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); + + if (rng.NextDouble() < 0.8) + invertFlow = true; + } + + if (invertFlow) + flowDirection ^= true; + + positionInfos[i].RelativeAngle = OsuHitObjectGenerationUtils.GetRelativeTargetAngle( + positionInfos[i].DistanceFromPrevious, + (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + + flowChangeOffset * (float)Math.Sqrt(640 - positionInfos[i].DistanceFromPrevious), + flowDirection + ); } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 5e827d4782..9044fe142b 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -186,5 +187,37 @@ namespace osu.Game.Rulesets.Osu.Utils length * MathF.Sin(angle) ); } + + public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) + { + var timingPoints = beatmap.ControlPointInfo.TimingPoints; + var currentTimingPoint = timingPoints.Reverse().FirstOrDefault(p => p.Time <= hitObject.StartTime); + + if (currentTimingPoint == null) + return false; + + double timeSinceTimingPoint = hitObject.StartTime - currentTimingPoint.Time; + + double length = downbeatsOnly + ? currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator + : currentTimingPoint.BeatLength; + + return (timeSinceTimingPoint + 1) % length < 2; + } + + public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) + { + float angle = (float)(3.3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); + float relativeAngle = MathHelper.Pi - angle; + return flowDirection ? -relativeAngle : relativeAngle; + } + + public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) + { + double x1 = 1 - rng.NextDouble(); + double x2 = 1 - rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(MathHelper.TwoPi * x2); + return mean + stdDev * (float)stdNormal; + } } } From 33c6c6af6b2995f4717009a2cc646775e32d419b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 13:50:09 +0200 Subject: [PATCH 004/174] Adjust target angle calculation parameters --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 9044fe142b..b9c384f842 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Utils public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(3.3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); + float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); float relativeAngle = MathHelper.Pi - angle; return flowDirection ? -relativeAngle : relativeAngle; } From 7317b9b90960a2b326ec858e61b668375fa7e0ac Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 14:59:28 +0200 Subject: [PATCH 005/174] Remove unused field --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 475d5a2a67..2560ed921d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -22,8 +22,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); - private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private Random? rng; public void ApplyToBeatmap(IBeatmap beatmap) From 9090e7502075ed9731f75408b43f5d9afe35646d Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 20:43:17 +0200 Subject: [PATCH 006/174] Add XML documentation --- .../Utils/OsuHitObjectGenerationUtils.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index b9c384f842..f503956aee 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -188,6 +188,11 @@ namespace osu.Game.Rulesets.Osu.Utils ); } + /// The beatmap hitObject is a part of. + /// The that should be checked. + /// If true, this method only returns true if hitObject is on a downbeat. + /// If false, it returns true if hitObject is on any beat. + /// true if hitObject is on a (down-)beat, false otherwise. public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { var timingPoints = beatmap.ControlPointInfo.TimingPoints; @@ -205,6 +210,9 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } + /// The target distance between the previous and the current . + /// The angle (in rad) by which the target angle should be offset. + /// Whether the relative angle should be positive or negative. public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); From 1bb27cd4880f4dd4766d1cace022c37e0f23585b Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 19 Jun 2022 23:03:41 +0200 Subject: [PATCH 007/174] Code optimisation --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 23 +++++++++++++++---- .../Utils/OsuHitObjectGenerationUtils.cs | 18 ++++----------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2560ed921d..55151ae2c1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.Utils; @@ -22,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTarget)).ToArray(); + private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; + private Random? rng; public void ApplyToBeatmap(IBeatmap beatmap) @@ -43,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods bool invertFlow = false; if (i == 0 || - (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && (i <= 2 || !positionInfos[i - 3].HitObject.NewCombo)) || + (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { @@ -63,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods float flowChangeOffset = 0; float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); - if (positionInfos[i - 1].HitObject.NewCombo && (i <= 1 || !positionInfos[i - 2].HitObject.NewCombo) && rng.NextDouble() < 0.6) + if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); @@ -72,12 +75,12 @@ namespace osu.Game.Rulesets.Osu.Mods } if (invertFlow) - flowDirection ^= true; + flowDirection = !flowDirection; - positionInfos[i].RelativeAngle = OsuHitObjectGenerationUtils.GetRelativeTargetAngle( + positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + - flowChangeOffset * (float)Math.Sqrt(640 - positionInfos[i].DistanceFromPrevious), + flowChangeOffset * (float)Math.Sqrt(playfield_diagonal - positionInfos[i].DistanceFromPrevious), flowDirection ); } @@ -85,5 +88,15 @@ namespace osu.Game.Rulesets.Osu.Mods osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); } + + /// The target distance between the previous and the current . + /// The angle (in rad) by which the target angle should be offset. + /// Whether the relative angle should be positive or negative. + private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) + { + float angle = (float)(3 / (1 + 200 * Math.Exp(0.016 * (targetDistance - 466))) + 0.45 + offset); + float relativeAngle = (float)Math.PI - angle; + return flowDirection ? -relativeAngle : relativeAngle; + } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index f503956aee..117d6f3a80 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Utils public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { var timingPoints = beatmap.ControlPointInfo.TimingPoints; - var currentTimingPoint = timingPoints.Reverse().FirstOrDefault(p => p.Time <= hitObject.StartTime); + var currentTimingPoint = timingPoints.LastOrDefault(p => p.Time <= hitObject.StartTime); if (currentTimingPoint == null) return false; @@ -210,21 +210,11 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } - /// The target distance between the previous and the current . - /// The angle (in rad) by which the target angle should be offset. - /// Whether the relative angle should be positive or negative. - public static float GetRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) - { - float angle = (float)(3 / (1 + 200 * Math.Pow(MathHelper.E, 0.016 * (targetDistance - 466))) + 0.45 + offset); - float relativeAngle = MathHelper.Pi - angle; - return flowDirection ? -relativeAngle : relativeAngle; - } - public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { - double x1 = 1 - rng.NextDouble(); - double x2 = 1 - rng.NextDouble(); - double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(MathHelper.TwoPi * x2); + double x1 = rng.NextDouble(); + double x2 = rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } } From 3356742ba2d5b74372c6641a4d857cf6138ad44f Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 20 Jun 2022 00:05:03 +0200 Subject: [PATCH 008/174] Adjust angle offset caluclations --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 55151ae2c1..ef3eaf2a9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.02f); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); if (rng.NextDouble() < 0.6) invertFlow = true; @@ -64,11 +64,11 @@ namespace osu.Game.Rulesets.Osu.Mods else { float flowChangeOffset = 0; - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.03f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { - flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.05f); + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); if (rng.NextDouble() < 0.8) invertFlow = true; @@ -79,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Mods positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, - (sequenceOffset + oneTimeOffset) * (float)Math.Sqrt(positionInfos[i].DistanceFromPrevious) + - flowChangeOffset * (float)Math.Sqrt(playfield_diagonal - positionInfos[i].DistanceFromPrevious), + (sequenceOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + + flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious), flowDirection ); } From a912bcadf82aa7eb4e885cf81da35e980824dd55 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Mon, 20 Jun 2022 00:19:29 +0200 Subject: [PATCH 009/174] Fix possible exception caused by `log(0)` --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 117d6f3a80..ce7f39cdf8 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -212,8 +212,8 @@ namespace osu.Game.Rulesets.Osu.Utils public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { - double x1 = rng.NextDouble(); - double x2 = rng.NextDouble(); + double x1 = 1 - rng.NextDouble(); + double x2 = 1 - rng.NextDouble(); double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } From 2f1186d3284b8cad32d7c6ae83a50bc72d00b326 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 22 Jun 2022 16:49:07 +0200 Subject: [PATCH 010/174] Add comments and XML doc --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index ce7f39cdf8..a890dbde43 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -210,10 +210,16 @@ namespace osu.Game.Rulesets.Osu.Utils return (timeSinceTimingPoint + 1) % length < 2; } + /// + /// Generates a random number from a normal distribution using the Box-Muller transform. + /// public static float RandomGaussian(Random rng, float mean = 0, float stdDev = 1) { + // Generate 2 random numbers in the interval (0,1]. + // x1 must not be 0 since log(0) = undefined. double x1 = 1 - rng.NextDouble(); double x2 = 1 - rng.NextDouble(); + double stdNormal = Math.Sqrt(-2 * Math.Log(x1)) * Math.Sin(2 * Math.PI * x2); return mean + stdDev * (float)stdNormal; } From da7d297d85e4ae5d59747dac3ae8686e1849433f Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Tue, 26 Jul 2022 19:07:25 +0200 Subject: [PATCH 011/174] Adjust parameters --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index ef3eaf2a9d..716fa990c9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -43,17 +43,13 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < positionInfos.Count; i++) { - bool invertFlow = false; - if (i == 0 || (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.25)) + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.3)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); - - if (rng.NextDouble() < 0.6) - invertFlow = true; + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0012f); + flowDirection = !flowDirection; } if (i == 0) @@ -64,18 +60,13 @@ namespace osu.Game.Rulesets.Osu.Mods else { float flowChangeOffset = 0; - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0015f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - - if (rng.NextDouble() < 0.8) - invertFlow = true; - } - - if (invertFlow) flowDirection = !flowDirection; + } positionInfos[i].RelativeAngle = getRelativeTargetAngle( positionInfos[i].DistanceFromPrevious, @@ -94,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(3 / (1 + 200 * Math.Exp(0.016 * (targetDistance - 466))) + 0.45 + offset); + float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 320))) + 0.5 + offset); float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } From b2e7da5aa06189bf52e0f5e38bf7cb9bd8b6a712 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 28 Jul 2022 18:37:12 +0200 Subject: [PATCH 012/174] Add basic Queue based implementation of KPS --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 10 ++ .../Screens/Play/HUD/KeysPerSecondCounter.cs | 125 ++++++++++++++++++ osu.Game/Screens/Play/KeyCounter.cs | 2 + 3 files changed, 137 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs new file mode 100644 index 0000000000..451e297e05 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneKeysPerSecondCounter + { + + } +} diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs new file mode 100644 index 0000000000..dc9a51dbf3 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs @@ -0,0 +1,125 @@ +// 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.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Screens.Play.HUD +{ + public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable + { + private static Queue? timestamps; + + private static event Action? onNewInput; + private readonly TimeSpan refreshSpan = TimeSpan.FromSeconds(1); + + private const float alpha_when_invalid = 0.3f; + private readonly Bindable valid = new Bindable(); + + public static void AddTimestamp() + { + timestamps?.Enqueue(DateTime.Now); + onNewInput?.Invoke(); + } + + protected override double RollingDuration => 250; + + public bool UsesFixedAnchor { get; set; } + + public KeysPerSecondCounter() + { + timestamps ??= new Queue(); + Current.Value = 0; + onNewInput += updateCounter; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + valid.BindValueChanged(e => + DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateCounter(); + } + + protected override void Update() + { + if (timestamps != null) + { + if (timestamps.TryPeek(out var earliest) && DateTime.Now - earliest >= refreshSpan) + timestamps.Dequeue(); + } + + updateCounter(); + + base.Update(); + } + + private void updateCounter() + { + valid.Value = timestamps != null; + Current.Value = timestamps?.Count ?? 0; + } + + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid + }; + + private class TextComponent : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + private readonly OsuSpriteText text; + + public TextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), + Text = @"KPS", + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better + } + } + }; + } + } + } +} diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 1e5ada5295..b8bbac9a7e 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -55,6 +56,7 @@ namespace osu.Game.Screens.Play public void Increment() { + KeysPerSecondCounter.AddTimestamp(); if (!IsCounting) return; From 079150849a53ee3545a8b58c137bb7648fdb4b3d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 28 Jul 2022 18:37:50 +0200 Subject: [PATCH 013/174] Add some tests --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 69 ++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 451e297e05..e20a83b54a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -1,10 +1,75 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Input; + namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter + public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene { - + private KeysPerSecondCounter counter; + + [SetUpSteps] + public void Setup() + { + createCounter(); + } + + private void createCounter() => AddStep("Create counter", () => + { + Child = counter = new KeysPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5) + }; + }); + + [Test] + public void TestManualTrigger() + { + AddAssert("Counter = 0", () => counter.Current.Value == 0); + AddRepeatStep("manual trigger", KeysPerSecondCounter.AddTimestamp, 20); + AddAssert("Counter is not 0", () => counter.Current.Value > 0); + } + + [Test] + public void TestKpsAsideKeyCounter() + { + AddStep("Create key counter display", () => + Add(new KeyCounterDisplay + { + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + Y = 100, + Children = new KeyCounter[] + { + new KeyCounterKeyboard(Key.W), + new KeyCounterKeyboard(Key.X), + new KeyCounterKeyboard(Key.C), + new KeyCounterKeyboard(Key.V) + } + }) + ); + AddAssert("Counter = 0", () => counter.Current.Value == 0); + addPressKeyStep(Key.W); + addPressKeyStep(Key.X); + addPressKeyStep(Key.C); + addPressKeyStep(Key.V); + AddAssert("Counter = 4", () => counter.Current.Value == 4); + } + + private void addPressKeyStep(Key key) + { + AddStep($"Press {key} key", () => InputManager.Key(key)); + } } } From 89855cc1d6af208b5e132264e610b16a2386c8c4 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 31 Jul 2022 01:29:57 +0200 Subject: [PATCH 014/174] Change KPS Counter implementation base and add better replay integration The counter implementaiton is now list based, and will not invalidate previous hits by removing them but by testing if they are within the 1 second span, allowing better integration with replays and spectators. --- .../Screens/Play/HUD/KeysPerSecondCounter.cs | 67 ++++++++++++++----- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 + osu.Game/Screens/Play/Player.cs | 4 ++ 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs index dc9a51dbf3..d32ca1410a 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs @@ -1,17 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Logging; +using osu.Framework.Timing; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -19,37 +26,71 @@ namespace osu.Game.Screens.Play.HUD { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { - private static Queue? timestamps; + private static List timestamps; + private static double maxTime = double.NegativeInfinity; - private static event Action? onNewInput; - private readonly TimeSpan refreshSpan = TimeSpan.FromSeconds(1); + private static event Action onNewInput; + private const int invalidation_timeout = 1000; private const float alpha_when_invalid = 0.3f; + private readonly Bindable valid = new Bindable(); + private static GameplayClock gameplayClock; + private static IClock referenceClock; + + private static IClock clock => referenceClock ?? gameplayClock; + + [Resolved(canBeNull: true)] + private DrawableRuleset drawableRuleset { get; set; } + + [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] + public BindableNumber SmoothingTime { get; } = new BindableNumber(350) + { + MaxValue = 1000, + MinValue = 0 + }; + public static void AddTimestamp() { - timestamps?.Enqueue(DateTime.Now); + Logger.Log($"Input timestamp attempt C: {clock.CurrentTime}ms | GC: {gameplayClock.CurrentTime} | RC: {referenceClock?.CurrentTime ?? -1} | Max: {maxTime})", level: LogLevel.Debug); + + if (clock.CurrentTime >= maxTime) + { + Logger.Log("Input timestamp added.", level: LogLevel.Debug); + timestamps?.Add(clock.CurrentTime); + maxTime = timestamps?.Max() ?? clock.CurrentTime; + } + onNewInput?.Invoke(); } - protected override double RollingDuration => 250; + public static void Reset() + { + timestamps?.Clear(); + maxTime = int.MinValue; + } + + protected override double RollingDuration => SmoothingTime.Value; public bool UsesFixedAnchor { get; set; } public KeysPerSecondCounter() { - timestamps ??= new Queue(); + timestamps ??= new List(); Current.Value = 0; onNewInput += updateCounter; + Scheduler.AddOnce(updateCounter); } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, GameplayClock clock) { + gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); + referenceClock = drawableRuleset?.FrameStableClock; } protected override void LoadComplete() @@ -61,21 +102,15 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - if (timestamps != null) - { - if (timestamps.TryPeek(out var earliest) && DateTime.Now - earliest >= refreshSpan) - timestamps.Dequeue(); - } + base.Update(); updateCounter(); - - base.Update(); } private void updateCounter() { - valid.Value = timestamps != null; - Current.Value = timestamps?.Count ?? 0; + valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500); + Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index b6094726c0..aaf2e997f2 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -65,6 +66,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { + KeysPerSecondCounter.Reset(); config.BindWith(OsuSetting.KeyOverlay, configVisibility); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9a058e45c5..fb2f556611 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1044,6 +1045,9 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); fadeOut(); + + KeysPerSecondCounter.Reset(); + return base.OnExiting(e); } From bc059cc1d22ef01c4ec10aa6b58a1249febb1fdf Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 1 Aug 2022 21:46:01 +0100 Subject: [PATCH 015/174] Implemented KeepUpright --- osu.Game/Extensions/DrawableExtensions.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 35f2d61437..5b92600cd1 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -8,6 +8,7 @@ using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; +using System; namespace osu.Game.Extensions { @@ -79,5 +80,24 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } + + /// + /// Keeps the drawable upright no matter the Rotation of its parents. + /// + /// The drawable. + public static void KeepUpright(this Drawable drawable) + { + var result = drawable.Parent.DrawInfo; + var scale = result.Matrix.ExtractScale(); + var rotation = new Matrix3( + result.Matrix.Row0 / scale.X, + result.Matrix.Row1 / scale.Y, + new Vector3(0.0f, 0.0f, 1.0f) + ); + float angle = MathF.Atan2(rotation.M12, rotation.M11); + angle *= (360 / (2 * MathF.PI)); + drawable.Rotation = -angle; + } + } } From df85bd74d7ab9eabd2c51b283ab0e15bafa25edc Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 1 Aug 2022 21:46:37 +0100 Subject: [PATCH 016/174] Keep TextSprites in SongProgressInfo upright --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 8f10e84509..520d0c661d 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -48,38 +48,67 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - timeCurrent = new OsuSpriteText + new Container { - Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - Margin = new MarginPadding + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Left = margin, - }, + timeCurrent = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } }, - progress = new OsuSpriteText + new Container { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + progress = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } }, - timeLeft = new OsuSpriteText + new Container { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - Margin = new MarginPadding + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Right = margin, - }, + timeLeft = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + Margin = new MarginPadding + { + Right = margin, + }, + } + } } }; } + protected override void LoadComplete() + { + base.LoadComplete(); + keepTextSpritesUpright(); + } + protected override void Update() { base.Update(); @@ -106,5 +135,13 @@ namespace osu.Game.Screens.Play.HUD } private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; + + private void keepTextSpritesUpright() + { + timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + } + } } From 78a98cdb9c23ef9b0827d77adfc8a07c571308bd Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 2 Aug 2022 17:03:02 +0100 Subject: [PATCH 017/174] Prevent TextSprites inside SongProgressInfo from being stretched or flipped --- osu.Game/Extensions/DrawableExtensions.cs | 30 ++++++++++++++++--- .../Screens/Play/HUD/DefaultSongProgress.cs | 5 ++-- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 7 +++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 5b92600cd1..46105218c5 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -82,11 +82,12 @@ namespace osu.Game.Extensions } /// - /// Keeps the drawable upright no matter the Rotation of its parents. + /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// /// The drawable. - public static void KeepUpright(this Drawable drawable) + public static void KeepUprightAndUnstretched(this Drawable drawable) { + // Fix the rotation var result = drawable.Parent.DrawInfo; var scale = result.Matrix.ExtractScale(); var rotation = new Matrix3( @@ -94,9 +95,30 @@ namespace osu.Game.Extensions result.Matrix.Row1 / scale.Y, new Vector3(0.0f, 0.0f, 1.0f) ); - float angle = MathF.Atan2(rotation.M12, rotation.M11); + rotation.Invert(); + float angle = MathF.Atan(rotation.M12 / rotation.M11); angle *= (360 / (2 * MathF.PI)); - drawable.Rotation = -angle; + drawable.Rotation = angle; + + // Fix the scale (includes flip) + var containerOriginToSpaceOrigin = new Matrix3( + new Vector3(1.0f, 0.0f, 0.0f), + new Vector3(0.0f, 1.0f, 0.0f), + new Vector3(drawable.DrawSize.X / 2, drawable.DrawSize.Y / 2, 1.0f) + ); + var containerOriginToSpaceOriginInverse = containerOriginToSpaceOrigin; + containerOriginToSpaceOriginInverse.Invert(); + Matrix3 rotatedBack = (containerOriginToSpaceOriginInverse * (rotation * (containerOriginToSpaceOrigin * result.Matrix))); + + bool xFliped = rotation.M11 < 0; + bool yFliped = rotation.M22 < 0; + + var rotatedBackScale = rotatedBack.ExtractScale(); + + drawable.Scale = new Vector2( + (xFliped ? -1 : 1) / rotatedBackScale.X, + (yFliped ? -1 : 1) / rotatedBackScale.Y + ); } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 96a6c56860..9ed99ab5f6 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -16,7 +17,6 @@ namespace osu.Game.Screens.Play.HUD { public class DefaultSongProgress : SongProgress { - private const float info_height = 20; private const float bottom_bar_height = 5; private const float graph_height = SquareGraph.Column.WIDTH * 6; private const float handle_height = 18; @@ -67,7 +67,6 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = info_height, }, graph = new SongProgressGraph { @@ -180,7 +179,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { base.Update(); - Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y; + Height = bottom_bar_height + graph_height + handle_size.Y + info.Height - graph.Y; } private void updateBarVisibility() diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 520d0c661d..656498fc43 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Play.HUD if (clock != null) gameplayClock = clock; + AutoSizeAxes = Axes.Y; Children = new Drawable[] { new Container @@ -138,9 +139,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; - progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; - timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUpright(timeCurrent); }; + timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; } } From bc21a2ed569a443bd03fb5e230aebc280fa1bcc1 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 2 Aug 2022 17:41:17 +0100 Subject: [PATCH 018/174] Remove unnecessary using directive --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 9ed99ab5f6..361e35ed45 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; From 42d1bdfc95fe78555fd5ce0426bc79b6a4d6f736 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 04:17:01 +0200 Subject: [PATCH 019/174] Move KPS calculation to a standalone class --- .../Gameplay/TestSceneKeyPerSecondCounter.cs | 91 +++++++++++++++++++ .../Gameplay/TestSceneKeysPerSecondCounter.cs | 4 +- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 91 +++++++++++++++++++ .../{ => KPSCounter}/KeysPerSecondCounter.cs | 63 ++----------- osu.Game/Screens/Play/KeyCounter.cs | 4 +- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 - osu.Game/Screens/Play/Player.cs | 4 +- 7 files changed, 195 insertions(+), 64 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs create mode 100644 osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs rename osu.Game/Screens/Play/HUD/{ => KPSCounter}/KeysPerSecondCounter.cs (63%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs new file mode 100644 index 0000000000..a2eaea29eb --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneKeyPerSecondCounter : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override bool HasCustomSteps => false; + protected override bool Autoplay => true; + + private GameplayClock gameplayClock; + private DrawableRuleset drawableRuleset; + + // private DependencyProvidingContainer dependencyContainer; + private KeysPerSecondCounter counter; + + [SetUpSteps] + public new void SetUpSteps() + { + /* + CreateTest(() => AddStep("Create components", () => + { + Logger.Log($"{(Player != null ? Player.ToString() : "null")}", level: LogLevel.Debug); + dependencyContainer = new DependencyProvidingContainer + { + RelativePositionAxes = Axes.Both, + }; + })); + */ + } + + private void createCounter() + { + AddStep("Create counter", () => + { + /* + if (!Contains(dependencyContainer)) + { + Add(dependencyContainer); + } + + if (dependencyContainer.CachedDependencies.Length == 0) + { + dependencyContainer.CachedDependencies = new (Type, object)[] + { + (typeof(GameplayClock), , + (typeof(DrawableRuleset),) + }; + } + Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock)); + */ + + Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock); + Dependencies.Cache(drawableRuleset = Player.DrawableRuleset); + + Add(counter = new KeysPerSecondCounter + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Scale = new Vector2(5), + Position = new Vector2(10, 100) + } + ); + }); + AddAssert("ensure counter added", () => Contains(counter)); + } + + [Test] + public void TestInGameTimeConsistency() + { + createCounter(); + + AddUntilStep("Wait until first note", () => counter.Current.Value != 0); + AddStep("Pause gameplay", () => gameplayClock.IsPaused.Value = true); + AddAssert("KPS = 1", () => counter.Current.Value == 1); + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index e20a83b54a..c8c31d1366 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -7,7 +7,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Input; @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestManualTrigger() { AddAssert("Counter = 0", () => counter.Current.Value == 0); - AddRepeatStep("manual trigger", KeysPerSecondCounter.AddTimestamp, 20); + AddRepeatStep("manual trigger", KeysPerSecondCalculator.AddInput, 20); AddAssert("Counter is not 0", () => counter.Current.Value > 0); } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs new file mode 100644 index 0000000000..f9839abde4 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Screens.Play.HUD.KPSCounter +{ + public class KeysPerSecondCalculator : IDisposable + { + private static KeysPerSecondCalculator instance; + + public static void AddInput() + { + instance?.onNewInput.Invoke(); + } + + public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) + { + if (instance != null) return instance; + + try + { + return new KeysPerSecondCalculator(gameplayClock, drawableRuleset); + } + catch (ArgumentNullException) + { + return null; + } + } + + private readonly List timestamps; + private readonly GameplayClock gameplayClock; + private readonly DrawableRuleset drawableRuleset; + + private event Action onNewInput; + + private IClock workingClock => (IClock)drawableRuleset.FrameStableClock ?? gameplayClock; + + // Having the rate from mods is preffered to using GameplayClock.TrueGameplayRate() + // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. + private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value + ?? 1; + + private double maxTime = double.NegativeInfinity; + + public bool Ready => workingClock != null && gameplayClock != null; + public int Value => timestamps.Count(isTimestampWithinSpan); + + private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset) + { + instance = this; + timestamps = new List(); + this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock)); + this.drawableRuleset = drawableRuleset; + onNewInput += addTimestamp; + } + + private void addTimestamp() + { + if (workingClock != null && workingClock.CurrentTime >= maxTime) + { + timestamps.Add(workingClock.CurrentTime); + maxTime = workingClock.CurrentTime; + } + } + + private bool isTimestampWithinSpan(double timestamp) + { + if (!Ready) + return false; + + double span = 1000 * rate; + double relativeTime = workingClock.CurrentTime - timestamp; + return relativeTime >= 0 && relativeTime <= span; + } + + public void Dispose() + { + instance = null; + } + + ~KeysPerSecondCalculator() => Dispose(); + } +} diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs similarity index 63% rename from osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index d32ca1410a..2fcca2ffce 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -3,17 +3,12 @@ #nullable disable -using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Logging; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,28 +17,20 @@ using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Screens.Play.HUD.KPSCounter { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { - private static List timestamps; - private static double maxTime = double.NegativeInfinity; - - private static event Action onNewInput; - - private const int invalidation_timeout = 1000; private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - - private static GameplayClock gameplayClock; - private static IClock referenceClock; - - private static IClock clock => referenceClock ?? gameplayClock; + private GameplayClock gameplayClock; [Resolved(canBeNull: true)] private DrawableRuleset drawableRuleset { get; set; } + private KeysPerSecondCalculator calculator => KeysPerSecondCalculator.GetInstance(gameplayClock, drawableRuleset); + [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] public BindableNumber SmoothingTime { get; } = new BindableNumber(350) { @@ -51,66 +38,30 @@ namespace osu.Game.Screens.Play.HUD MinValue = 0 }; - public static void AddTimestamp() - { - Logger.Log($"Input timestamp attempt C: {clock.CurrentTime}ms | GC: {gameplayClock.CurrentTime} | RC: {referenceClock?.CurrentTime ?? -1} | Max: {maxTime})", level: LogLevel.Debug); - - if (clock.CurrentTime >= maxTime) - { - Logger.Log("Input timestamp added.", level: LogLevel.Debug); - timestamps?.Add(clock.CurrentTime); - maxTime = timestamps?.Max() ?? clock.CurrentTime; - } - - onNewInput?.Invoke(); - } - - public static void Reset() - { - timestamps?.Clear(); - maxTime = int.MinValue; - } - protected override double RollingDuration => SmoothingTime.Value; public bool UsesFixedAnchor { get; set; } public KeysPerSecondCounter() { - timestamps ??= new List(); Current.Value = 0; - onNewInput += updateCounter; - Scheduler.AddOnce(updateCounter); } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameplayClock clock) + private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset) { gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); - referenceClock = drawableRuleset?.FrameStableClock; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateCounter(); } protected override void Update() { base.Update(); - updateCounter(); - } - - private void updateCounter() - { - valid.Value = timestamps != null && MathHelper.ApproximatelyEquivalent(gameplayClock.CurrentTime, referenceClock.CurrentTime, 500); - Current.Value = timestamps?.Count(timestamp => clock.CurrentTime - timestamp is >= 0 and <= invalidation_timeout) ?? 0; + valid.Value = calculator.Ready; + Current.Value = calculator.Value; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index b8bbac9a7e..044c9ee24e 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Graphics; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play public void Increment() { - KeysPerSecondCounter.AddTimestamp(); + KeysPerSecondCalculator.AddInput(); if (!IsCounting) return; diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index aaf2e997f2..b6094726c0 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Configuration; -using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Graphics; @@ -66,7 +65,6 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - KeysPerSecondCounter.Reset(); config.BindWith(OsuSetting.KeyOverlay, configVisibility); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index fb2f556611..88cd197076 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1046,7 +1046,7 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCounter.Reset(); + KeysPerSecondCalculator.GetInstance().Dispose(); return base.OnExiting(e); } From 15fb4d8dd5f3b8ba0b53e34882ef14164c387aa6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:53:14 +0100 Subject: [PATCH 020/174] Change Implementation and name of KeepUprightAndUnstretched --- osu.Game/Extensions/DrawableExtensions.cs | 56 +++++++++++------------ 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 46105218c5..fd5bbab567 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -81,45 +81,43 @@ namespace osu.Game.Extensions } } + /// /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// /// The drawable. - public static void KeepUprightAndUnstretched(this Drawable drawable) + public static void KeepUprightAndUnscaled(this Drawable drawable) { - // Fix the rotation - var result = drawable.Parent.DrawInfo; - var scale = result.Matrix.ExtractScale(); - var rotation = new Matrix3( - result.Matrix.Row0 / scale.X, - result.Matrix.Row1 / scale.Y, - new Vector3(0.0f, 0.0f, 1.0f) - ); - rotation.Invert(); - float angle = MathF.Atan(rotation.M12 / rotation.M11); + var parentMatrix = drawable.Parent.DrawInfo.Matrix; + float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); angle *= (360 / (2 * MathF.PI)); - drawable.Rotation = angle; - // Fix the scale (includes flip) - var containerOriginToSpaceOrigin = new Matrix3( - new Vector3(1.0f, 0.0f, 0.0f), - new Vector3(0.0f, 1.0f, 0.0f), - new Vector3(drawable.DrawSize.X / 2, drawable.DrawSize.Y / 2, 1.0f) - ); - var containerOriginToSpaceOriginInverse = containerOriginToSpaceOrigin; - containerOriginToSpaceOriginInverse.Invert(); - Matrix3 rotatedBack = (containerOriginToSpaceOriginInverse * (rotation * (containerOriginToSpaceOrigin * result.Matrix))); + parentMatrix.Transpose(); + parentMatrix.M13 = 0.0f; + parentMatrix.M23 = 0.0f; - bool xFliped = rotation.M11 < 0; - bool yFliped = rotation.M22 < 0; + if ((Math.Abs(Math.Abs(angle) - 90.0)) < 2.0f) + { + Matrix3 m = Matrix3.CreateRotationZ(MathHelper.DegreesToRadians(40.0f)); + m.Transpose(); + parentMatrix *= m; + drawable.Rotation = 40.0f; + } + else + drawable.Rotation = 0.0f; - var rotatedBackScale = rotatedBack.ExtractScale(); + Matrix3 C = parentMatrix.Inverted(); + + float alpha, beta, sx, sy; + sy = C.M22; + alpha = C.M12 / C.M22; + + beta = (C.M21 == 0.0f) ? 0.0f : 1 / ((C.M11 / C.M21) - alpha); + sx = (beta == 0.0f) ? C.M11 : C.M21 / beta; + + drawable.Scale = new Vector2(sx, sy); + drawable.Shear = new Vector2(-alpha, -beta); - drawable.Scale = new Vector2( - (xFliped ? -1 : 1) / rotatedBackScale.X, - (yFliped ? -1 : 1) / rotatedBackScale.Y - ); } - } } From 8618d9ea0d21ea9f67d9f2f3751c3b7cda08f614 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:55:41 +0100 Subject: [PATCH 021/174] Implement GrowToFitContainer --- .../Graphics/Containers/GrowToFitContainer.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 osu.Game/Graphics/Containers/GrowToFitContainer.cs diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs new file mode 100644 index 0000000000..6333718928 --- /dev/null +++ b/osu.Game/Graphics/Containers/GrowToFitContainer.cs @@ -0,0 +1,22 @@ +// 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.Containers; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that grows in size to fit its child and retains its size when its child shrinks + /// + public class GrowToFitContainer : Container + { + protected override void Update() + { + base.Update(); + Height = Math.Max(Child.Height, Height); + Width = Math.Max(Child.Width, Width); + } + } + +} From 12ef99a1a1534c255138eab4c336f385e145c144 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 12:56:08 +0100 Subject: [PATCH 022/174] Fix text position --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 656498fc43..8c9f3aaa8d 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -5,15 +5,22 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System; + namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { + private GrowToFitContainer timeCurrentContainer; + private GrowToFitContainer timeLeftContainer; + private GrowToFitContainer progressContainer; + private OsuSpriteText timeCurrent; private OsuSpriteText timeLeft; private OsuSpriteText progress; @@ -51,12 +58,14 @@ namespace osu.Game.Screens.Play.HUD { new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = timeCurrentContainer = new GrowToFitContainer { - timeCurrent = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -67,37 +76,37 @@ namespace osu.Game.Screens.Play.HUD }, new Container { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = timeLeftContainer = new GrowToFitContainer { - progress = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = progress = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, Colour = colours.BlueLighter, Font = OsuFont.Numeric, - } + } } }, new Container { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomRight, + Origin = Anchor.CentreRight, + Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Child = progressContainer = new GrowToFitContainer { - timeLeft = new OsuSpriteText + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, Colour = colours.BlueLighter, Font = OsuFont.Numeric, - Margin = new MarginPadding - { - Right = margin, - }, } } } @@ -139,9 +148,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrent.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; - progress.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; - timeLeft.OnUpdate += (timeCurrent) => { Extensions.DrawableExtensions.KeepUprightAndUnstretched(timeCurrent); }; + timeCurrentContainer.OnUpdate += (timeCurrentContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeCurrentContainer); }; + progressContainer.OnUpdate += (progressContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(progressContainer); }; + timeLeftContainer.OnUpdate += (timeLeftContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeLeftContainer); }; } } From 0243f8d6ac0f97716f017468075c6a0ede2aa220 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 14:28:15 +0100 Subject: [PATCH 023/174] Clean up --- osu.Game/Extensions/DrawableExtensions.cs | 4 +--- osu.Game/Graphics/Containers/GrowToFitContainer.cs | 1 - osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 8 +++----- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index fd5bbab567..f587b1c55b 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -81,7 +81,6 @@ namespace osu.Game.Extensions } } - /// /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. /// @@ -90,7 +89,7 @@ namespace osu.Game.Extensions { var parentMatrix = drawable.Parent.DrawInfo.Matrix; float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); - angle *= (360 / (2 * MathF.PI)); + angle = MathHelper.RadiansToDegrees(angle); parentMatrix.Transpose(); parentMatrix.M13 = 0.0f; @@ -117,7 +116,6 @@ namespace osu.Game.Extensions drawable.Scale = new Vector2(sx, sy); drawable.Shear = new Vector2(-alpha, -beta); - } } } diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs index 6333718928..9b4ad0dba9 100644 --- a/osu.Game/Graphics/Containers/GrowToFitContainer.cs +++ b/osu.Game/Graphics/Containers/GrowToFitContainer.cs @@ -18,5 +18,4 @@ namespace osu.Game.Graphics.Containers Width = Math.Max(Child.Width, Width); } } - } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 8c9f3aaa8d..34f773509a 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -148,10 +147,9 @@ namespace osu.Game.Screens.Play.HUD private void keepTextSpritesUpright() { - timeCurrentContainer.OnUpdate += (timeCurrentContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeCurrentContainer); }; - progressContainer.OnUpdate += (progressContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(progressContainer); }; - timeLeftContainer.OnUpdate += (timeLeftContainer) => { Extensions.DrawableExtensions.KeepUprightAndUnscaled(timeLeftContainer); }; + timeCurrentContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; + progressContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; + timeLeftContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; } - } } From 24c29b7e2f5d6ec717a056db1149099c1632df46 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 15:51:07 +0200 Subject: [PATCH 024/174] Do not add KPS calculation when gameplay rate is 0 --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index f9839abde4..5ac3647e0e 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private void addTimestamp() { - if (workingClock != null && workingClock.CurrentTime >= maxTime) + if (workingClock != null && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; From b4e0fa7c53cf3d1a8d580562c8301a6e70d4d2bc Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 15:53:06 +0200 Subject: [PATCH 025/174] Rewrite tests for KPS --- .../Gameplay/TestSceneKeyPerSecondCounter.cs | 91 -------------- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 114 ++++++++++-------- 2 files changed, 66 insertions(+), 139 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs deleted file mode 100644 index a2eaea29eb..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyPerSecondCounter.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Testing; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; -using osuTK; - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestSceneKeyPerSecondCounter : PlayerTestScene - { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - protected override bool HasCustomSteps => false; - protected override bool Autoplay => true; - - private GameplayClock gameplayClock; - private DrawableRuleset drawableRuleset; - - // private DependencyProvidingContainer dependencyContainer; - private KeysPerSecondCounter counter; - - [SetUpSteps] - public new void SetUpSteps() - { - /* - CreateTest(() => AddStep("Create components", () => - { - Logger.Log($"{(Player != null ? Player.ToString() : "null")}", level: LogLevel.Debug); - dependencyContainer = new DependencyProvidingContainer - { - RelativePositionAxes = Axes.Both, - }; - })); - */ - } - - private void createCounter() - { - AddStep("Create counter", () => - { - /* - if (!Contains(dependencyContainer)) - { - Add(dependencyContainer); - } - - if (dependencyContainer.CachedDependencies.Length == 0) - { - dependencyContainer.CachedDependencies = new (Type, object)[] - { - (typeof(GameplayClock), , - (typeof(DrawableRuleset),) - }; - } - Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock)); - */ - - Dependencies.Cache(gameplayClock = Player.GameplayClockContainer.GameplayClock); - Dependencies.Cache(drawableRuleset = Player.DrawableRuleset); - - Add(counter = new KeysPerSecondCounter - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Scale = new Vector2(5), - Position = new Vector2(10, 100) - } - ); - }); - AddAssert("ensure counter added", () => Contains(counter)); - } - - [Test] - public void TestInGameTimeConsistency() - { - createCounter(); - - AddUntilStep("Wait until first note", () => counter.Current.Value != 0); - AddStep("Pause gameplay", () => gameplayClock.IsPaused.Value = true); - AddAssert("KPS = 1", () => counter.Current.Value == 1); - } - } -} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index c8c31d1366..0cc9b91d71 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -3,9 +3,15 @@ #nullable disable +using System.Linq; +using AutoMapper.Internal; using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; -using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; @@ -13,63 +19,75 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene + public class TestSceneKeysPerSecondCounter : PlayerTestScene { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override bool HasCustomSteps => false; + protected override bool Autoplay => false; + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); + + private GameplayClock gameplayClock; + private DrawableRuleset drawableRuleset; + private KeysPerSecondCounter counter; - [SetUpSteps] - public void Setup() + private void createCounter() + { + AddStep("Create counter", () => + { + gameplayClock = Player.GameplayClockContainer.GameplayClock; + drawableRuleset = Player.DrawableRuleset; + + Player.HUDOverlay.Add(counter = new KeysPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + }); + counter.SmoothingTime.Value = 0; + }); + AddUntilStep("Counter created", () => Player.HUDOverlay.Contains(counter)); + } + + [Test] + public void TestBasic() { createCounter(); - } - private void createCounter() => AddStep("Create counter", () => - { - Child = counter = new KeysPerSecondCounter + AddStep("press 1 key", () => InputManager.Key(Key.D)); + AddAssert("KPS = 1", () => counter.Current.Value == 1); + AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); + AddStep("press 4 keys", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5) - }; - }); - - [Test] - public void TestManualTrigger() - { - AddAssert("Counter = 0", () => counter.Current.Value == 0); - AddRepeatStep("manual trigger", KeysPerSecondCalculator.AddInput, 20); - AddAssert("Counter is not 0", () => counter.Current.Value > 0); - } - - [Test] - public void TestKpsAsideKeyCounter() - { - AddStep("Create key counter display", () => - Add(new KeyCounterDisplay + InputManager.Key(Key.D); + InputManager.Key(Key.F); + InputManager.Key(Key.J); + InputManager.Key(Key.K); + }); + AddAssert("KPS = 4", () => counter.Current.Value == 4); + AddStep("Pause player", () => Player.Pause()); + AddAssert("KPS = 4", () => counter.Current.Value == 4); + AddStep("Resume player", () => Player.Resume()); + AddStep("press 4 keys", () => + { + InputManager.Key(Key.D); + InputManager.Key(Key.F); + InputManager.Key(Key.J); + InputManager.Key(Key.K); + }); + AddAssert("KPS = 8", () => counter.Current.Value == 8); + AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); + AddStep("Add DT", () => + { + var dt = new ManiaModDoubleTime { - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Y = 100, - Children = new KeyCounter[] + SpeedChange = { - new KeyCounterKeyboard(Key.W), - new KeyCounterKeyboard(Key.X), - new KeyCounterKeyboard(Key.C), - new KeyCounterKeyboard(Key.V) + Value = 2 } - }) - ); - AddAssert("Counter = 0", () => counter.Current.Value == 0); - addPressKeyStep(Key.W); - addPressKeyStep(Key.X); - addPressKeyStep(Key.C); - addPressKeyStep(Key.V); - AddAssert("Counter = 4", () => counter.Current.Value == 4); - } - - private void addPressKeyStep(Key key) - { - AddStep($"Press {key} key", () => InputManager.Key(key)); + }; + Player.Mods.Value.Concat((dt.Yield()).ToArray()); + }); } } } From b46bc5d65b9e4441bb3dc4509f4ff328317f22b6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 5 Aug 2022 14:57:33 +0100 Subject: [PATCH 026/174] Remove empty line --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 34f773509a..e9d0ffe4b9 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -11,7 +11,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using System; - namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container From 0c07df2c267c36fb1c5ac5cf4dae095b94d01521 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 17:04:33 +0200 Subject: [PATCH 027/174] Remove DT from KPS test --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 0cc9b91d71..6e59c53a1f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -3,14 +3,10 @@ #nullable disable -using System.Linq; -using AutoMapper.Internal; using NUnit.Framework; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets; using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.KPSCounter; @@ -76,18 +72,6 @@ namespace osu.Game.Tests.Visual.Gameplay InputManager.Key(Key.K); }); AddAssert("KPS = 8", () => counter.Current.Value == 8); - AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); - AddStep("Add DT", () => - { - var dt = new ManiaModDoubleTime - { - SpeedChange = - { - Value = 2 - } - }; - Player.Mods.Value.Concat((dt.Yield()).ToArray()); - }); } } } From 0886137e39b965571143387cd44b516b185fd189 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 5 Aug 2022 16:31:20 +0200 Subject: [PATCH 028/174] Prevent KeysPerSecondCounter from NRE when no instance is initialized --- .../Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 8 ++++---- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 5ac3647e0e..a29f4c9706 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter public static void AddInput() { - instance?.onNewInput.Invoke(); + instance?.onNewInput?.Invoke(); } public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) @@ -41,9 +41,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private event Action onNewInput; - private IClock workingClock => (IClock)drawableRuleset.FrameStableClock ?? gameplayClock; + private IClock workingClock => (IClock)drawableRuleset?.FrameStableClock ?? gameplayClock; - // Having the rate from mods is preffered to using GameplayClock.TrueGameplayRate() + // Having the rate from mods is preferred to using GameplayClock.TrueGameplayRate() // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value ?? 1; @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private void addTimestamp() { - if (workingClock != null && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) + if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 88cd197076..70b1dc9a41 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1046,7 +1046,7 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCalculator.GetInstance().Dispose(); + KeysPerSecondCalculator.GetInstance()?.Dispose(); return base.OnExiting(e); } From b2557a8d2d4ca1ecde0cc6b709085df912316ede Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 7 Aug 2022 00:53:00 +0200 Subject: [PATCH 029/174] Refactor KPS - Remove '#nullable disable' in KeysPerSecondCalculator and KeysPerSecondCounter - Remove KeysPerSecondCalculator IDisposable implementation - Make KeysPerSecondCalculator static instance initialized once by KeysPerSecondCounters - Auto transfer dependencies from KeysPerSecondCounter to KeysPerSecondCalculator using Resolved properties - Add internal reset logic to KeysPerSecondCalculator and make it independent from Player - Use GameplayClock.TrueGameplayRate to get real-time rate. If 0 then it defaults to the last non 0 rate if no such mod is enabled --- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 106 +++++++++++------- .../HUD/KPSCounter/KeysPerSecondCounter.cs | 33 +++--- osu.Game/Screens/Play/Player.cs | 3 - 3 files changed, 81 insertions(+), 61 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index a29f4c9706..3c0d585984 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -12,59 +10,93 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KPSCounter { - public class KeysPerSecondCalculator : IDisposable + public class KeysPerSecondCalculator { - private static KeysPerSecondCalculator instance; - public static void AddInput() { - instance?.onNewInput?.Invoke(); - } - - public static KeysPerSecondCalculator GetInstance(GameplayClock gameplayClock = null, DrawableRuleset drawableRuleset = null) - { - if (instance != null) return instance; - - try - { - return new KeysPerSecondCalculator(gameplayClock, drawableRuleset); - } - catch (ArgumentNullException) - { - return null; - } + onNewInput?.Invoke(); } private readonly List timestamps; - private readonly GameplayClock gameplayClock; - private readonly DrawableRuleset drawableRuleset; + private GameplayClock? gameplayClock; + private DrawableRuleset? drawableRuleset; - private event Action onNewInput; + public GameplayClock? GameplayClock + { + get => gameplayClock; + set + { + onResetRequested?.Invoke(); - private IClock workingClock => (IClock)drawableRuleset?.FrameStableClock ?? gameplayClock; + if (value != null) + { + gameplayClock = value; + } + } + } - // Having the rate from mods is preferred to using GameplayClock.TrueGameplayRate() - // as it returns 0 when paused in replays, not useful for players who want to "analyze" a replay. - private double rate => (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value + public DrawableRuleset? DrawableRuleset + { + get => drawableRuleset; + set + { + onResetRequested?.Invoke(); + + if (value != null) + { + drawableRuleset = value; + baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value ?? 1; + } + } + } + + private static event Action? onNewInput; + private static event Action? onResetRequested; + + private IClock? workingClock => drawableRuleset?.FrameStableClock; + + private double baseRate; + + private double rate + { + get + { + if (gameplayClock != null) + { + if (gameplayClock.TrueGameplayRate > 0) + { + baseRate = gameplayClock.TrueGameplayRate; + } + } + + return baseRate; + } + } private double maxTime = double.NegativeInfinity; public bool Ready => workingClock != null && gameplayClock != null; public int Value => timestamps.Count(isTimestampWithinSpan); - private KeysPerSecondCalculator(GameplayClock gameplayClock, DrawableRuleset drawableRuleset) + public KeysPerSecondCalculator() { - instance = this; timestamps = new List(); - this.gameplayClock = gameplayClock ?? throw new ArgumentNullException(nameof(gameplayClock)); - this.drawableRuleset = drawableRuleset; onNewInput += addTimestamp; + onResetRequested += cleanUp; + } + + private void cleanUp() + { + timestamps.Clear(); + maxTime = double.NegativeInfinity; } private void addTimestamp() { - if (Ready && workingClock.CurrentTime >= maxTime && gameplayClock.TrueGameplayRate > 0) + if (workingClock == null) return; + + if (workingClock.CurrentTime >= maxTime) { timestamps.Add(workingClock.CurrentTime); maxTime = workingClock.CurrentTime; @@ -73,19 +105,11 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private bool isTimestampWithinSpan(double timestamp) { - if (!Ready) - return false; + if (workingClock == null) return false; double span = 1000 * rate; double relativeTime = workingClock.CurrentTime - timestamp; return relativeTime >= 0 && relativeTime <= span; } - - public void Dispose() - { - instance = null; - } - - ~KeysPerSecondCalculator() => Dispose(); } } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index 2fcca2ffce..ad7b6c8f5c 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -24,21 +21,24 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private GameplayClock gameplayClock; + + private static readonly KeysPerSecondCalculator calculator = new KeysPerSecondCalculator(); + + [Resolved] + private GameplayClock? gameplayClock + { + get => calculator.GameplayClock; + set => calculator.GameplayClock = value; + } [Resolved(canBeNull: true)] - private DrawableRuleset drawableRuleset { get; set; } - - private KeysPerSecondCalculator calculator => KeysPerSecondCalculator.GetInstance(gameplayClock, drawableRuleset); - - [SettingSource("Smoothing time", "How smooth the counter should change\nThe more it is smooth, the less it's accurate.")] - public BindableNumber SmoothingTime { get; } = new BindableNumber(350) + private DrawableRuleset? drawableRuleset { - MaxValue = 1000, - MinValue = 0 - }; + get => calculator.DrawableRuleset; + set => calculator.DrawableRuleset = value; + } - protected override double RollingDuration => SmoothingTime.Value; + protected override double RollingDuration => 350; public bool UsesFixedAnchor { get; set; } @@ -48,9 +48,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameplayClock clock, DrawableRuleset ruleset) + private void load(OsuColour colours) { - gameplayClock = clock; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); @@ -61,7 +60,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter base.Update(); valid.Value = calculator.Ready; - Current.Value = calculator.Value; + Current.Value = calculator.Ready ? calculator.Value : 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 70b1dc9a41..e3844088e2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD.KPSCounter; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -1046,8 +1045,6 @@ namespace osu.Game.Screens.Play fadeOut(); - KeysPerSecondCalculator.GetInstance()?.Dispose(); - return base.OnExiting(e); } From 0bfa6fa975e5bb3e0510b2c71d9e62416f17bbf2 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 13:18:29 +0100 Subject: [PATCH 030/174] Implement UprightUnscaledContainer --- .../Graphics/Containers/UprightContainer.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 osu.Game/Graphics/Containers/UprightContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightContainer.cs b/osu.Game/Graphics/Containers/UprightContainer.cs new file mode 100644 index 0000000000..21035e82b9 --- /dev/null +++ b/osu.Game/Graphics/Containers/UprightContainer.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Layout; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. + /// + public class UprightUnscaledContainer : Container + { + public UprightUnscaledContainer() + { + AddLayout(layout); + } + + private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo); + + protected override void Update() + { + base.Update(); + if (!layout.IsValid) + { + Extensions.DrawableExtensions.KeepUprightAndUnscaled(this); + layout.Validate(); + } + } + } +} From ed86255e2b45f7e658b9a47975044652902df107 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 13:20:22 +0100 Subject: [PATCH 031/174] Use UprightUnscaledContainer instead of KeepUprightAndUnscaled --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index e9d0ffe4b9..069492fd80 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -15,10 +15,6 @@ namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { - private GrowToFitContainer timeCurrentContainer; - private GrowToFitContainer timeLeftContainer; - private GrowToFitContainer progressContainer; - private OsuSpriteText timeCurrent; private OsuSpriteText timeLeft; private OsuSpriteText progress; @@ -59,16 +55,22 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = timeCurrentContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeCurrent = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + Child = timeCurrent = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } } } }, @@ -77,17 +79,23 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = timeLeftContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = progress = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Child = progress = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } + } } }, new Container @@ -95,28 +103,28 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = progressContainer = new GrowToFitContainer + Child = new UprightUnscaledContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeLeft = new OsuSpriteText + AutoSizeAxes = Axes.Both, + Child = new GrowToFitContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, + Child = timeLeft = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, + } } } } }; } - protected override void LoadComplete() - { - base.LoadComplete(); - keepTextSpritesUpright(); - } - protected override void Update() { base.Update(); @@ -143,12 +151,5 @@ namespace osu.Game.Screens.Play.HUD } private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; - - private void keepTextSpritesUpright() - { - timeCurrentContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - progressContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - timeLeftContainer.OnUpdate += Extensions.DrawableExtensions.KeepUprightAndUnscaled; - } } } From cfd07cb366bb007b3936bc387eebd37857de1f9f Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Sun, 7 Aug 2022 15:04:11 +0100 Subject: [PATCH 032/174] Set InvalidationSource to parent and clean up --- .../{UprightContainer.cs => UprightUnscaledContainer.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game/Graphics/Containers/{UprightContainer.cs => UprightUnscaledContainer.cs} (95%) diff --git a/osu.Game/Graphics/Containers/UprightContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs similarity index 95% rename from osu.Game/Graphics/Containers/UprightContainer.cs rename to osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index 21035e82b9..cdb839fdec 100644 --- a/osu.Game/Graphics/Containers/UprightContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.Containers AddLayout(layout); } - private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo); + private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); protected override void Update() { From b52a07c16a6559b3661453f533783ae9abeb7a69 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 8 Aug 2022 21:27:46 +0200 Subject: [PATCH 033/174] Use DI to provide dependencies for KPS Calculator and improve input gathering KPS Calculator now uses DI to retrieve the clocks. Using `HUDOverlay` it is now cached for `KeysPerSecondCounter`s to resolve it. This also allows to make an "Attach" flow like `KeyCounter`. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 5 +- osu.Game/Rulesets/UI/RulesetInputManager.cs | 37 +++++++++- .../HUD/KPSCounter/KeysPerSecondCalculator.cs | 70 +++++++++---------- .../HUD/KPSCounter/KeysPerSecondCounter.cs | 22 +++--- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++- osu.Game/Screens/Play/KeyCounter.cs | 2 - 6 files changed, 90 insertions(+), 54 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index f7f62d2af0..b28e3355a4 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; namespace osu.Game.Rulesets.UI @@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter, ICanAttachKpsCalculator where TObject : HitObject { public override event Action NewResult; @@ -340,6 +341,8 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + public void Attach(KeysPerSecondCalculator kps) => (KeyBindingInputManager as ICanAttachKpsCalculator)?.Attach(kps); + /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. /// diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 7c37913576..23e64153eb 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,11 +20,12 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler, ICanAttachKpsCalculator where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -186,6 +187,35 @@ namespace osu.Game.Rulesets.UI #endregion + #region KPS Counter Attachment + + public void Attach(KeysPerSecondCalculator kps) + { + var listener = new ActionListener(); + + KeyBindingContainer.Add(listener); + + kps.Listener = listener; + } + + public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler + { + public override event Action OnNewInput; + + public bool OnPressed(KeyBindingPressEvent e) + { + OnNewInput?.Invoke(); + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + + #endregion + protected virtual KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); @@ -229,6 +259,11 @@ namespace osu.Game.Rulesets.UI void Attach(KeyCounterDisplay keyCounter); } + public interface ICanAttachKpsCalculator + { + void Attach(KeysPerSecondCalculator keysPerSecondCalculator); + } + public class RulesetInputManagerInputState : InputState where T : struct { diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 3c0d585984..96a6d5b8eb 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -4,55 +4,36 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KPSCounter { - public class KeysPerSecondCalculator + public class KeysPerSecondCalculator : Component { - public static void AddInput() - { - onNewInput?.Invoke(); - } - private readonly List timestamps; - private GameplayClock? gameplayClock; - private DrawableRuleset? drawableRuleset; - public GameplayClock? GameplayClock + private InputListener? listener; + + [Resolved] + private GameplayClock? gameplayClock { get; set; } + + [Resolved(canBeNull: true)] + private DrawableRuleset? drawableRuleset { get; set; } + + public InputListener Listener { - get => gameplayClock; set { onResetRequested?.Invoke(); - - if (value != null) - { - gameplayClock = value; - } + listener = value; + listener.OnNewInput += addTimestamp; } } - public DrawableRuleset? DrawableRuleset - { - get => drawableRuleset; - set - { - onResetRequested?.Invoke(); - - if (value != null) - { - drawableRuleset = value; - baseRate = (drawableRuleset.Mods.FirstOrDefault(m => m is ModRateAdjust) as ModRateAdjust)?.SpeedChange.Value - ?? 1; - } - } - } - - private static event Action? onNewInput; - private static event Action? onResetRequested; + private event Action? onResetRequested; private IClock? workingClock => drawableRuleset?.FrameStableClock; @@ -81,8 +62,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter public KeysPerSecondCalculator() { + RelativeSizeAxes = Axes.Both; timestamps = new List(); - onNewInput += addTimestamp; onResetRequested += cleanUp; } @@ -90,6 +71,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter { timestamps.Clear(); maxTime = double.NegativeInfinity; + + if (listener != null) + listener.OnNewInput -= addTimestamp; } private void addTimestamp() @@ -111,5 +95,21 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter double relativeTime = workingClock.CurrentTime - timestamp; return relativeTime >= 0 && relativeTime <= span; } + + ~KeysPerSecondCalculator() + { + cleanUp(); + } + + public abstract class InputListener : Component + { + protected InputListener() + { + RelativeSizeAxes = Axes.Both; + Depth = float.MinValue; + } + + public abstract event Action? OnNewInput; + } } } diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index ad7b6c8f5c..d6f1d19770 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -22,21 +22,15 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private readonly Bindable valid = new Bindable(); - private static readonly KeysPerSecondCalculator calculator = new KeysPerSecondCalculator(); - [Resolved] - private GameplayClock? gameplayClock - { - get => calculator.GameplayClock; - set => calculator.GameplayClock = value; - } + private KeysPerSecondCalculator? calculator { get; set; } + + // This is to force the skin editor to show the component only in a Gameplay context + [Resolved] + private GameplayClock? gameplayClock { get; set; } [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset - { - get => calculator.DrawableRuleset; - set => calculator.DrawableRuleset = value; - } + private DrawableRuleset? drawableRuleset { get; set; } protected override double RollingDuration => 350; @@ -59,8 +53,8 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter { base.Update(); - valid.Value = calculator.Ready; - Current.Value = calculator.Ready ? calculator.Value : 0; + valid.Value = calculator != null && calculator.Ready; + Current.Value = calculator != null ? calculator.Ready ? calculator.Value : 0 : 0; } protected override IHasText CreateText() => new TextComponent diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 8f80644d52..1c28e04950 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; +using osu.Game.Screens.Play.HUD.KPSCounter; using osu.Game.Skinning; using osuTK; @@ -49,6 +50,9 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + [Cached] + private readonly KeysPerSecondCalculator keysPerSecondCalculator; + public Bindable ShowHealthBar = new Bindable(true); private readonly DrawableRuleset drawableRuleset; @@ -122,7 +126,8 @@ namespace osu.Game.Screens.Play KeyCounter = CreateKeyCounter(), HoldToQuit = CreateHoldForMenuButton(), } - } + }, + keysPerSecondCalculator = new KeysPerSecondCalculator() }; } @@ -260,6 +265,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKpsCalculator)?.Attach(keysPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index 044c9ee24e..1e5ada5295 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Screens.Play.HUD.KPSCounter; using osuTK; using osuTK.Graphics; @@ -56,7 +55,6 @@ namespace osu.Game.Screens.Play public void Increment() { - KeysPerSecondCalculator.AddInput(); if (!IsCounting) return; From edb8e5e33e3649f0be60e8b4a53498ab200076a4 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Tue, 9 Aug 2022 02:43:41 +0200 Subject: [PATCH 034/174] Temporarily emptying `TestSceneKeysPerSecondCounter` until a good test can be found --- .../Gameplay/TestSceneKeysPerSecondCounter.cs | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs index 6e59c53a1f..8bc2eae1d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs @@ -1,77 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mania; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; -using osuTK; -using osuTK.Input; - namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecondCounter : PlayerTestScene + public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene { - protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); - protected override bool HasCustomSteps => false; - protected override bool Autoplay => false; - protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false); - - private GameplayClock gameplayClock; - private DrawableRuleset drawableRuleset; - - private KeysPerSecondCounter counter; - - private void createCounter() - { - AddStep("Create counter", () => - { - gameplayClock = Player.GameplayClockContainer.GameplayClock; - drawableRuleset = Player.DrawableRuleset; - - Player.HUDOverlay.Add(counter = new KeysPerSecondCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - }); - counter.SmoothingTime.Value = 0; - }); - AddUntilStep("Counter created", () => Player.HUDOverlay.Contains(counter)); - } - - [Test] - public void TestBasic() - { - createCounter(); - - AddStep("press 1 key", () => InputManager.Key(Key.D)); - AddAssert("KPS = 1", () => counter.Current.Value == 1); - AddUntilStep("Wait for KPS cooldown", () => counter.Current.Value <= 0); - AddStep("press 4 keys", () => - { - InputManager.Key(Key.D); - InputManager.Key(Key.F); - InputManager.Key(Key.J); - InputManager.Key(Key.K); - }); - AddAssert("KPS = 4", () => counter.Current.Value == 4); - AddStep("Pause player", () => Player.Pause()); - AddAssert("KPS = 4", () => counter.Current.Value == 4); - AddStep("Resume player", () => Player.Resume()); - AddStep("press 4 keys", () => - { - InputManager.Key(Key.D); - InputManager.Key(Key.F); - InputManager.Key(Key.J); - InputManager.Key(Key.K); - }); - AddAssert("KPS = 8", () => counter.Current.Value == 8); - } } } From 2367dc96107091207c468738a93f6ece62310f05 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Tue, 9 Aug 2022 13:06:11 +0100 Subject: [PATCH 035/174] Improved KeepUprightAndUnscaled --- osu.Game/Extensions/DrawableExtensions.cs | 36 +++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index f587b1c55b..320b9d7996 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -82,40 +82,38 @@ namespace osu.Game.Extensions } /// - /// Keeps the drawable upright and prevents it from being scaled or flipped with its Parent. + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// /// The drawable. public static void KeepUprightAndUnscaled(this Drawable drawable) { + // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. var parentMatrix = drawable.Parent.DrawInfo.Matrix; - float angle = MathF.Atan(parentMatrix.M12 / parentMatrix.M11); - angle = MathHelper.RadiansToDegrees(angle); - parentMatrix.Transpose(); + + // Remove Translation. parentMatrix.M13 = 0.0f; parentMatrix.M23 = 0.0f; - if ((Math.Abs(Math.Abs(angle) - 90.0)) < 2.0f) - { - Matrix3 m = Matrix3.CreateRotationZ(MathHelper.DegreesToRadians(40.0f)); - m.Transpose(); - parentMatrix *= m; - drawable.Rotation = 40.0f; - } - else - drawable.Rotation = 0.0f; - Matrix3 C = parentMatrix.Inverted(); - float alpha, beta, sx, sy; + // Extract the rotation. + float angle = MathF.Atan2(C.M21, C.M11); + drawable.Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + m.Transpose(); + C = m * C; + + // Extract shear and scale. + float alpha, sx, sy; + sx = C.M11; sy = C.M22; alpha = C.M12 / C.M22; - beta = (C.M21 == 0.0f) ? 0.0f : 1 / ((C.M11 / C.M21) - alpha); - sx = (beta == 0.0f) ? C.M11 : C.M21 / beta; - drawable.Scale = new Vector2(sx, sy); - drawable.Shear = new Vector2(-alpha, -beta); + drawable.Shear = new Vector2(-alpha, 0); } } } From 9e80d3f71c01a2e5789348a9237bc1d92c4b6c2b Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:42:22 +0200 Subject: [PATCH 036/174] Re-adjust timespan conditions in `KeysPerSecondCalculator` --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 96a6d5b8eb..890404a0aa 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter double span = 1000 * rate; double relativeTime = workingClock.CurrentTime - timestamp; - return relativeTime >= 0 && relativeTime <= span; + return relativeTime > 0 && relativeTime <= span; } ~KeysPerSecondCalculator() From 46e372cb99ba47a86f17418353086e95a4ddd3fd Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:43:15 +0200 Subject: [PATCH 037/174] Add more readiness checks in `KeysPerSecondCalculator` --- osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs index 890404a0aa..dddef9abaf 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private double maxTime = double.NegativeInfinity; - public bool Ready => workingClock != null && gameplayClock != null; + public bool Ready => workingClock != null && gameplayClock != null && listener != null; public int Value => timestamps.Count(isTimestampWithinSpan); public KeysPerSecondCalculator() From 0a94fb4039df50a5a48e55cae546221557fcff4d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:46:31 +0200 Subject: [PATCH 038/174] Make KPS counter strictly depending only on KPS calculator `KeysPerSecondCounter` now depends on `KeysPerSecondCalculator` via the `BackgroundDependencyLoaderAttribute` method, making it appear only in a gameplay context without requiring `GameplayClock` without using it. --- .../Play/HUD/KPSCounter/KeysPerSecondCounter.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs index d6f1d19770..47ebede623 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs @@ -10,7 +10,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.UI; +// using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -22,15 +22,7 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter private readonly Bindable valid = new Bindable(); - [Resolved] - private KeysPerSecondCalculator? calculator { get; set; } - - // This is to force the skin editor to show the component only in a Gameplay context - [Resolved] - private GameplayClock? gameplayClock { get; set; } - - [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset { get; set; } + private KeysPerSecondCalculator? calculator; protected override double RollingDuration => 350; @@ -42,8 +34,9 @@ namespace osu.Game.Screens.Play.HUD.KPSCounter } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, KeysPerSecondCalculator calculator) { + this.calculator = calculator; Colour = colours.BlueLighter; valid.BindValueChanged(e => DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); From d58d5eebe2ec52e19142442dcdfd162d6263f213 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 00:51:13 +0200 Subject: [PATCH 039/174] Add basic tests for KPS Created private mock classes to use them in place of `GameplayClock` and `DrawableRuleset`. --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 306 ++++++++++++++++++ .../Gameplay/TestSceneKeysPerSecondCounter.cs | 9 - 2 files changed, 306 insertions(+), 9 deletions(-) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs delete mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs new file mode 100644 index 0000000000..4bda998c49 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -0,0 +1,306 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.KPSCounter; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneKeysPerSecond : OsuTestScene + { + private DependencyProvidingContainer? dependencyContainer; + private MockFrameStableClock? mainClock; + private KeysPerSecondCalculator? calculator; + private ManualInputListener? listener; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create components", () => + { + var ruleset = CreateRuleset(); + + Debug.Assert(ruleset != null); + + Children = new Drawable[] + { + dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), + (typeof(DrawableRuleset), new DrawableCookieziRuleset(ruleset, mainClock)) + } + }, + }; + }); + } + + private void createCalculator() + { + AddStep("create calculator", () => + { + dependencyContainer!.Children = new Drawable[] + { + calculator = new KeysPerSecondCalculator + { + Listener = listener = new ManualInputListener() + }, + new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(KeysPerSecondCalculator), calculator) }, + Child = new KeysPerSecondCounter // For visual debugging, has no real purpose in the tests + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } + } + }; + }); + } + + [Test] + public void TestBasicConsistency() + { + createCalculator(); + + AddStep("Create gradually increasing KPS inputs", () => + { + addInputs(generateGraduallyIncreasingKps()); + }); + + for (int i = 0; i < 10; i++) + { + seek(i * 10000); + advanceForwards(2); + int kps = i + 1; + AddAssert($"{kps} KPS", () => calculator!.Value == kps); + } + } + + [Test] + public void TestRateAdjustConsistency() + { + createCalculator(); + + AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); + + advanceForwards(2); + + for (double i = 1; i <= 2; i += 0.25) + { + changeRate(i); + double rate = i; + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + } + + for (double i = 1; i >= 0.5; i -= 0.25) + { + changeRate(i); + double rate = i; + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + } + } + + [Test] + public void TestInputsDiscardedOnRewind() + { + createCalculator(); + + AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); + seek(1000); + + AddAssert("KPS = 10", () => calculator!.Value == 10); + + AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); + seek(1000); + AddAssert("KPS didn't changed", () => calculator!.Value == 10); + } + + private void seek(double time) => AddStep($"Seek main clock to {time}ms", () => mainClock?.Seek(time)); + + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => + (mainClock?.UnderlyingClock as MockFrameBasedClock)!.Rate = rate); + + private void advanceForwards(int frames = 1) => AddStep($"Advance main clock {frames} frame(s) forward.", () => + { + if (mainClock == null) return; + + MockFrameBasedClock underlyingClock = (MockFrameBasedClock)mainClock.UnderlyingClock; + underlyingClock.Backwards = false; + + for (int i = 0; i < frames; i++) + { + underlyingClock.ProcessFrame(); + } + }); + + private void addInputs(IEnumerable inputs) + { + Debug.Assert(mainClock != null && listener != null); + if (!inputs.Any()) return; + + double baseTime = mainClock.CurrentTime; + + foreach (double timestamp in inputs) + { + mainClock.Seek(timestamp); + listener.AddInput(); + } + + mainClock.Seek(baseTime); + } + + private IEnumerable generateGraduallyIncreasingKps() + { + IEnumerable? final = null; + + for (int i = 1; i <= 10; i++) + { + var currentKps = generateConsistentKps(i, (i - 1) * 10000); + + if (i == 1) + { + final = currentKps; + continue; + } + + final = final!.Concat(currentKps); + } + + return final!; + } + + private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) + { + double end = start + 1000 * duration; + + for (; start < end; start += 1000 / kps) + { + yield return start; + } + } + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + + #region Mock classes + + private class ManualInputListener : KeysPerSecondCalculator.InputListener + { + public override event Action? OnNewInput; + + public void AddInput() => OnNewInput?.Invoke(); + } + + private class MockFrameBasedClock : ManualClock, IFrameBasedClock + { + public const double FRAME_INTERVAL = 1000; + public bool Backwards; + + public MockFrameBasedClock() + { + Rate = 1; + IsRunning = true; + } + + public void ProcessFrame() + { + CurrentTime += FRAME_INTERVAL * Rate * (Backwards ? -1 : 1); + TimeInfo = new FrameTimeInfo + { + Current = CurrentTime, + Elapsed = FRAME_INTERVAL * Rate * (Backwards ? -1 : 1) + }; + } + + public void Seek(double time) + { + TimeInfo = new FrameTimeInfo + { + Elapsed = time - CurrentTime, + Current = CurrentTime = time + }; + } + + public double ElapsedFrameTime => TimeInfo.Elapsed; + public double FramesPerSecond => 1 / FRAME_INTERVAL; + public FrameTimeInfo TimeInfo { get; private set; } + } + + private class MockFrameStableClock : GameplayClock, IFrameStableClock + { + public MockFrameStableClock(MockFrameBasedClock underlyingClock) + : base(underlyingClock) + { + } + + public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); + + public IBindable IsCatchingUp => new Bindable(); + public IBindable WaitingOnFrames => new Bindable(); + } + + private class DrawableCookieziRuleset : DrawableRuleset + { + public DrawableCookieziRuleset(Ruleset ruleset, IFrameStableClock clock) + : base(ruleset) + { + FrameStableClock = clock; + } + +#pragma warning disable CS0067 + public override event Action? NewResult; + public override event Action? RevertResult; +#pragma warning restore CS0067 + public override Playfield? Playfield => null; + public override Container? Overlays => null; + public override Container? FrameStableComponents => null; + public override IFrameStableClock FrameStableClock { get; } + + internal override bool FrameStablePlayback { get; set; } + public override IReadOnlyList Mods => Array.Empty(); + public override IEnumerable Objects => Array.Empty(); + public override double GameplayStartTime => 0; + public override GameplayCursorContainer? Cursor => null; + + public override void SetReplayScore(Score replayScore) + { + } + + public override void SetRecordTarget(Score score) + { + } + + public override void RequestResume(Action continueResume) + { + } + + public override void CancelResume() + { + } + } + + #endregion + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs deleted file mode 100644 index 8bc2eae1d4..0000000000 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecondCounter.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Tests.Visual.Gameplay -{ - public class TestSceneKeysPerSecondCounter : OsuManualInputManagerTestScene - { - } -} From 0e1efbd865c96a2aa53c98f80cc1b00d287f2190 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 01:03:47 +0200 Subject: [PATCH 040/174] Rename `DrawableCookieziRuleset` to `MockDrawableRuleset` --- osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index 4bda998c49..edfba7f154 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new (Type, object)[] { (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), - (typeof(DrawableRuleset), new DrawableCookieziRuleset(ruleset, mainClock)) + (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) } }, }; @@ -261,9 +261,9 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable WaitingOnFrames => new Bindable(); } - private class DrawableCookieziRuleset : DrawableRuleset + private class MockDrawableRuleset : DrawableRuleset { - public DrawableCookieziRuleset(Ruleset ruleset, IFrameStableClock clock) + public MockDrawableRuleset(Ruleset ruleset, IFrameStableClock clock) : base(ruleset) { FrameStableClock = clock; From 3c6461b9e4383174278cf3b4ab7493a6c076473d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 09:36:35 +0200 Subject: [PATCH 041/174] Remove KPS acronym usage --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 6 +++--- osu.Game/Rulesets/UI/RulesetInputManager.cs | 16 ++++++---------- .../KeysPerSecondCalculator.cs | 2 +- .../KeysPerSecondCounter.cs | 4 ++-- osu.Game/Screens/Play/HUDOverlay.cs | 5 ++--- 6 files changed, 15 insertions(+), 20 deletions(-) rename osu.Game/Screens/Play/HUD/{KPSCounter => KeysPerSecond}/KeysPerSecondCalculator.cs (98%) rename osu.Game/Screens/Play/HUD/{KPSCounter => KeysPerSecond}/KeysPerSecondCounter.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index edfba7f154..5c1ca18dbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osuTK; namespace osu.Game.Tests.Visual.Gameplay diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index b28e3355a4..443e4392cf 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,7 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter, ICanAttachKpsCalculator + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter where TObject : HitObject { public override event Action NewResult; @@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); - public void Attach(KeysPerSecondCalculator kps) => (KeyBindingInputManager as ICanAttachKpsCalculator)?.Attach(kps); + public void Attach(KeysPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 23e64153eb..2e9986ada6 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,12 +20,12 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler, ICanAttachKpsCalculator + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -187,15 +187,15 @@ namespace osu.Game.Rulesets.UI #endregion - #region KPS Counter Attachment + #region Keys per second Counter Attachment - public void Attach(KeysPerSecondCalculator kps) + public void Attach(KeysPerSecondCalculator calculator) { var listener = new ActionListener(); KeyBindingContainer.Add(listener); - kps.Listener = listener; + calculator.Listener = listener; } public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler @@ -257,11 +257,7 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachKeyCounter { void Attach(KeyCounterDisplay keyCounter); - } - - public interface ICanAttachKpsCalculator - { - void Attach(KeysPerSecondCalculator keysPerSecondCalculator); + void Attach(KeysPerSecondCalculator calculator); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs similarity index 98% rename from osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs rename to osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs index dddef9abaf..ecc9c6ef86 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KPSCounter +namespace osu.Game.Screens.Play.HUD.KeysPerSecond { public class KeysPerSecondCalculator : Component { diff --git a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs similarity index 98% rename from osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 47ebede623..6589dbb719 100644 --- a/osu.Game/Screens/Play/HUD/KPSCounter/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -10,11 +10,11 @@ using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -// using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; +// using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KPSCounter +namespace osu.Game.Screens.Play.HUD.KeysPerSecond { public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1c28e04950..20a1a27f3d 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.KPSCounter; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Skinning; using osuTK; @@ -127,7 +127,6 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - keysPerSecondCalculator = new KeysPerSecondCalculator() }; } @@ -265,7 +264,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKpsCalculator)?.Attach(keysPerSecondCalculator); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(keysPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From 787dee249da553736e006485582a8eaddcf54ef5 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 10:37:06 +0200 Subject: [PATCH 042/174] Move `KeysPerSecondCalculator` instantiation from `HUDOverlay` to `Player` This prevents messing with *future* Skin (de)serialization --- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++++--- osu.Game/Screens/Play/Player.cs | 7 +++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 20a1a27f3d..1cddbcac41 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,8 +50,7 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - [Cached] - private readonly KeysPerSecondCalculator keysPerSecondCalculator; + private KeysPerSecondCalculator keysPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -131,8 +130,10 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, INotificationOverlay notificationOverlay) + private void load(OsuConfigManager config, INotificationOverlay notificationOverlay, KeysPerSecondCalculator calculator) { + keysPerSecondCalculator = calculator; + if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e3844088e2..ba7e01a803 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,6 +34,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -122,6 +123,8 @@ namespace osu.Game.Screens.Play private SkipOverlay skipIntroOverlay; private SkipOverlay skipOutroOverlay; + protected KeysPerSecondCalculator KeysPerSecondCalculator { get; private set; } + protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -226,6 +229,9 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(ScoreProcessor); + KeysPerSecondCalculator = new KeysPerSecondCalculator(); + dependencies.CacheAs(KeysPerSecondCalculator); + HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); @@ -442,6 +448,7 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = () => PerformExit(true), }, + KeysPerSecondCalculator }, }; From d29cba80e9705a2a28c4dff23c0dd55312923654 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 11:01:16 +0200 Subject: [PATCH 043/174] Remove useless comment in `KeysPerSecondCounter` --- osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 6589dbb719..7bd4d41242 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; -// using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.KeysPerSecond { From 9b252b1d81ce40307324e2a3197433dd874d464c Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 11 Aug 2022 11:58:30 +0200 Subject: [PATCH 044/174] Make `KeysPerSecondCalculator` dependency in `HUDOverlay` nullable --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 ++ osu.Game/Screens/Play/HUDOverlay.cs | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 2e9986ada6..590a305f77 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -191,6 +191,8 @@ namespace osu.Game.Rulesets.UI public void Attach(KeysPerSecondCalculator calculator) { + if (calculator == null) return; + var listener = new ActionListener(); KeyBindingContainer.Add(listener); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1cddbcac41..458e19826b 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,7 +50,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - private KeysPerSecondCalculator keysPerSecondCalculator; + [Resolved(canBeNull: true)] + private KeysPerSecondCalculator keysPerSecondCalculator { get; set; } public Bindable ShowHealthBar = new Bindable(true); @@ -130,10 +131,8 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader(true)] - private void load(OsuConfigManager config, INotificationOverlay notificationOverlay, KeysPerSecondCalculator calculator) + private void load(OsuConfigManager config, INotificationOverlay notificationOverlay) { - keysPerSecondCalculator = calculator; - if (drawableRuleset != null) { BindDrawableRuleset(drawableRuleset); From 78fe72476de7ea3b591596480059741182259b45 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 00:01:40 +0200 Subject: [PATCH 045/174] Adjust parameters --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 716fa990c9..3c0642ca15 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -46,9 +46,9 @@ namespace osu.Game.Rulesets.Osu.Mods if (i == 0 || (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.3)) + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.4)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0012f); + sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); flowDirection = !flowDirection; } From 8c624d3269902ea8f83e874fbfebd204c97b8206 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 00:57:49 +0200 Subject: [PATCH 046/174] Add comments and improve code readability --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 62 +++++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 3c0642ca15..0a89f3eb43 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.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 osu.Framework.Utils; using osu.Game.Beatmaps; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToBeatmap(IBeatmap beatmap) { - if (!(beatmap is OsuBeatmap osuBeatmap)) + if (beatmap is not OsuBeatmap osuBeatmap) return; Seed.Value ??= RNG.Next(); @@ -38,17 +39,17 @@ namespace osu.Game.Rulesets.Osu.Mods var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); - float sequenceOffset = 0; + // Offsets the angles of all hit objects in a "section" by the same amount. + float sectionOffset = 0; + + // Whether the angles are positive or negative (clockwise or counter-clockwise flow). bool flowDirection = false; for (int i = 0; i < positionInfos.Count; i++) { - if (i == 0 || - (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) || - OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(osuBeatmap, positionInfos[i - 1].HitObject) && rng.NextDouble() < 0.4)) + if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) { - sequenceOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); flowDirection = !flowDirection; } @@ -59,21 +60,25 @@ namespace osu.Game.Rulesets.Osu.Mods } else { + // Offsets only the angle of the current hit object if a flow change occurs. float flowChangeOffset = 0; + + // Offsets only the angle of the current hit object. float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - if (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng.NextDouble() < 0.6) + if (shouldApplyFlowChange(positionInfos, i, 0.6f)) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); flowDirection = !flowDirection; } - positionInfos[i].RelativeAngle = getRelativeTargetAngle( - positionInfos[i].DistanceFromPrevious, - (sequenceOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + - flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious), - flowDirection - ); + float totalOffset = + // sectionOffset and oneTimeOffset should mainly affect patterns with large spacing. + (sectionOffset + oneTimeOffset) * positionInfos[i].DistanceFromPrevious + + // flowChangeOffset should mainly affect streams. + flowChangeOffset * (playfield_diagonal - positionInfos[i].DistanceFromPrevious); + + positionInfos[i].RelativeAngle = getRelativeTargetAngle(positionInfos[i].DistanceFromPrevious, totalOffset, flowDirection); } } @@ -89,5 +94,34 @@ namespace osu.Game.Rulesets.Osu.Mods float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } + + /// + /// A new section should be started...
+ /// ...at the beginning of the .
+ /// ...on every combo start with a probability of (excluding new-combo-spam and 1-2-combos).
+ /// ...on every downbeat.
+ /// ...on every beat with a probability of .
+ ///
+ /// Whether a new section should be started at the current . + private bool shouldStartNewSection( + OsuBeatmap beatmap, + IReadOnlyList positionInfos, + int i, + float newComboProbability, + float beatProbability + ) => + i == 0 || + (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng?.NextDouble() < newComboProbability) || + OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true) || + (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject) && rng?.NextDouble() < beatProbability); + + /// + /// A flow change should occur on every combo start with a probability of (excluding new-combo-spam and 1-2-combos). + /// + /// Whether a flow change should be applied at the current . + private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i, float probability) => + positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo && + rng?.NextDouble() < probability; } } From 7a41b9f25a35c375670103baad777e15ea231e59 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sat, 13 Aug 2022 03:11:58 +0200 Subject: [PATCH 047/174] Adjust angle and `sectionOffset` calculations --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 0a89f3eb43..2be1ad231a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) { - sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.001f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); flowDirection = !flowDirection; } @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// Whether the relative angle should be positive or negative. private static float getRelativeTargetAngle(float targetDistance, float offset, bool flowDirection) { - float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 320))) + 0.5 + offset); + float angle = (float)(2.16 / (1 + 200 * Math.Exp(0.036 * (targetDistance - 310))) + 0.5 + offset); float relativeAngle = (float)Math.PI - angle; return flowDirection ? -relativeAngle : relativeAngle; } From e08f71797ec89bbcf41be2689a6f9d939623d4ba Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sat, 13 Aug 2022 04:27:26 +0200 Subject: [PATCH 048/174] Change displayed metric from "KPS" to "clicks/s" --- .../HUD/KeysPerSecond/KeysPerSecondCounter.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs index 7bd4d41242..a5c122f5b1 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs @@ -80,13 +80,30 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, - new OsuSpriteText + new FillFlowContainer { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), - Text = @"KPS", - Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Font = OsuFont.Numeric.With(size: 6, fixedWidth: false), + Text = @"clicks", + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Font = OsuFont.Numeric.With(size: 6, fixedWidth: false), + Text = @"/sec", + Padding = new MarginPadding { Bottom = 3f }, // align baseline better + } + } } } }; From fa2ebe1d5f8fde264e4253f16195fb73f9db0b35 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 18:02:29 +0800 Subject: [PATCH 049/174] add basic touch functionality --- osu.Game.Rulesets.Catch/CatchInputManager.cs | 2 ++ osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index 86f35c8cee..5b62154a34 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -4,11 +4,13 @@ #nullable disable using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch { + [Cached] public class CatchInputManager : RulesetInputManager { public CatchInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index b6cea92173..83dc5770a6 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -32,6 +33,13 @@ namespace osu.Game.Rulesets.Catch.UI TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); } + [BackgroundDependencyLoader] + private void load() + { + KeyBindingInputManager.Add(new TouchInputField()); + } + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); From 757d236e14460ad5af6a1f3b7b901c713cd492d6 Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 18:55:31 +0800 Subject: [PATCH 050/174] Add the UI file --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 280 ++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 osu.Game.Rulesets.Catch/UI/TouchInputField.cs diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs new file mode 100644 index 0000000000..1219ebd02d --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -0,0 +1,280 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using System.Diagnostics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osuTK.Graphics; +using osuTK; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class TouchInputField : VisibilityContainer + { + public enum TouchCatchAction + { + MoveLeft = 0, + MoveRight = 1, + DashLeft = 2, + DashRight = 3, + None = 4 + } + + private Dictionary trackedActions = new Dictionary(); + + private KeyBindingContainer keyBindingContainer = null!; + + private Container mainContent = null!; + + // Fill values with null because UI is not declared in constructor + private ArrowHitbox leftBox = null!; + private ArrowHitbox rightBox = null!; + private ArrowHitbox leftDashBox = null!; + private ArrowHitbox rightDashBox = null!; + + [BackgroundDependencyLoader] + private void load(CatchInputManager catchInputManager, OsuColour colours) + { + Show(); + Debug.Assert(catchInputManager.KeyBindingContainer != null); + + keyBindingContainer = catchInputManager.KeyBindingContainer; + + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Blue) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.CentreLeft, + X = 0, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f) + }, + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Blue) + { + Anchor = Anchor.TopRight, + Origin = Anchor.CentreRight, + X = 0, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Pink) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.CentreLeft, + X = 0.1f, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Pink) + { + Anchor = Anchor.TopRight, + Origin = Anchor.CentreRight, + X = -0.1f, + RelativePositionAxes = Axes.Both, + Size = new Vector2(100.0f, 100.0f), + }, + } + }, + }; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + Hide(); + return false; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + return false; + + handleDown(e.Button, e.ScreenSpaceMousePosition); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + return; + + handleUp(e.Button); + base.OnMouseUp(e); + } + + protected override void OnTouchMove(TouchMoveEvent e) + { + // I'm not sure if this is posible but let's be safe + if (!trackedActions.ContainsKey(e.Touch.Source)) + trackedActions.Add(e.Touch.Source, TouchCatchAction.None); + + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.MousePosition); + + calculateActiveKeys(); + + base.OnTouchMove(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); + return true; + } + + protected override void OnTouchUp(TouchUpEvent e) + { + handleUp(e.Touch.Source); + base.OnTouchUp(e); + } + + private CatchAction removeDashFromAction(TouchCatchAction touchCatchAction) + { + if (touchCatchAction == TouchCatchAction.DashLeft || touchCatchAction == TouchCatchAction.MoveLeft) + return CatchAction.MoveLeft; + return CatchAction.MoveRight; + } + + private void calculateActiveKeys() + { + if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) + keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); + + if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.MoveRight)) + keyBindingContainer.TriggerPressed(CatchAction.MoveRight); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveRight); + + if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.DashLeft)) + keyBindingContainer.TriggerPressed(CatchAction.Dash); + else + keyBindingContainer.TriggerReleased(CatchAction.Dash); + } + + private void handleDown(object source, Vector2 position) + { + Show(); + + TouchCatchAction catchAction = getTouchCatchActionFromInput(position); + + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + + trackedActions.Add(source, catchAction); + calculateActiveKeys(); + } + + private void handleUp(object source) + { + trackedActions.Remove(source); + + calculateActiveKeys(); + } + + private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) + { + if (leftDashBox.Contains(inputPosition)) + return TouchCatchAction.DashLeft; + if (rightDashBox.Contains(inputPosition)) + return TouchCatchAction.DashRight; + if (leftBox.Contains(inputPosition)) + return TouchCatchAction.MoveLeft; + if (rightBox.Contains(inputPosition)) + return TouchCatchAction.MoveRight; + return TouchCatchAction.None; + } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class ArrowHitbox : CompositeDrawable, IKeyBindingHandler + { + private readonly TouchCatchAction handledAction; + + private readonly Box overlay; + + private readonly Dictionary trackedActions; + + private bool isHiglighted = false; + + public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + { + this.handledAction = handledAction; + this.trackedActions = trackedActions; + + InternalChildren = new Drawable[] + { + new Container + { + Width = 1, + Height = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + overlay = new Box + { + Alpha = 0, + Colour = colour.Multiply(1.4f).Darken(2.8f), + Blending = BlendingParameters.Additive, + Size = new Vector2(100.0f, 100.0f), + }, + new Box + { + Alpha = 0.5f, + Colour = colour, + Size = new Vector2(100.0f, 100.0f), + } + } + } + }; + } + + public bool OnPressed(KeyBindingPressEvent _) + { + if (trackedActions.ContainsValue(handledAction)) + { + isHiglighted = true; + overlay.FadeTo(0.5f, 80, Easing.OutQuint); + } + return false; + } + + public void OnReleased(KeyBindingReleaseEvent _) + { + if (isHiglighted && !trackedActions.ContainsValue(handledAction)) + { + isHiglighted = false; + overlay.FadeOut(1000, Easing.Out); + } + } + } + } +} From 09e45f39b2aa8c1bb3f359133c3ca30b564629ea Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 13 Aug 2022 19:55:47 +0800 Subject: [PATCH 051/174] Add the touchinputfield file because it was untracked --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 1219ebd02d..cb853d1abd 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -146,13 +146,6 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private CatchAction removeDashFromAction(TouchCatchAction touchCatchAction) - { - if (touchCatchAction == TouchCatchAction.DashLeft || touchCatchAction == TouchCatchAction.MoveLeft) - return CatchAction.MoveLeft; - return CatchAction.MoveRight; - } - private void calculateActiveKeys() { if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) From d5f10cbb9d1396ab10c32981c436729fb4c2c639 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 18:53:00 +0200 Subject: [PATCH 052/174] Revert 787dee24 and initialize calculator in `HUDOverlay` --- osu.Game/Screens/Play/HUDOverlay.cs | 5 +++-- osu.Game/Screens/Play/Player.cs | 7 ------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 458e19826b..20f7f7d6c2 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -50,8 +50,8 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - [Resolved(canBeNull: true)] - private KeysPerSecondCalculator keysPerSecondCalculator { get; set; } + [Cached] + private readonly KeysPerSecondCalculator keysPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -127,6 +127,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, + keysPerSecondCalculator = new KeysPerSecondCalculator() }; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ba7e01a803..e3844088e2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -34,7 +34,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; -using osu.Game.Screens.Play.HUD.KeysPerSecond; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -123,8 +122,6 @@ namespace osu.Game.Screens.Play private SkipOverlay skipIntroOverlay; private SkipOverlay skipOutroOverlay; - protected KeysPerSecondCalculator KeysPerSecondCalculator { get; private set; } - protected ScoreProcessor ScoreProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; } @@ -229,9 +226,6 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(ScoreProcessor); - KeysPerSecondCalculator = new KeysPerSecondCalculator(); - dependencies.CacheAs(KeysPerSecondCalculator); - HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); @@ -448,7 +442,6 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = () => PerformExit(true), }, - KeysPerSecondCalculator }, }; From 5106c00a9c5010f751b428a7b0726d95bc5b7620 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Sun, 14 Aug 2022 19:02:29 +0200 Subject: [PATCH 053/174] Improve code quality --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 54 +++++++++++----------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 2be1ad231a..ce54d87dfa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Mods for (int i = 0; i < positionInfos.Count; i++) { - if (shouldStartNewSection(osuBeatmap, positionInfos, i, 0.6f, 0.4f)) + if (shouldStartNewSection(osuBeatmap, positionInfos, i)) { sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); flowDirection = !flowDirection; @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Offsets only the angle of the current hit object. float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); - if (shouldApplyFlowChange(positionInfos, i, 0.6f)) + if (shouldApplyFlowChange(positionInfos, i)) { flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); flowDirection = !flowDirection; @@ -95,33 +95,35 @@ namespace osu.Game.Rulesets.Osu.Mods return flowDirection ? -relativeAngle : relativeAngle; } - /// - /// A new section should be started...
- /// ...at the beginning of the .
- /// ...on every combo start with a probability of (excluding new-combo-spam and 1-2-combos).
- /// ...on every downbeat.
- /// ...on every beat with a probability of .
- ///
/// Whether a new section should be started at the current . - private bool shouldStartNewSection( - OsuBeatmap beatmap, - IReadOnlyList positionInfos, - int i, - float newComboProbability, - float beatProbability - ) => - i == 0 || - (positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo && rng?.NextDouble() < newComboProbability) || - OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true) || - (OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject) && rng?.NextDouble() < beatProbability); + private bool shouldStartNewSection(OsuBeatmap beatmap, IReadOnlyList positionInfos, int i) + { + if (i == 0) + return true; + + // Exclude new-combo-spam and 1-2-combos. + bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo; + bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true); + bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject); + + return (previousObjectStartedCombo && randomBool(0.6f)) || + previousObjectWasOnDownbeat || + (previousObjectWasOnBeat && randomBool(0.4f)); + } - /// - /// A flow change should occur on every combo start with a probability of (excluding new-combo-spam and 1-2-combos). - /// /// Whether a flow change should be applied at the current . - private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i, float probability) => - positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && - positionInfos[i - 1].HitObject.NewCombo && + private bool shouldApplyFlowChange(IReadOnlyList positionInfos, int i) + { + // Exclude new-combo-spam and 1-2-combos. + bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && + positionInfos[i - 1].HitObject.NewCombo; + + return previousObjectStartedCombo && randomBool(0.6f); + } + + /// true with a probability of , false otherwise. + private bool randomBool(float probability) => rng?.NextDouble() < probability; } } From 9dc806506e55ca13b39241b50e71a1b7da7a332b Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 19:09:34 +0200 Subject: [PATCH 054/174] Make `ActionListener` and `KeysPerSecondCalculator` not rely on events to add timestamps --- .../Visual/Gameplay/TestSceneKeysPerSecond.cs | 13 +++++----- osu.Game/Rulesets/UI/RulesetInputManager.cs | 11 +++++--- .../KeysPerSecond/KeysPerSecondCalculator.cs | 25 ++++++------------- 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs index 5c1ca18dbc..ac7b7521c9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs @@ -61,10 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay { dependencyContainer!.Children = new Drawable[] { - calculator = new KeysPerSecondCalculator - { - Listener = listener = new ManualInputListener() - }, + calculator = new KeysPerSecondCalculator(), new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, @@ -77,6 +74,7 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; + calculator!.Listener = listener = new ManualInputListener(calculator!); }); } @@ -208,9 +206,12 @@ namespace osu.Game.Tests.Visual.Gameplay private class ManualInputListener : KeysPerSecondCalculator.InputListener { - public override event Action? OnNewInput; + public void AddInput() => Calculator.AddTimestamp(); - public void AddInput() => OnNewInput?.Invoke(); + public ManualInputListener(KeysPerSecondCalculator calculator) + : base(calculator) + { + } } private class MockFrameBasedClock : ManualClock, IFrameBasedClock diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 590a305f77..23580bc40a 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -193,7 +193,7 @@ namespace osu.Game.Rulesets.UI { if (calculator == null) return; - var listener = new ActionListener(); + var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); @@ -202,11 +202,9 @@ namespace osu.Game.Rulesets.UI public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler { - public override event Action OnNewInput; - public bool OnPressed(KeyBindingPressEvent e) { - OnNewInput?.Invoke(); + Calculator.AddTimestamp(); return false; } @@ -214,6 +212,11 @@ namespace osu.Game.Rulesets.UI public void OnReleased(KeyBindingReleaseEvent e) { } + + public ActionListener(KeysPerSecondCalculator calculator) + : base(calculator) + { + } } #endregion diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs index ecc9c6ef86..20ab09e9cc 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs @@ -29,7 +29,6 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { onResetRequested?.Invoke(); listener = value; - listener.OnNewInput += addTimestamp; } } @@ -43,12 +42,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { get { - if (gameplayClock != null) + if (gameplayClock?.TrueGameplayRate > 0) { - if (gameplayClock.TrueGameplayRate > 0) - { - baseRate = gameplayClock.TrueGameplayRate; - } + baseRate = gameplayClock.TrueGameplayRate; } return baseRate; @@ -71,12 +67,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond { timestamps.Clear(); maxTime = double.NegativeInfinity; - - if (listener != null) - listener.OnNewInput -= addTimestamp; } - private void addTimestamp() + public void AddTimestamp() { if (workingClock == null) return; @@ -96,20 +89,16 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond return relativeTime > 0 && relativeTime <= span; } - ~KeysPerSecondCalculator() - { - cleanUp(); - } - public abstract class InputListener : Component { - protected InputListener() + protected KeysPerSecondCalculator Calculator; + + protected InputListener(KeysPerSecondCalculator calculator) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; + Calculator = calculator; } - - public abstract event Action? OnNewInput; } } } From 2aa3a1b50d558b8c01097b7c601889848596d8f6 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 14 Aug 2022 20:12:11 +0200 Subject: [PATCH 055/174] Rename all "KeysPerSecond" usages to "ClicksPerSecond" --- ...sPerSecond.cs => TestSceneClicksPerSecond.cs} | 16 ++++++++-------- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +++++----- .../ClicksPerSecondCalculator.cs} | 10 +++++----- .../ClicksPerSecondCounter.cs} | 10 +++++----- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++---- 6 files changed, 29 insertions(+), 29 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneKeysPerSecond.cs => TestSceneClicksPerSecond.cs} (94%) rename osu.Game/Screens/Play/HUD/{KeysPerSecond/KeysPerSecondCalculator.cs => ClicksPerSecond/ClicksPerSecondCalculator.cs} (90%) rename osu.Game/Screens/Play/HUD/{KeysPerSecond/KeysPerSecondCounter.cs => ClicksPerSecond/ClicksPerSecondCounter.cs} (92%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs similarity index 94% rename from osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index ac7b7521c9..8a5bd1af0f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeysPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -19,16 +19,16 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneKeysPerSecond : OsuTestScene + public class TestSceneClicksPerSecond : OsuTestScene { private DependencyProvidingContainer? dependencyContainer; private MockFrameStableClock? mainClock; - private KeysPerSecondCalculator? calculator; + private ClicksPerSecondCalculator? calculator; private ManualInputListener? listener; [SetUpSteps] @@ -61,12 +61,12 @@ namespace osu.Game.Tests.Visual.Gameplay { dependencyContainer!.Children = new Drawable[] { - calculator = new KeysPerSecondCalculator(), + calculator = new ClicksPerSecondCalculator(), new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(KeysPerSecondCalculator), calculator) }, - Child = new KeysPerSecondCounter // For visual debugging, has no real purpose in the tests + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -204,11 +204,11 @@ namespace osu.Game.Tests.Visual.Gameplay #region Mock classes - private class ManualInputListener : KeysPerSecondCalculator.InputListener + private class ManualInputListener : ClicksPerSecondCalculator.InputListener { public void AddInput() => Calculator.AddTimestamp(); - public ManualInputListener(KeysPerSecondCalculator calculator) + public ManualInputListener(ClicksPerSecondCalculator calculator) : base(calculator) { } diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 443e4392cf..cb483bff81 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -30,7 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; namespace osu.Game.Rulesets.UI @@ -341,7 +341,7 @@ namespace osu.Game.Rulesets.UI public void Attach(KeyCounterDisplay keyCounter) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); - public void Attach(KeysPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); + public void Attach(ClicksPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 23580bc40a..4b7ce22cfc 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -20,7 +20,7 @@ using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.UI #region Keys per second Counter Attachment - public void Attach(KeysPerSecondCalculator calculator) + public void Attach(ClicksPerSecondCalculator calculator) { if (calculator == null) return; @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.UI calculator.Listener = listener; } - public class ActionListener : KeysPerSecondCalculator.InputListener, IKeyBindingHandler + public class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler { public bool OnPressed(KeyBindingPressEvent e) { @@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.UI { } - public ActionListener(KeysPerSecondCalculator calculator) + public ActionListener(ClicksPerSecondCalculator calculator) : base(calculator) { } @@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.UI public interface ICanAttachKeyCounter { void Attach(KeyCounterDisplay keyCounter); - void Attach(KeysPerSecondCalculator calculator); + void Attach(ClicksPerSecondCalculator calculator); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs similarity index 90% rename from osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs rename to osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 20ab09e9cc..9dc8615fb6 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -9,9 +9,9 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Rulesets.UI; -namespace osu.Game.Screens.Play.HUD.KeysPerSecond +namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public class KeysPerSecondCalculator : Component + public class ClicksPerSecondCalculator : Component { private readonly List timestamps; @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond public bool Ready => workingClock != null && gameplayClock != null && listener != null; public int Value => timestamps.Count(isTimestampWithinSpan); - public KeysPerSecondCalculator() + public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; timestamps = new List(); @@ -91,9 +91,9 @@ namespace osu.Game.Screens.Play.HUD.KeysPerSecond public abstract class InputListener : Component { - protected KeysPerSecondCalculator Calculator; + protected ClicksPerSecondCalculator Calculator; - protected InputListener(KeysPerSecondCalculator calculator) + protected InputListener(ClicksPerSecondCalculator calculator) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; diff --git a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs similarity index 92% rename from osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs rename to osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index a5c122f5b1..049f38ba4c 100644 --- a/osu.Game/Screens/Play/HUD/KeysPerSecond/KeysPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -13,27 +13,27 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; -namespace osu.Game.Screens.Play.HUD.KeysPerSecond +namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { - public class KeysPerSecondCounter : RollingCounter, ISkinnableDrawable + public class ClicksPerSecondCounter : RollingCounter, ISkinnableDrawable { private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private KeysPerSecondCalculator? calculator; + private ClicksPerSecondCalculator? calculator; protected override double RollingDuration => 350; public bool UsesFixedAnchor { get; set; } - public KeysPerSecondCounter() + public ClicksPerSecondCounter() { Current.Value = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours, KeysPerSecondCalculator calculator) + private void load(OsuColour colours, ClicksPerSecondCalculator calculator) { this.calculator = calculator; Colour = colours.BlueLighter; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 20f7f7d6c2..b27efaf13a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -22,7 +22,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; -using osu.Game.Screens.Play.HUD.KeysPerSecond; +using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osu.Game.Skinning; using osuTK; @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Play public readonly PlayerSettingsOverlay PlayerSettingsOverlay; [Cached] - private readonly KeysPerSecondCalculator keysPerSecondCalculator; + private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; public Bindable ShowHealthBar = new Bindable(true); @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - keysPerSecondCalculator = new KeysPerSecondCalculator() + clicksPerSecondCalculator = new ClicksPerSecondCalculator() }; } @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKeyCounter)?.Attach(keysPerSecondCalculator); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(clicksPerSecondCalculator); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From ff497c452fd48978b5f4b7021086f01368c4d62d Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 15 Aug 2022 17:23:29 +0800 Subject: [PATCH 056/174] Fix formatting + Add tests + fix touch UI --- .../TestSceneCatchTouchInput.cs | 45 ++++++++ .../UI/DrawableCatchRuleset.cs | 1 - osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 104 +++++++++++------- 3 files changed, 110 insertions(+), 40 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs new file mode 100644 index 0000000000..03d031a0a8 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatchTouchInput : OsuTestScene + { + private TouchInputField touchInputField = null!; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create inputfield", () => + { + Child = new CatchInputManager(new CatchRuleset().RulesetInfo) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + touchInputField = new TouchInputField + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }; + }); + } + + [Test] + public void TestInputField() + { + AddStep("show inputfield", () => touchInputField.Show()); + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 83dc5770a6..b84d0c60d5 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Catch.UI KeyBindingInputManager.Add(new TouchInputField()); } - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index cb853d1abd..3b92ffa445 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -50,48 +50,70 @@ namespace osu.Game.Rulesets.Catch.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; - Children = new Drawable[] { mainContent = new Container { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, + Alpha = 0, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Blue) + new Container { - Anchor = Anchor.TopLeft, + RelativeSizeAxes = Axes.Both, + Width = 0.15f, + Height = 1, + Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - X = 0, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f) + Children = new Drawable[] + { + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray2) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray3) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + } + } }, - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Blue) + new Container { - Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.15f, + Height = 1, + Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - X = 0, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), + Children = new Drawable[] + { + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray2) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray3) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Width = 0.5f, + }, + } }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Pink) - { - Anchor = Anchor.TopLeft, - Origin = Anchor.CentreLeft, - X = 0.1f, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), - }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Pink) - { - Anchor = Anchor.TopRight, - Origin = Anchor.CentreRight, - X = -0.1f, - RelativePositionAxes = Axes.Both, - Size = new Vector2(100.0f, 100.0f), - }, - } + }, }, }; } @@ -99,13 +121,13 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - Hide(); + PopOut(); return false; } protected override bool OnMouseDown(MouseDownEvent e) { - if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -114,14 +136,15 @@ namespace osu.Game.Rulesets.Catch.UI protected override void OnMouseUp(MouseUpEvent e) { - if (getTouchCatchActionFromInput(e.MousePosition) != TouchCatchAction.None) + if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) return; handleUp(e.Button); base.OnMouseUp(e); } - protected override void OnTouchMove(TouchMoveEvent e) + /* I plan to come back to this code to add touch support + * protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe if (!trackedActions.ContainsKey(e.Touch.Source)) @@ -132,7 +155,7 @@ namespace osu.Game.Rulesets.Catch.UI calculateActiveKeys(); base.OnTouchMove(e); - } + }*/ protected override bool OnTouchDown(TouchDownEvent e) { @@ -166,7 +189,7 @@ namespace osu.Game.Rulesets.Catch.UI private void handleDown(object source, Vector2 position) { - Show(); + PopIn(); TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -195,6 +218,7 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) return TouchCatchAction.MoveRight; + return TouchCatchAction.None; } @@ -216,7 +240,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Dictionary trackedActions; - private bool isHiglighted = false; + private bool isHiglighted; public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) { @@ -228,7 +252,6 @@ namespace osu.Game.Rulesets.Catch.UI new Container { Width = 1, - Height = 1, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { @@ -237,13 +260,15 @@ namespace osu.Game.Rulesets.Catch.UI Alpha = 0, Colour = colour.Multiply(1.4f).Darken(2.8f), Blending = BlendingParameters.Additive, - Size = new Vector2(100.0f, 100.0f), + Width = 1, + RelativeSizeAxes = Axes.Both, }, new Box { - Alpha = 0.5f, + Alpha = 0.8f, Colour = colour, - Size = new Vector2(100.0f, 100.0f), + Width = 1, + RelativeSizeAxes = Axes.Both, } } } @@ -257,6 +282,7 @@ namespace osu.Game.Rulesets.Catch.UI isHiglighted = true; overlay.FadeTo(0.5f, 80, Easing.OutQuint); } + return false; } From be7367b90e5023004bd8352084054eaa8607911a Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 15 Aug 2022 17:52:26 +0800 Subject: [PATCH 057/174] Invert the dash and normal hits. --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 3b92ffa445..771902df65 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -68,18 +68,18 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray2) + leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray3) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray3) + leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray2) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, @@ -95,18 +95,18 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreRight, Children = new Drawable[] { - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray2) + rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray3) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray3) + rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray2) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Width = 0.5f, From 3de35a15180fd37a72df6ae3bb72d589797f25ef Mon Sep 17 00:00:00 2001 From: Ryuki Date: Thu, 18 Aug 2022 18:40:02 +0200 Subject: [PATCH 058/174] Update calculator and tests to match changes on clocks --- .../Gameplay/TestSceneClicksPerSecond.cs | 22 ++++++++++++++++--- .../ClicksPerSecondCalculator.cs | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 8a5bd1af0f..69868e07b4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Gameplay RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { - (typeof(GameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), + (typeof(IGameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) } }, @@ -249,17 +249,33 @@ namespace osu.Game.Tests.Visual.Gameplay public FrameTimeInfo TimeInfo { get; private set; } } - private class MockFrameStableClock : GameplayClock, IFrameStableClock + private class MockFrameStableClock : IGameplayClock, IFrameStableClock { + internal readonly IFrameBasedClock UnderlyingClock; + + public readonly BindableBool IsPaused = new BindableBool(); + public MockFrameStableClock(MockFrameBasedClock underlyingClock) - : base(underlyingClock) { + UnderlyingClock = underlyingClock; } public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); public IBindable IsCatchingUp => new Bindable(); public IBindable WaitingOnFrames => new Bindable(); + public double CurrentTime => UnderlyingClock.CurrentTime; + public double Rate => UnderlyingClock.Rate; + public bool IsRunning => UnderlyingClock.IsRunning; + public void ProcessFrame() => UnderlyingClock.ProcessFrame(); + + public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; + public double FramesPerSecond => UnderlyingClock.FramesPerSecond; + public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; + public double TrueGameplayRate => UnderlyingClock.Rate; + public double? StartTime => 0; + public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + IBindable IGameplayClock.IsPaused => IsPaused; } private class MockDrawableRuleset : DrawableRuleset diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 9dc8615fb6..d9c3c6ffec 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond private InputListener? listener; [Resolved] - private GameplayClock? gameplayClock { get; set; } + private IGameplayClock? gameplayClock { get; set; } [Resolved(canBeNull: true)] private DrawableRuleset? drawableRuleset { get; set; } From 3ac6500423a8c7607989e855de3eda71484eca8f Mon Sep 17 00:00:00 2001 From: Ryuki Date: Fri, 19 Aug 2022 00:49:03 +0200 Subject: [PATCH 059/174] Add new test resources for CPS counter --- .../Archives/modified-default-20220818.osk | Bin 0 -> 1145 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-default-20220818.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20220818.osk b/osu.Game.Tests/Resources/Archives/modified-default-20220818.osk new file mode 100644 index 0000000000000000000000000000000000000000..92215cbf8655decffeedfabd902ed0e8923bab66 GIT binary patch literal 1145 zcmWIWW@Zs#U|`^2NLVTqs`|jDa|)2R0W8A6P@J8arxn6S=HYqNAcrbLkFt<8S zdHCG=Wxv;FdCR48&5>TQc#^zr@Z(j<8zY0+!z(-V9<9<3Kyi8A6BC6ZpsPE9m=}ma zF3-$M%h$^)&d*!@>}gOyz$foBzM-3f0#51cg@#E_w1`>3Opjh`99S~7VrLd3s^caHu3EB{k%8ea&}Cvk$N45^=6Qs= zIOpdUYt#t>`Il0=~M34Q~2k%>zY_|J@Sq?YP0NlkD**?k?0eZrEh$HT#c&P z9%mv}`qyo)_ritmZ>gHp6`p^0>BQEfRwWm`?gy+5d263H)tddC+uj3ECk}@!ESGR@ALFKi%qnSq2%um-l#P&Pw z=#<$j^rQXL#=;|O+LN{a9{(t3aqe3de_f6Z*MX%+dkuuRR`--M_xVM-9hRSy`v1+3 zDecZRMT{q9mY8*gr!_ZO|AU)E;K zooDlh4K<=Qcl+?N03(VK0p5&EA`G~*98eboG=eBt z21M6|p4_2&7#JFNL3P0sKe|@*#E8%u4osrhlP09~Q;lPaH>i}<7Hjn}qAp8WR J3xP@)7yzY8uFwDg literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index c7eb334f25..1b03f8ef6b 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -36,7 +36,9 @@ namespace osu.Game.Tests.Skins "Archives/modified-default-20220723.osk", "Archives/modified-classic-20220723.osk", // Covers legacy song progress, UR counter, colour hit error metre. - "Archives/modified-classic-20220801.osk" + "Archives/modified-classic-20220801.osk", + // Covers clicks/s counter + "Archives/modified-default-20220818.osk" }; /// From 5cf54a788adc5601a518b41409a91a9daca76534 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Sun, 21 Aug 2022 03:15:30 +0200 Subject: [PATCH 060/174] Code cleanup for CPS counter --- .../ClicksPerSecond/ClicksPerSecondCounter.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 049f38ba4c..3ff06d5217 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -19,9 +18,8 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { private const float alpha_when_invalid = 0.3f; - private readonly Bindable valid = new Bindable(); - - private ClicksPerSecondCalculator? calculator; + [Resolved(canBeNull: false)] + private ClicksPerSecondCalculator calculator { get; set; } = null!; protected override double RollingDuration => 350; @@ -33,26 +31,19 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond } [BackgroundDependencyLoader] - private void load(OsuColour colours, ClicksPerSecondCalculator calculator) + private void load(OsuColour colours) { - this.calculator = calculator; Colour = colours.BlueLighter; - valid.BindValueChanged(e => - DrawableCount.FadeTo(e.NewValue ? 1 : alpha_when_invalid, 1000, Easing.OutQuint)); } protected override void Update() { base.Update(); - valid.Value = calculator != null && calculator.Ready; - Current.Value = calculator != null ? calculator.Ready ? calculator.Value : 0 : 0; + Current.Value = calculator.Ready ? calculator.Value : 0; } - protected override IHasText CreateText() => new TextComponent - { - Alpha = alpha_when_invalid - }; + protected override IHasText CreateText() => new TextComponent(); private class TextComponent : CompositeDrawable, IHasText { From c56390cd7b4f7f4c442a321e9a71ce1293e72a3d Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 22 Aug 2022 00:03:24 +0200 Subject: [PATCH 061/174] Use less custom classes for CPS tests --- .../Gameplay/TestSceneClicksPerSecond.cs | 272 +++++++++--------- 1 file changed, 144 insertions(+), 128 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 69868e07b4..375726dd9a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; @@ -13,9 +14,9 @@ using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -27,9 +28,12 @@ namespace osu.Game.Tests.Visual.Gameplay public class TestSceneClicksPerSecond : OsuTestScene { private DependencyProvidingContainer? dependencyContainer; - private MockFrameStableClock? mainClock; private ClicksPerSecondCalculator? calculator; private ManualInputListener? listener; + private GameplayClockContainer? gameplayClockContainer; + private ManualClock? manualClock; + private DrawableRuleset? drawableRuleset; + private IFrameStableClock? frameStableClock; [SetUpSteps] public void SetUpSteps() @@ -40,41 +44,20 @@ namespace osu.Game.Tests.Visual.Gameplay Debug.Assert(ruleset != null); - Children = new Drawable[] + Child = gameplayClockContainer = new GameplayClockContainer(manualClock = new ManualClock()); + gameplayClockContainer.AddRange(new Drawable[] { + drawableRuleset = new TestDrawableRuleset(frameStableClock = new TestFrameStableClock(manualClock)), dependencyContainer = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { - (typeof(IGameplayClock), mainClock = new MockFrameStableClock(new MockFrameBasedClock())), - (typeof(DrawableRuleset), new MockDrawableRuleset(ruleset, mainClock)) - } - }, - }; - }); - } - - private void createCalculator() - { - AddStep("create calculator", () => - { - dependencyContainer!.Children = new Drawable[] - { - calculator = new ClicksPerSecondCalculator(), - new DependencyProvidingContainer - { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, - Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), + (typeof(DrawableRuleset), drawableRuleset), + (typeof(IGameplayClock), gameplayClockContainer) } } - }; - calculator!.Listener = listener = new ManualInputListener(calculator!); + }); }); } @@ -82,6 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestBasicConsistency() { createCalculator(); + startClock(); AddStep("Create gradually increasing KPS inputs", () => { @@ -101,6 +85,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestRateAdjustConsistency() { createCalculator(); + startClock(); AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); @@ -125,6 +110,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestInputsDiscardedOnRewind() { createCalculator(); + startClock(); AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); @@ -136,38 +122,79 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("KPS didn't changed", () => calculator!.Value == 10); } - private void seek(double time) => AddStep($"Seek main clock to {time}ms", () => mainClock?.Seek(time)); - - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => - (mainClock?.UnderlyingClock as MockFrameBasedClock)!.Rate = rate); - - private void advanceForwards(int frames = 1) => AddStep($"Advance main clock {frames} frame(s) forward.", () => + private void seekAllClocks(double time) { - if (mainClock == null) return; + gameplayClockContainer?.Seek(time); + manualClock!.CurrentTime = time; + } - MockFrameBasedClock underlyingClock = (MockFrameBasedClock)mainClock.UnderlyingClock; - underlyingClock.Backwards = false; + protected override Ruleset CreateRuleset() => new OsuRuleset(); - for (int i = 0; i < frames; i++) + #region Quick steps methods + + private void createCalculator() + { + AddStep("create calculator", () => { - underlyingClock.ProcessFrame(); - } + Debug.Assert(dependencyContainer?.Dependencies.Get(typeof(DrawableRuleset)) is DrawableRuleset); + dependencyContainer!.Children = new Drawable[] + { + calculator = new ClicksPerSecondCalculator(), + new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } + } + }; + calculator!.Listener = listener = new ManualInputListener(calculator!); + }); + } + + private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); + + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock!.Rate = rate); + + private void advanceForwards(double time) => + AddStep($"Advance clocks {time} seconds forward.", () => + { + gameplayClockContainer!.Seek(gameplayClockContainer.CurrentTime + time * manualClock!.Rate); + + for (int i = 0; i < time; i++) + { + frameStableClock?.ProcessFrame(); + } + }); + + private void startClock() => AddStep("Start clocks", () => + { + gameplayClockContainer?.Start(); + manualClock!.Rate = 1; }); + #endregion + + #region Input generation + private void addInputs(IEnumerable inputs) { - Debug.Assert(mainClock != null && listener != null); + Debug.Assert(manualClock != null && listener != null && gameplayClockContainer != null); if (!inputs.Any()) return; - double baseTime = mainClock.CurrentTime; + double baseTime = gameplayClockContainer.CurrentTime; foreach (double timestamp in inputs) { - mainClock.Seek(timestamp); + seekAllClocks(timestamp); listener.AddInput(); } - mainClock.Seek(baseTime); + seekAllClocks(baseTime); } private IEnumerable generateGraduallyIncreasingKps() @@ -200,9 +227,52 @@ namespace osu.Game.Tests.Visual.Gameplay } } - protected override Ruleset CreateRuleset() => new ManiaRuleset(); + #endregion - #region Mock classes + #region Test classes + + private class TestFrameStableClock : IFrameStableClock + { + public TestFrameStableClock(IClock source, double startTime = 0) + { + this.source = source; + StartTime = startTime; + + if (source is ManualClock manualClock) + { + manualClock.CurrentTime = startTime; + } + } + + public double CurrentTime => source.CurrentTime; + public double Rate => source.Rate; + public bool IsRunning => source.IsRunning; + + private IClock source; + + public void ProcessFrame() + { + if (source is ManualClock manualClock) + { + manualClock.CurrentTime += 1000 * Rate; + } + + TimeInfo = new FrameTimeInfo + { + Elapsed = 1000 * Rate, + Current = CurrentTime + }; + } + + public double ElapsedFrameTime => TimeInfo.Elapsed; + public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; + public FrameTimeInfo TimeInfo { get; private set; } + + public double? StartTime { get; } + public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + public IBindable IsCatchingUp => new Bindable(); + public IBindable WaitingOnFrames => new Bindable(); + } private class ManualInputListener : ClicksPerSecondCalculator.InputListener { @@ -214,108 +284,54 @@ namespace osu.Game.Tests.Visual.Gameplay } } - private class MockFrameBasedClock : ManualClock, IFrameBasedClock +#nullable disable + + [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] + private class TestDrawableRuleset : DrawableRuleset { - public const double FRAME_INTERVAL = 1000; - public bool Backwards; + public override IEnumerable Objects => Enumerable.Empty(); - public MockFrameBasedClock() + public override event Action NewResult { - Rate = 1; - IsRunning = true; + add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); } - public void ProcessFrame() + public override event Action RevertResult { - CurrentTime += FRAME_INTERVAL * Rate * (Backwards ? -1 : 1); - TimeInfo = new FrameTimeInfo - { - Current = CurrentTime, - Elapsed = FRAME_INTERVAL * Rate * (Backwards ? -1 : 1) - }; + add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); + remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } - public void Seek(double time) - { - TimeInfo = new FrameTimeInfo - { - Elapsed = time - CurrentTime, - Current = CurrentTime = time - }; - } - - public double ElapsedFrameTime => TimeInfo.Elapsed; - public double FramesPerSecond => 1 / FRAME_INTERVAL; - public FrameTimeInfo TimeInfo { get; private set; } - } - - private class MockFrameStableClock : IGameplayClock, IFrameStableClock - { - internal readonly IFrameBasedClock UnderlyingClock; - - public readonly BindableBool IsPaused = new BindableBool(); - - public MockFrameStableClock(MockFrameBasedClock underlyingClock) - { - UnderlyingClock = underlyingClock; - } - - public void Seek(double time) => (UnderlyingClock as MockFrameBasedClock)?.Seek(time); - - public IBindable IsCatchingUp => new Bindable(); - public IBindable WaitingOnFrames => new Bindable(); - public double CurrentTime => UnderlyingClock.CurrentTime; - public double Rate => UnderlyingClock.Rate; - public bool IsRunning => UnderlyingClock.IsRunning; - public void ProcessFrame() => UnderlyingClock.ProcessFrame(); - - public double ElapsedFrameTime => UnderlyingClock.ElapsedFrameTime; - public double FramesPerSecond => UnderlyingClock.FramesPerSecond; - public FrameTimeInfo TimeInfo => UnderlyingClock.TimeInfo; - public double TrueGameplayRate => UnderlyingClock.Rate; - public double? StartTime => 0; - public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); - IBindable IGameplayClock.IsPaused => IsPaused; - } - - private class MockDrawableRuleset : DrawableRuleset - { - public MockDrawableRuleset(Ruleset ruleset, IFrameStableClock clock) - : base(ruleset) - { - FrameStableClock = clock; - } - -#pragma warning disable CS0067 - public override event Action? NewResult; - public override event Action? RevertResult; -#pragma warning restore CS0067 - public override Playfield? Playfield => null; - public override Container? Overlays => null; - public override Container? FrameStableComponents => null; + public override Playfield Playfield => null; + public override Container Overlays => null; + public override Container FrameStableComponents => null; public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } public override IReadOnlyList Mods => Array.Empty(); - public override IEnumerable Objects => Array.Empty(); + public override double GameplayStartTime => 0; - public override GameplayCursorContainer? Cursor => null; + public override GameplayCursorContainer Cursor => null; - public override void SetReplayScore(Score replayScore) + public TestDrawableRuleset() + : base(new OsuRuleset()) { } - public override void SetRecordTarget(Score score) + public TestDrawableRuleset(IFrameStableClock frameStableClock) + : this() { + FrameStableClock = frameStableClock; } - public override void RequestResume(Action continueResume) - { - } + public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - public override void CancelResume() - { - } + public override void SetRecordTarget(Score score) => throw new NotImplementedException(); + + public override void RequestResume(Action continueResume) => throw new NotImplementedException(); + + public override void CancelResume() => throw new NotImplementedException(); } #endregion From b5970495242d9555fad30c52a33b1e66f3f3a4e9 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Mon, 22 Aug 2022 10:47:37 +0200 Subject: [PATCH 062/174] Code cleanup for CPS tests - Remove null-forgiving operator usages - Fix code quality issues mentionned by NVika --- .../Gameplay/TestSceneClicksPerSecond.cs | 48 +++++++++---------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 375726dd9a..137ab7acdb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -27,11 +27,11 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneClicksPerSecond : OsuTestScene { - private DependencyProvidingContainer? dependencyContainer; - private ClicksPerSecondCalculator? calculator; + private DependencyProvidingContainer dependencyContainer = null!; + private ClicksPerSecondCalculator calculator = null!; private ManualInputListener? listener; - private GameplayClockContainer? gameplayClockContainer; - private ManualClock? manualClock; + private GameplayClockContainer gameplayClockContainer = null!; + private ManualClock manualClock = null!; private DrawableRuleset? drawableRuleset; private IFrameStableClock? frameStableClock; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay seek(i * 10000); advanceForwards(2); int kps = i + 1; - AddAssert($"{kps} KPS", () => calculator!.Value == kps); + AddAssert($"{kps} KPS", () => calculator.Value == kps); } } @@ -95,14 +95,14 @@ namespace osu.Game.Tests.Visual.Gameplay { changeRate(i); double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); } for (double i = 1; i >= 0.5; i -= 0.25) { changeRate(i); double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator!.Value, 10 * rate, 0.5)); + AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); } } @@ -115,17 +115,17 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); - AddAssert("KPS = 10", () => calculator!.Value == 10); + AddAssert("KPS = 10", () => calculator.Value == 10); AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); seek(1000); - AddAssert("KPS didn't changed", () => calculator!.Value == 10); + AddAssert("KPS didn't changed", () => calculator.Value == 10); } private void seekAllClocks(double time) { - gameplayClockContainer?.Seek(time); - manualClock!.CurrentTime = time; + gameplayClockContainer.Seek(time); + manualClock.CurrentTime = time; } protected override Ruleset CreateRuleset() => new OsuRuleset(); @@ -136,8 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("create calculator", () => { - Debug.Assert(dependencyContainer?.Dependencies.Get(typeof(DrawableRuleset)) is DrawableRuleset); - dependencyContainer!.Children = new Drawable[] + dependencyContainer.Children = new Drawable[] { calculator = new ClicksPerSecondCalculator(), new DependencyProvidingContainer @@ -152,18 +151,18 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; - calculator!.Listener = listener = new ManualInputListener(calculator!); + calculator.Listener = listener = new ManualInputListener(calculator); }); } private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock!.Rate = rate); + private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock.Rate = rate); private void advanceForwards(double time) => AddStep($"Advance clocks {time} seconds forward.", () => { - gameplayClockContainer!.Seek(gameplayClockContainer.CurrentTime + time * manualClock!.Rate); + gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime + time * manualClock.Rate); for (int i = 0; i < time; i++) { @@ -173,8 +172,8 @@ namespace osu.Game.Tests.Visual.Gameplay private void startClock() => AddStep("Start clocks", () => { - gameplayClockContainer?.Start(); - manualClock!.Rate = 1; + gameplayClockContainer.Start(); + manualClock.Rate = 1; }); #endregion @@ -183,7 +182,6 @@ namespace osu.Game.Tests.Visual.Gameplay private void addInputs(IEnumerable inputs) { - Debug.Assert(manualClock != null && listener != null && gameplayClockContainer != null); if (!inputs.Any()) return; double baseTime = gameplayClockContainer.CurrentTime; @@ -191,7 +189,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (double timestamp in inputs) { seekAllClocks(timestamp); - listener.AddInput(); + listener?.AddInput(); } seekAllClocks(baseTime); @@ -199,7 +197,7 @@ namespace osu.Game.Tests.Visual.Gameplay private IEnumerable generateGraduallyIncreasingKps() { - IEnumerable? final = null; + IEnumerable final = null!; for (int i = 1; i <= 10; i++) { @@ -211,10 +209,10 @@ namespace osu.Game.Tests.Visual.Gameplay continue; } - final = final!.Concat(currentKps); + final = final.Concat(currentKps); } - return final!; + return final; } private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) @@ -236,7 +234,6 @@ namespace osu.Game.Tests.Visual.Gameplay public TestFrameStableClock(IClock source, double startTime = 0) { this.source = source; - StartTime = startTime; if (source is ManualClock manualClock) { @@ -248,7 +245,7 @@ namespace osu.Game.Tests.Visual.Gameplay public double Rate => source.Rate; public bool IsRunning => source.IsRunning; - private IClock source; + private readonly IClock source; public void ProcessFrame() { @@ -268,7 +265,6 @@ namespace osu.Game.Tests.Visual.Gameplay public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; public FrameTimeInfo TimeInfo { get; private set; } - public double? StartTime { get; } public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); public IBindable IsCatchingUp => new Bindable(); public IBindable WaitingOnFrames => new Bindable(); From 1098e24c40afef4ee684706d2050e0e9dae9bd61 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 22 Aug 2022 14:24:52 +0100 Subject: [PATCH 063/174] Improved UprightUnscaledContainer --- osu.Game/Extensions/DrawableExtensions.cs | 36 ---------- .../Graphics/Containers/GrowToFitContainer.cs | 21 ------ .../Containers/UprightUnscaledContainer.cs | 66 ++++++++++++++++++- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 33 +++------- 4 files changed, 73 insertions(+), 83 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/GrowToFitContainer.cs diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 320b9d7996..35f2d61437 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -8,7 +8,6 @@ using osu.Game.Configuration; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK; -using System; namespace osu.Game.Extensions { @@ -80,40 +79,5 @@ namespace osu.Game.Extensions container.Add(child.CreateInstance()); } } - - /// - /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. - /// - /// The drawable. - public static void KeepUprightAndUnscaled(this Drawable drawable) - { - // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. - var parentMatrix = drawable.Parent.DrawInfo.Matrix; - parentMatrix.Transpose(); - - // Remove Translation. - parentMatrix.M13 = 0.0f; - parentMatrix.M23 = 0.0f; - - Matrix3 C = parentMatrix.Inverted(); - - // Extract the rotation. - float angle = MathF.Atan2(C.M21, C.M11); - drawable.Rotation = MathHelper.RadiansToDegrees(angle); - - // Remove rotation from the C matrix so that it only contains shear and scale. - Matrix3 m = Matrix3.CreateRotationZ(-angle); - m.Transpose(); - C = m * C; - - // Extract shear and scale. - float alpha, sx, sy; - sx = C.M11; - sy = C.M22; - alpha = C.M12 / C.M22; - - drawable.Scale = new Vector2(sx, sy); - drawable.Shear = new Vector2(-alpha, 0); - } } } diff --git a/osu.Game/Graphics/Containers/GrowToFitContainer.cs b/osu.Game/Graphics/Containers/GrowToFitContainer.cs deleted file mode 100644 index 9b4ad0dba9..0000000000 --- a/osu.Game/Graphics/Containers/GrowToFitContainer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Containers; - -namespace osu.Game.Graphics.Containers -{ - /// - /// A container that grows in size to fit its child and retains its size when its child shrinks - /// - public class GrowToFitContainer : Container - { - protected override void Update() - { - base.Update(); - Height = Math.Max(Child.Height, Height); - Width = Math.Max(Child.Width, Width); - } - } -} diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index cdb839fdec..b6c3b56c4a 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -4,6 +4,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Layout; +using osuTK; +using System; namespace osu.Game.Graphics.Containers { @@ -12,21 +14,81 @@ namespace osu.Game.Graphics.Containers /// public class UprightUnscaledContainer : Container { + protected override Container Content => content; + private readonly Container content; + public UprightUnscaledContainer() { + InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } - private LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); protected override void Update() { base.Update(); + if (!layout.IsValid) { - Extensions.DrawableExtensions.KeepUprightAndUnscaled(this); + keepUprightAndUnscaled(); layout.Validate(); } } + + /// + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. + /// + private void keepUprightAndUnscaled() + { + // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. + var parentMatrix = Parent.DrawInfo.Matrix; + + // Remove Translation. + parentMatrix.M31 = 0.0f; + parentMatrix.M32 = 0.0f; + + Matrix3 reversedParrent = parentMatrix.Inverted(); + + // Extract the rotation. + float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + reversedParrent *= m; + + // Extract shear and scale. + float sx = reversedParrent.M11; + float sy = reversedParrent.M22; + float alpha = reversedParrent.M21 / reversedParrent.M22; + + Scale = new Vector2(sx, sy); + Shear = new Vector2(-alpha, 0); + } + + /// + /// A container that grows in size to fit its children and retains its size when its children shrink + /// + private class GrowToFitContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public GrowToFitContainer() + { + InternalChild = content = new Container + { + AutoSizeAxes = Axes.Both, + }; + } + + protected override void Update() + { + base.Update(); + Height = Math.Max(content.Height, Height); + Width = Math.Max(content.Width, Width); + } + } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 069492fd80..c4cdc0cb29 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -60,17 +60,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeCurrent = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } }, @@ -84,17 +79,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = progress = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = progress = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } }, @@ -108,17 +98,12 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new GrowToFitContainer + Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Child = timeLeft = new OsuSpriteText - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Colour = colours.BlueLighter, - Font = OsuFont.Numeric, - } + Colour = colours.BlueLighter, + Font = OsuFont.Numeric, } } } From f3847b90fddaa6418c457a68ddd2912f637b0443 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Aug 2022 19:36:01 +0900 Subject: [PATCH 064/174] Tidy up attach logic --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 ++++--- osu.Game/Rulesets/UI/RulesetInputManager.cs | 23 +++++++++------------ osu.Game/Screens/Play/HUDOverlay.cs | 7 +++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 02df4d8fb3..73acb1759f 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.UI /// Displays an interactive ruleset gameplay instance. /// /// The type of HitObject contained by this DrawableRuleset. - public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachHUDPieces where TObject : HitObject { public override event Action NewResult; @@ -339,9 +339,10 @@ namespace osu.Game.Rulesets.UI public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); public void Attach(KeyCounterDisplay keyCounter) => - (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); - public void Attach(ClicksPerSecondCalculator calculator) => (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(calculator); + public void Attach(ClicksPerSecondCalculator calculator) => + (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); /// /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 4b7ce22cfc..401ebbfd74 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -25,7 +25,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.UI { - public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler + public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachHUDPieces, IHasReplayHandler, IHasRecordingHandler where T : struct { public readonly KeyBindingContainer KeyBindingContainer; @@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.UI .Select(action => new KeyCounterAction(action))); } - public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler + private class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { public ActionReceptor(KeyCounterDisplay target) : base(target) @@ -191,8 +191,6 @@ namespace osu.Game.Rulesets.UI public void Attach(ClicksPerSecondCalculator calculator) { - if (calculator == null) return; - var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); @@ -200,23 +198,22 @@ namespace osu.Game.Rulesets.UI calculator.Listener = listener; } - public class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler + private class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler { + public ActionListener(ClicksPerSecondCalculator calculator) + : base(calculator) + { + } + public bool OnPressed(KeyBindingPressEvent e) { Calculator.AddTimestamp(); - return false; } public void OnReleased(KeyBindingReleaseEvent e) { } - - public ActionListener(ClicksPerSecondCalculator calculator) - : base(calculator) - { - } } #endregion @@ -256,10 +253,10 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching various HUD pieces. /// Keys will be populated automatically and a receptor will be injected inside. /// - public interface ICanAttachKeyCounter + public interface ICanAttachHUDPieces { void Attach(KeyCounterDisplay keyCounter); void Attach(ClicksPerSecondCalculator calculator); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b27efaf13a..f9f3693385 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -264,8 +264,11 @@ namespace osu.Game.Screens.Play protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - (drawableRuleset as ICanAttachKeyCounter)?.Attach(clicksPerSecondCalculator); + if (drawableRuleset is ICanAttachHUDPieces attachTarget) + { + attachTarget.Attach(KeyCounter); + attachTarget.Attach(clicksPerSecondCalculator); + } replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } From 5cddc7ed1f8fadccedccc4b6efe0a836d1ec3037 Mon Sep 17 00:00:00 2001 From: Ryuki Date: Wed, 24 Aug 2022 17:12:52 +0200 Subject: [PATCH 065/174] Code cleanup (CPS) --- .../Gameplay/TestSceneClicksPerSecond.cs | 31 ++---- osu.Game/Rulesets/UI/RulesetInputManager.cs | 10 +- .../ClicksPerSecondCalculator.cs | 96 ++++++------------- .../ClicksPerSecond/ClicksPerSecondCounter.cs | 6 +- 4 files changed, 42 insertions(+), 101 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index 137ab7acdb..a140460251 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -29,7 +29,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private DependencyProvidingContainer dependencyContainer = null!; private ClicksPerSecondCalculator calculator = null!; - private ManualInputListener? listener; private GameplayClockContainer gameplayClockContainer = null!; private ManualClock manualClock = null!; private DrawableRuleset? drawableRuleset; @@ -151,7 +150,6 @@ namespace osu.Game.Tests.Visual.Gameplay } } }; - calculator.Listener = listener = new ManualInputListener(calculator); }); } @@ -189,7 +187,7 @@ namespace osu.Game.Tests.Visual.Gameplay foreach (double timestamp in inputs) { seekAllClocks(timestamp); - listener?.AddInput(); + calculator.AddTimestamp(); } seekAllClocks(baseTime); @@ -270,18 +268,6 @@ namespace osu.Game.Tests.Visual.Gameplay public IBindable WaitingOnFrames => new Bindable(); } - private class ManualInputListener : ClicksPerSecondCalculator.InputListener - { - public void AddInput() => Calculator.AddTimestamp(); - - public ManualInputListener(ClicksPerSecondCalculator calculator) - : base(calculator) - { - } - } - -#nullable disable - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] private class TestDrawableRuleset : DrawableRuleset { @@ -299,24 +285,19 @@ namespace osu.Game.Tests.Visual.Gameplay remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); } - public override Playfield Playfield => null; - public override Container Overlays => null; - public override Container FrameStableComponents => null; + public override Playfield Playfield => null!; + public override Container Overlays => null!; + public override Container FrameStableComponents => null!; public override IFrameStableClock FrameStableClock { get; } internal override bool FrameStablePlayback { get; set; } public override IReadOnlyList Mods => Array.Empty(); public override double GameplayStartTime => 0; - public override GameplayCursorContainer Cursor => null; - - public TestDrawableRuleset() - : base(new OsuRuleset()) - { - } + public override GameplayCursorContainer Cursor => null!; public TestDrawableRuleset(IFrameStableClock frameStableClock) - : this() + : base(new OsuRuleset()) { FrameStableClock = frameStableClock; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 401ebbfd74..dcd2c4fb5d 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -194,20 +194,20 @@ namespace osu.Game.Rulesets.UI var listener = new ActionListener(calculator); KeyBindingContainer.Add(listener); - - calculator.Listener = listener; } - private class ActionListener : ClicksPerSecondCalculator.InputListener, IKeyBindingHandler + private class ActionListener : Component, IKeyBindingHandler { + private readonly ClicksPerSecondCalculator calculator; + public ActionListener(ClicksPerSecondCalculator calculator) - : base(calculator) { + this.calculator = calculator; } public bool OnPressed(KeyBindingPressEvent e) { - Calculator.AddTimestamp(); + calculator.AddTimestamp(); return false; } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index d9c3c6ffec..20ac923b82 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -1,12 +1,10 @@ // 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.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Timing; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Play.HUD.ClicksPerSecond @@ -15,89 +13,53 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { private readonly List timestamps; - private InputListener? listener; + [Resolved] + private IGameplayClock gameplayClock { get; set; } = null!; [Resolved] - private IGameplayClock? gameplayClock { get; set; } + private DrawableRuleset drawableRuleset { get; set; } = null!; - [Resolved(canBeNull: true)] - private DrawableRuleset? drawableRuleset { get; set; } + private double rate; - public InputListener Listener - { - set - { - onResetRequested?.Invoke(); - listener = value; - } - } + // The latest timestamp GC seeked. Does not affect normal gameplay + // but prevents duplicate inputs on replays. + private double latestTime = double.NegativeInfinity; - private event Action? onResetRequested; - - private IClock? workingClock => drawableRuleset?.FrameStableClock; - - private double baseRate; - - private double rate - { - get - { - if (gameplayClock?.TrueGameplayRate > 0) - { - baseRate = gameplayClock.TrueGameplayRate; - } - - return baseRate; - } - } - - private double maxTime = double.NegativeInfinity; - - public bool Ready => workingClock != null && gameplayClock != null && listener != null; - public int Value => timestamps.Count(isTimestampWithinSpan); + public int Value { get; private set; } public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; timestamps = new List(); - onResetRequested += cleanUp; } - private void cleanUp() + protected override void Update() { - timestamps.Clear(); - maxTime = double.NegativeInfinity; + base.Update(); + + // When pausing in replays (using the space bar) GC.TrueGameplayRate returns 0 + // To prevent CPS value being 0, we store and use the last non-zero TrueGameplayRate + if (gameplayClock.TrueGameplayRate > 0) + { + rate = gameplayClock.TrueGameplayRate; + } + + Value = timestamps.Count(timestamp => + { + double window = 1000 * rate; + double relativeTime = drawableRuleset.FrameStableClock.CurrentTime - timestamp; + return relativeTime > 0 && relativeTime <= window; + }); } public void AddTimestamp() { - if (workingClock == null) return; - - if (workingClock.CurrentTime >= maxTime) + // Discard inputs if current gameplay time is not the latest + // to prevent duplicate inputs + if (drawableRuleset.FrameStableClock.CurrentTime >= latestTime) { - timestamps.Add(workingClock.CurrentTime); - maxTime = workingClock.CurrentTime; - } - } - - private bool isTimestampWithinSpan(double timestamp) - { - if (workingClock == null) return false; - - double span = 1000 * rate; - double relativeTime = workingClock.CurrentTime - timestamp; - return relativeTime > 0 && relativeTime <= span; - } - - public abstract class InputListener : Component - { - protected ClicksPerSecondCalculator Calculator; - - protected InputListener(ClicksPerSecondCalculator calculator) - { - RelativeSizeAxes = Axes.Both; - Depth = float.MinValue; - Calculator = calculator; + timestamps.Add(drawableRuleset.FrameStableClock.CurrentTime); + latestTime = drawableRuleset.FrameStableClock.CurrentTime; } } } diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs index 3ff06d5217..243d8ed1e8 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCounter.cs @@ -16,9 +16,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { public class ClicksPerSecondCounter : RollingCounter, ISkinnableDrawable { - private const float alpha_when_invalid = 0.3f; - - [Resolved(canBeNull: false)] + [Resolved] private ClicksPerSecondCalculator calculator { get; set; } = null!; protected override double RollingDuration => 350; @@ -40,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { base.Update(); - Current.Value = calculator.Ready ? calculator.Value : 0; + Current.Value = calculator.Value; } protected override IHasText CreateText() => new TextComponent(); From 29f3724047ec5e457a89f797f20689ac2e87ef54 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 01:28:57 +0100 Subject: [PATCH 066/174] Changed UprightUnscaledContainer to UprightUnstretchedContainer. --- .../Containers/UprightUnscaledContainer.cs | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index b6c3b56c4a..c64e4ee2b7 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -12,13 +12,24 @@ namespace osu.Game.Graphics.Containers /// /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. /// - public class UprightUnscaledContainer : Container + public class UprightUnstretchedContainer : Container { protected override Container Content => content; private readonly Container content; - public UprightUnscaledContainer() + /// + /// Controls how much this container scales compared to its parent (default is 1.0f). + /// + public float ScalingFactor { get; set; } + + /// + /// Controls the scaling of this container. + /// + public ScaleMode Scaling { get; set; } + + public UprightUnstretchedContainer() { + Scaling = ScaleMode.NoScaling; InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } @@ -31,7 +42,7 @@ namespace osu.Game.Graphics.Containers if (!layout.IsValid) { - keepUprightAndUnscaled(); + keepUprightAndUnstretched(); layout.Validate(); } } @@ -39,7 +50,7 @@ namespace osu.Game.Graphics.Containers /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// - private void keepUprightAndUnscaled() + private void keepUprightAndUnstretched() { // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; @@ -58,13 +69,32 @@ namespace osu.Game.Graphics.Containers Matrix3 m = Matrix3.CreateRotationZ(-angle); reversedParrent *= m; - // Extract shear and scale. + // Extract shear. + float alpha = reversedParrent.M21 / reversedParrent.M22; + Shear = new Vector2(-alpha, 0); + + // Etract scale. float sx = reversedParrent.M11; float sy = reversedParrent.M22; - float alpha = reversedParrent.M21 / reversedParrent.M22; - Scale = new Vector2(sx, sy); - Shear = new Vector2(-alpha, 0); + Vector3 parentScale = parentMatrix.ExtractScale(); + + float usedScale = 1.0f; + + switch (Scaling) + { + case ScaleMode.Horizontal: + usedScale = parentScale.X; + break; + + case ScaleMode.Vertical: + usedScale = parentScale.Y; + break; + } + + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + + Scale = new Vector2(sx * usedScale, sy * usedScale); } /// @@ -91,4 +121,24 @@ namespace osu.Game.Graphics.Containers } } } + + public enum ScaleMode + { + /// + /// Prevent this container from scaling. + /// + NoScaling, + + /// + /// Scale This container (vertically and horizontally) with the vertical axis of its parent + /// preserving the aspect ratio of the container. + /// + Vertical, + + /// + /// Scales This container (vertically and horizontally) with the horizontal axis of its parent + /// preserving the aspect ratio of the container. + /// + Horizontal, + } } From d98357aa5748d5a6ee400925684dc45a7bcdaec6 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 01:30:44 +0100 Subject: [PATCH 067/174] Made text inside SongProgressInfo scale. --- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index c4cdc0cb29..83fe8b14c3 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -55,11 +55,13 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = timeCurrent = new OsuSpriteText { Origin = Anchor.Centre, @@ -74,11 +76,13 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = progress = new OsuSpriteText { Origin = Anchor.Centre, @@ -93,11 +97,13 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = new UprightUnscaledContainer + Child = new UprightUnstretchedContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, + Scaling = ScaleMode.Vertical, + ScalingFactor = 0.5f, Child = timeLeft = new OsuSpriteText { Origin = Anchor.Centre, From 23efec65054946ca62f6b7f3b1473f9b55258c5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 15:56:47 +0900 Subject: [PATCH 068/174] Fix naming and comment typos --- .../Containers/UprightUnscaledContainer.cs | 19 ++++++++----------- osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 6 +++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index c64e4ee2b7..4d9630052a 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -10,9 +10,9 @@ using System; namespace osu.Game.Graphics.Containers { /// - /// A container that prevents itself and its children from getting rotated, scaled or flipped with its Parent. + /// A container that reverts any rotation (and optionally scale) applied by its direct parent. /// - public class UprightUnstretchedContainer : Container + public class UprightAspectMaintainingContainer : Container { protected override Container Content => content; private readonly Container content; @@ -20,16 +20,15 @@ namespace osu.Game.Graphics.Containers /// /// Controls how much this container scales compared to its parent (default is 1.0f). /// - public float ScalingFactor { get; set; } + public float ScalingFactor { get; set; } = 1; /// /// Controls the scaling of this container. /// - public ScaleMode Scaling { get; set; } + public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - public UprightUnstretchedContainer() + public UprightAspectMaintainingContainer() { - Scaling = ScaleMode.NoScaling; InternalChild = content = new GrowToFitContainer(); AddLayout(layout); } @@ -52,7 +51,7 @@ namespace osu.Game.Graphics.Containers /// private void keepUprightAndUnstretched() { - // Decomposes the inverse of the parent FrawInfo.Matrix into rotation, shear and scale. + // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; // Remove Translation. @@ -130,14 +129,12 @@ namespace osu.Game.Graphics.Containers NoScaling, /// - /// Scale This container (vertically and horizontally) with the vertical axis of its parent - /// preserving the aspect ratio of the container. + /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. /// Vertical, /// - /// Scales This container (vertically and horizontally) with the horizontal axis of its parent - /// preserving the aspect ratio of the container. + /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. /// Horizontal, } diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 83fe8b14c3..36ec16a607 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.Centre, Anchor = Anchor.Centre, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Play.HUD Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = new UprightUnstretchedContainer + Child = new UprightAspectMaintainingContainer { Origin = Anchor.Centre, Anchor = Anchor.Centre, From a40355186a1c8434535a7ccf286ee92e82c2d39d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 16:00:20 +0900 Subject: [PATCH 069/174] Tidy up constructor field initialisation --- osu.Game/Graphics/Containers/UprightUnscaledContainer.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index 4d9630052a..d2cd7d3373 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -14,8 +14,7 @@ namespace osu.Game.Graphics.Containers /// public class UprightAspectMaintainingContainer : Container { - protected override Container Content => content; - private readonly Container content; + protected override Container Content { get; } /// /// Controls how much this container scales compared to its parent (default is 1.0f). @@ -27,14 +26,14 @@ namespace osu.Game.Graphics.Containers /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + public UprightAspectMaintainingContainer() { - InternalChild = content = new GrowToFitContainer(); + AddInternal(Content = new GrowToFitContainer()); AddLayout(layout); } - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); - protected override void Update() { base.Update(); From d6359b00ad5022062f9469cb2103d4229ce38d88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 26 Aug 2022 19:20:36 +0900 Subject: [PATCH 070/174] Fix filename mismatch --- .../UprightAspectMaintainingContainer.cs | 122 ++++++++++++++++++ .../Containers/UprightUnscaledContainer.cs | 117 ----------------- 2 files changed, 122 insertions(+), 117 deletions(-) create mode 100644 osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs new file mode 100644 index 0000000000..e556117bc0 --- /dev/null +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -0,0 +1,122 @@ +// 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.Layout; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that reverts any rotation (and optionally scale) applied by its direct parent. + /// + public class UprightAspectMaintainingContainer : Container + { + protected override Container Content { get; } + + /// + /// Controls how much this container scales compared to its parent (default is 1.0f). + /// + public float ScalingFactor { get; set; } = 1; + + /// + /// Controls the scaling of this container. + /// + public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); + + public UprightAspectMaintainingContainer() + { + AddInternal(Content = new GrowToFitContainer()); + AddLayout(layout); + } + + protected override void Update() + { + base.Update(); + + if (!layout.IsValid) + { + keepUprightAndUnstretched(); + layout.Validate(); + } + } + + /// + /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. + /// + private void keepUprightAndUnstretched() + { + // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. + var parentMatrix = Parent.DrawInfo.Matrix; + + // Remove Translation. + parentMatrix.M31 = 0.0f; + parentMatrix.M32 = 0.0f; + + Matrix3 reversedParrent = parentMatrix.Inverted(); + + // Extract the rotation. + float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + Rotation = MathHelper.RadiansToDegrees(angle); + + // Remove rotation from the C matrix so that it only contains shear and scale. + Matrix3 m = Matrix3.CreateRotationZ(-angle); + reversedParrent *= m; + + // Extract shear. + float alpha = reversedParrent.M21 / reversedParrent.M22; + Shear = new Vector2(-alpha, 0); + + // Etract scale. + float sx = reversedParrent.M11; + float sy = reversedParrent.M22; + + Vector3 parentScale = parentMatrix.ExtractScale(); + + float usedScale = 1.0f; + + switch (Scaling) + { + case ScaleMode.Horizontal: + usedScale = parentScale.X; + break; + + case ScaleMode.Vertical: + usedScale = parentScale.Y; + break; + } + + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + + Scale = new Vector2(sx * usedScale, sy * usedScale); + } + + /// + /// A container that grows in size to fit its children and retains its size when its children shrink + /// + private class GrowToFitContainer : Container + { + protected override Container Content => content; + private readonly Container content; + + public GrowToFitContainer() + { + InternalChild = content = new Container + { + AutoSizeAxes = Axes.Both, + }; + } + + protected override void Update() + { + base.Update(); + Height = Math.Max(content.Height, Height); + Width = Math.Max(content.Width, Width); + } + } + } +} diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs index d2cd7d3373..d801f4a215 100644 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs @@ -1,125 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Layout; -using osuTK; -using System; - namespace osu.Game.Graphics.Containers { - /// - /// A container that reverts any rotation (and optionally scale) applied by its direct parent. - /// - public class UprightAspectMaintainingContainer : Container - { - protected override Container Content { get; } - - /// - /// Controls how much this container scales compared to its parent (default is 1.0f). - /// - public float ScalingFactor { get; set; } = 1; - - /// - /// Controls the scaling of this container. - /// - public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); - - public UprightAspectMaintainingContainer() - { - AddInternal(Content = new GrowToFitContainer()); - AddLayout(layout); - } - - protected override void Update() - { - base.Update(); - - if (!layout.IsValid) - { - keepUprightAndUnstretched(); - layout.Validate(); - } - } - - /// - /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. - /// - private void keepUprightAndUnstretched() - { - // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. - var parentMatrix = Parent.DrawInfo.Matrix; - - // Remove Translation. - parentMatrix.M31 = 0.0f; - parentMatrix.M32 = 0.0f; - - Matrix3 reversedParrent = parentMatrix.Inverted(); - - // Extract the rotation. - float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); - Rotation = MathHelper.RadiansToDegrees(angle); - - // Remove rotation from the C matrix so that it only contains shear and scale. - Matrix3 m = Matrix3.CreateRotationZ(-angle); - reversedParrent *= m; - - // Extract shear. - float alpha = reversedParrent.M21 / reversedParrent.M22; - Shear = new Vector2(-alpha, 0); - - // Etract scale. - float sx = reversedParrent.M11; - float sy = reversedParrent.M22; - - Vector3 parentScale = parentMatrix.ExtractScale(); - - float usedScale = 1.0f; - - switch (Scaling) - { - case ScaleMode.Horizontal: - usedScale = parentScale.X; - break; - - case ScaleMode.Vertical: - usedScale = parentScale.Y; - break; - } - - usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; - - Scale = new Vector2(sx * usedScale, sy * usedScale); - } - - /// - /// A container that grows in size to fit its children and retains its size when its children shrink - /// - private class GrowToFitContainer : Container - { - protected override Container Content => content; - private readonly Container content; - - public GrowToFitContainer() - { - InternalChild = content = new Container - { - AutoSizeAxes = Axes.Both, - }; - } - - protected override void Update() - { - base.Update(); - Height = Math.Max(content.Height, Height); - Width = Math.Max(content.Width, Width); - } - } - } - public enum ScaleMode { /// From 24edffcbc47628e49df7e25159d1dbb437f2ddd5 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 26 Aug 2022 12:47:12 +0100 Subject: [PATCH 071/174] Moved ScaleMode to UprightAspectMaintainingContainer.cs --- .../UprightAspectMaintainingContainer.cs | 18 +++++++++++++++ .../Containers/UprightUnscaledContainer.cs | 23 ------------------- 2 files changed, 18 insertions(+), 23 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/UprightUnscaledContainer.cs diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index e556117bc0..9736cba206 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -119,4 +119,22 @@ namespace osu.Game.Graphics.Containers } } } + + public enum ScaleMode + { + /// + /// Prevent this container from scaling. + /// + NoScaling, + + /// + /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. + /// + Vertical, + + /// + /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. + /// + Horizontal, + } } diff --git a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs b/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs deleted file mode 100644 index d801f4a215..0000000000 --- a/osu.Game/Graphics/Containers/UprightUnscaledContainer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Graphics.Containers -{ - public enum ScaleMode - { - /// - /// Prevent this container from scaling. - /// - NoScaling, - - /// - /// Scale uniformly (maintaining aspect ratio) based on the vertical scale of the parent. - /// - Vertical, - - /// - /// Scale uniformly (maintaining aspect ratio) based on the horizontal scale of the parent. - /// - Horizontal, - } -} From d4a52baa56f0eee37c6a50ba65c29d57dc8bc3f9 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 00:07:42 +0100 Subject: [PATCH 072/174] Added visual test for UprightAspectMaintainingContainer --- ...tSceneUprightAspectMaintainingContainer.cs | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs new file mode 100644 index 0000000000..8b87e4030e --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -0,0 +1,245 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK.Graphics; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUprightAspectMaintainingContainer : OsuGridTestScene + { + private const int rows = 3; + private const int columns = 4; + + private readonly ScaleMode[] scaleModeValues = { ScaleMode.NoScaling, ScaleMode.Horizontal, ScaleMode.Vertical }; + private readonly float[] scalingFactorValues = { 1.0f / 3, 1.0f / 2, 1.0f, 1.5f }; + + private readonly List> parentContainers = new List>(rows); + private readonly List> childContainers = new List>(rows); + + // Preferably should be set to (4 * 2^n) + private const int rotation_step_count = 8; + + private readonly List flipStates = new List(); + private readonly List rotationSteps = new List(); + private readonly List scaleSteps = new List(); + + public TestSceneUprightAspectMaintainingContainer() + : base(rows, columns) + { + for (int i = 0; i < rows; i++) + { + parentContainers.Add(new List()); + childContainers.Add(new List()); + + for (int j = 0; j < columns; j++) + { + UprightAspectMaintainingContainer child; + Container parent = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = 80, + Width = 80, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(255, 0, 0, 160), + }, + new OsuSpriteText + { + Text = "Parent", + }, + child = new UprightAspectMaintainingContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + + // These are the parameters being Tested + Scaling = scaleModeValues[i], + ScalingFactor = scalingFactorValues[j], + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0, 0, 255, 160), + }, + new OsuSpriteText + { + Text = "Text", + Font = OsuFont.Numeric, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Padding = new MarginPadding + { + Horizontal = 4, + Vertical = 4, + } + }, + } + } + } + }; + + Container cellInfo = new Container + { + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Scaling: " + scaleModeValues[i].ToString(), + }, + new OsuSpriteText + { + Text = "ScalingFactor: " + scalingFactorValues[j].ToString("0.00"), + Margin = new MarginPadding + { + Top = 15, + }, + }, + }, + }; + + Cell(i * columns + j).Add(cellInfo); + Cell(i * columns + j).Add(parent); + parentContainers[i].Add(parent); + childContainers[i].Add(child); + } + } + + flipStates.AddRange(new[] { 1, -1 }); + rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count))); + scaleSteps.AddRange(new[] { 1, 0.5f, 0.3f, 1.5f, 2.0f }); + } + + [Test] + public void ExplicitlySizedParent() + { + var parentStates = from xFlip in flipStates + from yFlip in flipStates + from xScale in scaleSteps + from yScale in scaleSteps + from rotation in rotationSteps + select new { xFlip, yFlip, xScale, yScale, rotation }; + + foreach (var state in parentStates) + { + Vector2 parentScale = new Vector2(state.xFlip * state.xScale, state.yFlip * state.yScale); + float parentRotation = state.rotation; + + AddStep("S: (" + parentScale.X.ToString("0.00") + ", " + parentScale.Y.ToString("0.00") + "), R: " + parentRotation.ToString("0.00"), () => + { + foreach (List list in parentContainers) + { + foreach (Container container in list) + { + container.Scale = parentScale; + container.Rotation = parentRotation; + } + } + }); + + AddAssert("Check if state is valid", () => + { + foreach (int i in Enumerable.Range(0, parentContainers.Count)) + { + foreach (int j in Enumerable.Range(0, parentContainers[i].Count)) + { + if (!uprightAspectMaintainingContainerStateIsValid(parentContainers[i][j], childContainers[i][j])) + return false; + } + } + + return true; + }); + } + } + + private bool uprightAspectMaintainingContainerStateIsValid(Container parent, UprightAspectMaintainingContainer child) + { + Matrix3 parentMatrix = parent.DrawInfo.Matrix; + Matrix3 childMatrix = child.DrawInfo.Matrix; + Vector3 childScale = childMatrix.ExtractScale(); + Vector3 parentScale = parentMatrix.ExtractScale(); + + // Orientation check + if (!(isNearlyZero(MathF.Abs(childMatrix.M21)) && isNearlyZero(MathF.Abs(childMatrix.M12)))) + return false; + + // flip check + if (!(childMatrix.M11 * childMatrix.M22 > 0)) + return false; + + // Aspect ratio check + if (!isNearlyZero(childScale.X - childScale.Y, 0.0001f)) + return false; + + // ScalingMode check + switch (child.Scaling) + { + case ScaleMode.NoScaling: + if (!(isNearlyZero(childMatrix.M11 - 1.0f) && isNearlyZero(childMatrix.M22 - 1.0f))) + return false; + + break; + + case ScaleMode.Vertical: + if (!(checkScaling(child.ScalingFactor, parentScale.Y, childScale.Y))) + return false; + + break; + + case ScaleMode.Horizontal: + if (!(checkScaling(child.ScalingFactor, parentScale.X, childScale.X))) + return false; + + break; + } + + return true; + } + + private bool checkScaling(float scalingFactor, float parentScale, float childScale) + { + if (scalingFactor <= 1.0f) + { + if (!isNearlyZero(1.0f + (parentScale - 1.0f) * scalingFactor - childScale)) + return false; + } + else if (scalingFactor > 1.0f) + { + if (parentScale < 1.0f) + { + if (!isNearlyZero((parentScale * (1.0f / scalingFactor)) - childScale)) + return false; + } + else if (!isNearlyZero(parentScale * scalingFactor - childScale)) + return false; + } + + return true; + } + + private bool isNearlyZero(float f, float epsilon = 0.00001f) + { + return f < epsilon; + } + } +} From 62210bce4ee3c97c212e9f064ec6631ab271dd10 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 00:08:19 +0100 Subject: [PATCH 073/174] Fixed issues found in UprightAspectMaintainingContainer --- .../UprightAspectMaintainingContainer.cs | 59 ++++++++++--------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 9736cba206..64b9eb409b 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -14,8 +14,6 @@ namespace osu.Game.Graphics.Containers /// public class UprightAspectMaintainingContainer : Container { - protected override Container Content { get; } - /// /// Controls how much this container scales compared to its parent (default is 1.0f). /// @@ -30,7 +28,6 @@ namespace osu.Game.Graphics.Containers public UprightAspectMaintainingContainer() { - AddInternal(Content = new GrowToFitContainer()); AddLayout(layout); } @@ -45,6 +42,14 @@ namespace osu.Game.Graphics.Containers } } + public override void Add(Drawable drawable) + { + base.Add(new GrowToFitContainer + { + Child = drawable + }); + } + /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// @@ -57,23 +62,23 @@ namespace osu.Game.Graphics.Containers parentMatrix.M31 = 0.0f; parentMatrix.M32 = 0.0f; - Matrix3 reversedParrent = parentMatrix.Inverted(); + Matrix3 reversedParent = parentMatrix.Inverted(); // Extract the rotation. - float angle = MathF.Atan2(reversedParrent.M12, reversedParrent.M11); + float angle = MathF.Atan2(reversedParent.M12, reversedParent.M11); Rotation = MathHelper.RadiansToDegrees(angle); // Remove rotation from the C matrix so that it only contains shear and scale. Matrix3 m = Matrix3.CreateRotationZ(-angle); - reversedParrent *= m; + reversedParent *= m; // Extract shear. - float alpha = reversedParrent.M21 / reversedParrent.M22; + float alpha = reversedParent.M21 / reversedParent.M22; Shear = new Vector2(-alpha, 0); // Etract scale. - float sx = reversedParrent.M11; - float sy = reversedParrent.M22; + float sx = reversedParent.M11; + float sy = reversedParent.M22; Vector3 parentScale = parentMatrix.ExtractScale(); @@ -90,32 +95,30 @@ namespace osu.Game.Graphics.Containers break; } - usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + if (Scaling != ScaleMode.NoScaling) + { + if (ScalingFactor < 1.0f) + usedScale = 1.0f + (usedScale - 1.0f) * ScalingFactor; + if (ScalingFactor > 1.0f) + usedScale = (usedScale < 1.0f) ? usedScale * (1.0f / ScalingFactor) : usedScale * ScalingFactor; + } Scale = new Vector2(sx * usedScale, sy * usedScale); } - /// - /// A container that grows in size to fit its children and retains its size when its children shrink - /// - private class GrowToFitContainer : Container + public class GrowToFitContainer : Container { - protected override Container Content => content; - private readonly Container content; - - public GrowToFitContainer() - { - InternalChild = content = new Container - { - AutoSizeAxes = Axes.Both, - }; - } - protected override void Update() { - base.Update(); - Height = Math.Max(content.Height, Height); - Width = Math.Max(content.Width, Width); + if ((Child.RelativeSizeAxes & Axes.X) != 0) + RelativeSizeAxes |= Axes.X; + else + Width = Math.Max(Child.Width, Width); + + if ((Child.RelativeSizeAxes & Axes.Y) != 0) + RelativeSizeAxes |= Axes.Y; + else + Height = Math.Max(Child.Height, Height); } } } From d20e7da2d98365e6e2e75fc239132069964d5344 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 21:03:22 +0100 Subject: [PATCH 074/174] Changed epsilon --- .../TestSceneUprightAspectMaintainingContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index 8b87e4030e..f81f8e8d85 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; @@ -188,7 +189,7 @@ namespace osu.Game.Tests.Visual.UserInterface return false; // Aspect ratio check - if (!isNearlyZero(childScale.X - childScale.Y, 0.0001f)) + if (!isNearlyZero(childScale.X - childScale.Y)) return false; // ScalingMode check @@ -237,7 +238,7 @@ namespace osu.Game.Tests.Visual.UserInterface return true; } - private bool isNearlyZero(float f, float epsilon = 0.00001f) + private bool isNearlyZero(float f, float epsilon = Precision.FLOAT_EPSILON) { return f < epsilon; } From 43f2ba659697370d5e885d2adf784852447f1535 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 22:00:33 +0100 Subject: [PATCH 075/174] Added test scene --- .../TestSceneGrowToFitContent.cs | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs new file mode 100644 index 0000000000..bcff992f9d --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; +using System.Linq; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneGrowToFitContent : OsuGridTestScene + { + private readonly List parentContainers = new List(); + private readonly List childContainers = new List(); + private readonly List texts = new List(); + + public TestSceneGrowToFitContent() + : base(1, 2) + { + for (int i = 0; i < 2; i++) + { + OsuSpriteText text; + UprightAspectMaintainingContainer childContainer; + Container parentContainer = new Container + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Rotation = 45, + Y = -200, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Red, + }, + childContainer = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Blue, + }, + text = new OsuSpriteText + { + Text = "Text", + Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 40), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + }, + } + }, + } + }; + + Container cellInfo = new Container + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding + { + Top = 100, + }, + Child = new OsuSpriteText + { + Text = (i == 0) ? "GrowToFitContent == true" : "GrowToFitContent == false", + Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + }; + + parentContainers.Add(parentContainer); + childContainers.Add(childContainer); + texts.Add(text); + Cell(i).Add(cellInfo); + Cell(i).Add(parentContainer); + } + } + + [Test] + public void TestResizeText() + { + AddStep("reset...", () => + { + childContainers[0].GrowToFitContent = false; + childContainers[1].GrowToFitContent = false; + }); + + AddStep("setup...", () => + { + childContainers[0].GrowToFitContent = true; + childContainers[1].GrowToFitContent = false; + }); + + for (int i = 0; i < 10; i++) + { + AddStep("Add Character", () => + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].Text += "."; + } + }); + } + + for (int i = 0; i < 10; i++) + { + AddStep("Remove Character", () => + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + string text = texts[j].Text.ToString(); + texts[j].Text = text.Remove(text.Length - 1, 1); + } + }); + } + } + + [Test] + public void TestScaleText() + { + AddStep("reset...", () => + { + childContainers[0].GrowToFitContent = false; + childContainers[1].GrowToFitContent = false; + }); + + AddStep("setup...", () => + { + childContainers[0].GrowToFitContent = true; + childContainers[1].GrowToFitContent = false; + }); + + for (int i = 0; i < 1; i++) + { + AddStep("Big text", scaleUp); + + AddWaitStep("wait...", 5); + + AddStep("Small text", scaleDown); + } + } + + private void scaleUp() + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].ScaleTo(new Vector2(2, 2), 1000); + } + } + + private void scaleDown() + { + foreach (int j in Enumerable.Range(0, parentContainers.Count)) + { + texts[j].ScaleTo(new Vector2(1, 1), 1000); + } + } + } +} From cda7faecf798e9ad9242c44b0edcf544fb5d1801 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Mon, 29 Aug 2022 22:01:24 +0100 Subject: [PATCH 076/174] Added GrowToFitContent Parameter. --- .../UprightAspectMaintainingContainer.cs | 86 ++++++++++++++++--- 1 file changed, 73 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 64b9eb409b..8efc29c83c 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -24,6 +24,27 @@ namespace osu.Game.Graphics.Containers ///
public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; + /// + /// If this is true, all wrapper containers will be set to grow with their content + /// and not shrink back, this is used to fix the position of children that change + /// in size when using AutoSizeAxes. + /// + public bool GrowToFitContent + { + get => growToFitContent; + set + { + if (growToFitContent != value) + { + foreach (GrowToFitContainer c in Children) + c.GrowToFitContentUpdated = true; + growToFitContent = value; + } + } + } + + private bool growToFitContent = true; + private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); public UprightAspectMaintainingContainer() @@ -44,10 +65,11 @@ namespace osu.Game.Graphics.Containers public override void Add(Drawable drawable) { - base.Add(new GrowToFitContainer - { - Child = drawable - }); + var wrapper = new GrowToFitContainer(); + wrapper.Wrap(drawable); + wrapper.AutoSizeAxes = Axes.None; + drawable.Origin = drawable.Anchor = Anchor.Centre; + base.Add(wrapper); } /// @@ -58,7 +80,7 @@ namespace osu.Game.Graphics.Containers // Decomposes the inverse of the parent DrawInfo.Matrix into rotation, shear and scale. var parentMatrix = Parent.DrawInfo.Matrix; - // Remove Translation. + // Remove Translation.> parentMatrix.M31 = 0.0f; parentMatrix.M32 = 0.0f; @@ -108,17 +130,55 @@ namespace osu.Game.Graphics.Containers public class GrowToFitContainer : Container { + private readonly LayoutValue layout = new LayoutValue(Invalidation.RequiredParentSizeToFit, InvalidationSource.Child); + + private Vector2 maxChildSize; + + public bool GrowToFitContentUpdated { get; set; } + + public GrowToFitContainer() + { + AddLayout(layout); + } + protected override void Update() { - if ((Child.RelativeSizeAxes & Axes.X) != 0) - RelativeSizeAxes |= Axes.X; - else - Width = Math.Max(Child.Width, Width); + UprightAspectMaintainingContainer parent = (UprightAspectMaintainingContainer)Parent; - if ((Child.RelativeSizeAxes & Axes.Y) != 0) - RelativeSizeAxes |= Axes.Y; - else - Height = Math.Max(Child.Height, Height); + if (!layout.IsValid || GrowToFitContentUpdated) + { + if ((Child.RelativeSizeAxes & Axes.X) != 0) + RelativeSizeAxes |= Axes.X; + else + { + if (parent.GrowToFitContent) + Width = Math.Max(Child.Width * Child.Scale.X, maxChildSize.X); + else + Width = Child.Width * Child.Scale.X; + } + + if ((Child.RelativeSizeAxes & Axes.Y) != 0) + RelativeSizeAxes |= Axes.Y; + else + { + if (parent.GrowToFitContent) + Height = Math.Max(Child.Height * Child.Scale.Y, maxChildSize.Y); + else + Height = Child.Height * Child.Scale.Y; + } + + // reset max_child_size or update it + if (!parent.GrowToFitContent) + maxChildSize = Child.Size; + else + { + maxChildSize.X = MathF.Max(maxChildSize.X, Child.Size.X); + maxChildSize.Y = MathF.Max(maxChildSize.Y, Child.Size.Y); + } + + GrowToFitContentUpdated = false; + layout.Validate(); + } } } } From eb02a9a14442d970b2b710b69e749abaa7cd93eb Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:04:28 +0100 Subject: [PATCH 077/174] Removed GrowToFItContainer --- .../UprightAspectMaintainingContainer.cs | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs index 8efc29c83c..300b5bd4b4 100644 --- a/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs +++ b/osu.Game/Graphics/Containers/UprightAspectMaintainingContainer.cs @@ -24,27 +24,6 @@ namespace osu.Game.Graphics.Containers /// public ScaleMode Scaling { get; set; } = ScaleMode.Vertical; - /// - /// If this is true, all wrapper containers will be set to grow with their content - /// and not shrink back, this is used to fix the position of children that change - /// in size when using AutoSizeAxes. - /// - public bool GrowToFitContent - { - get => growToFitContent; - set - { - if (growToFitContent != value) - { - foreach (GrowToFitContainer c in Children) - c.GrowToFitContentUpdated = true; - growToFitContent = value; - } - } - } - - private bool growToFitContent = true; - private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawInfo, InvalidationSource.Parent); public UprightAspectMaintainingContainer() @@ -63,15 +42,6 @@ namespace osu.Game.Graphics.Containers } } - public override void Add(Drawable drawable) - { - var wrapper = new GrowToFitContainer(); - wrapper.Wrap(drawable); - wrapper.AutoSizeAxes = Axes.None; - drawable.Origin = drawable.Anchor = Anchor.Centre; - base.Add(wrapper); - } - /// /// Keeps the drawable upright and unstretched preventing it from being rotated, sheared, scaled or flipped with its Parent. /// @@ -127,60 +97,6 @@ namespace osu.Game.Graphics.Containers Scale = new Vector2(sx * usedScale, sy * usedScale); } - - public class GrowToFitContainer : Container - { - private readonly LayoutValue layout = new LayoutValue(Invalidation.RequiredParentSizeToFit, InvalidationSource.Child); - - private Vector2 maxChildSize; - - public bool GrowToFitContentUpdated { get; set; } - - public GrowToFitContainer() - { - AddLayout(layout); - } - - protected override void Update() - { - UprightAspectMaintainingContainer parent = (UprightAspectMaintainingContainer)Parent; - - if (!layout.IsValid || GrowToFitContentUpdated) - { - if ((Child.RelativeSizeAxes & Axes.X) != 0) - RelativeSizeAxes |= Axes.X; - else - { - if (parent.GrowToFitContent) - Width = Math.Max(Child.Width * Child.Scale.X, maxChildSize.X); - else - Width = Child.Width * Child.Scale.X; - } - - if ((Child.RelativeSizeAxes & Axes.Y) != 0) - RelativeSizeAxes |= Axes.Y; - else - { - if (parent.GrowToFitContent) - Height = Math.Max(Child.Height * Child.Scale.Y, maxChildSize.Y); - else - Height = Child.Height * Child.Scale.Y; - } - - // reset max_child_size or update it - if (!parent.GrowToFitContent) - maxChildSize = Child.Size; - else - { - maxChildSize.X = MathF.Max(maxChildSize.X, Child.Size.X); - maxChildSize.Y = MathF.Max(maxChildSize.Y, Child.Size.Y); - } - - GrowToFitContentUpdated = false; - layout.Validate(); - } - } - } } public enum ScaleMode From 4a630b53846f64d1f1d5fd96569991fdd316b246 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:05:06 +0100 Subject: [PATCH 078/174] Implemented SizePreservingSpriteText --- .../Sprites/SizePreservingTextSprite.cs | 107 ++++++++++++++++++ osu.Game/Screens/Play/HUD/SongProgressInfo.cs | 12 +- 2 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs diff --git a/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs b/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs new file mode 100644 index 0000000000..11b81b26fd --- /dev/null +++ b/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs @@ -0,0 +1,107 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.Sprites +{ + public class SizePreservingSpriteText : CompositeDrawable + { + private readonly OsuSpriteText text = new OsuSpriteText(); + + private Vector2 maximumSize; + + public SizePreservingSpriteText(Vector2? minimumSize = null) + { + text.Origin = Anchor.Centre; + text.Anchor = Anchor.Centre; + + AddInternal(text); + maximumSize = minimumSize ?? Vector2.Zero; + } + + protected override void Update() + { + Width = maximumSize.X = MathF.Max(maximumSize.X, text.Width); + Height = maximumSize.Y = MathF.Max(maximumSize.Y, text.Height); + } + + public new Axes AutoSizeAxes + { + get => Axes.None; + set => throw new InvalidOperationException("You can't set AutoSizeAxes of this container"); + } + + /// + /// Gets or sets the text to be displayed. + /// + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + /// + /// Contains information on the font used to display the text. + /// + public FontUsage Font + { + get => text.Font; + set => text.Font = value; + } + + /// + /// True if a shadow should be displayed around the text. + /// + public bool Shadow + { + get => text.Shadow; + set => text.Shadow = value; + } + + /// + /// The colour of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Color4 ShadowColour + { + get => text.ShadowColour; + set => text.ShadowColour = value; + } + + /// + /// The offset of the shadow displayed around the text. A shadow will only be displayed if the property is set to true. + /// + public Vector2 ShadowOffset + { + get => text.ShadowOffset; + set => text.ShadowOffset = value; + } + + /// + /// True if the 's vertical size should be equal to (the full height) or precisely the size of used characters. + /// Set to false to allow better centering of individual characters/numerals/etc. + /// + public bool UseFullGlyphHeight + { + get => text.UseFullGlyphHeight; + set => text.UseFullGlyphHeight = value; + } + + public override bool IsPresent => text.IsPresent; + + public override string ToString() => text.ToString(); + + public float LineBaseHeight => text.LineBaseHeight; + + public IEnumerable FilterTerms => text.FilterTerms; + } +} diff --git a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 81abfce0c7..d0eb8f8ca1 100644 --- a/osu.Game/Screens/Play/HUD/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -15,9 +15,9 @@ namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { - private OsuSpriteText timeCurrent; - private OsuSpriteText timeLeft; - private OsuSpriteText progress; + private SizePreservingSpriteText timeCurrent; + private SizePreservingSpriteText timeLeft; + private SizePreservingSpriteText progress; private double startTime; private double endTime; @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = timeCurrent = new OsuSpriteText + Child = timeCurrent = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = progress = new OsuSpriteText + Child = progress = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Play.HUD AutoSizeAxes = Axes.Both, Scaling = ScaleMode.Vertical, ScalingFactor = 0.5f, - Child = timeLeft = new OsuSpriteText + Child = timeLeft = new SizePreservingSpriteText { Origin = Anchor.Centre, Anchor = Anchor.Centre, From a548b28158c018d738d29fd2a2e94b70c00673f7 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:05:46 +0100 Subject: [PATCH 079/174] Added test scene for SizePreservingSpriteText --- .../TestSceneGrowToFitContent.cs | 175 ------------------ .../TestSceneSizePreservingTextSprite.cs | 96 ++++++++++ 2 files changed, 96 insertions(+), 175 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs deleted file mode 100644 index bcff992f9d..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneGrowToFitContent.cs +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics; -using System.Linq; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public class TestSceneGrowToFitContent : OsuGridTestScene - { - private readonly List parentContainers = new List(); - private readonly List childContainers = new List(); - private readonly List texts = new List(); - - public TestSceneGrowToFitContent() - : base(1, 2) - { - for (int i = 0; i < 2; i++) - { - OsuSpriteText text; - UprightAspectMaintainingContainer childContainer; - Container parentContainer = new Container - { - Origin = Anchor.BottomRight, - Anchor = Anchor.BottomCentre, - AutoSizeAxes = Axes.Both, - Rotation = 45, - Y = -200, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Red, - }, - childContainer = new UprightAspectMaintainingContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.Blue, - }, - text = new OsuSpriteText - { - Text = "Text", - Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 40), - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - }, - } - }, - } - }; - - Container cellInfo = new Container - { - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Margin = new MarginPadding - { - Top = 100, - }, - Child = new OsuSpriteText - { - Text = (i == 0) ? "GrowToFitContent == true" : "GrowToFitContent == false", - Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - }, - }; - - parentContainers.Add(parentContainer); - childContainers.Add(childContainer); - texts.Add(text); - Cell(i).Add(cellInfo); - Cell(i).Add(parentContainer); - } - } - - [Test] - public void TestResizeText() - { - AddStep("reset...", () => - { - childContainers[0].GrowToFitContent = false; - childContainers[1].GrowToFitContent = false; - }); - - AddStep("setup...", () => - { - childContainers[0].GrowToFitContent = true; - childContainers[1].GrowToFitContent = false; - }); - - for (int i = 0; i < 10; i++) - { - AddStep("Add Character", () => - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].Text += "."; - } - }); - } - - for (int i = 0; i < 10; i++) - { - AddStep("Remove Character", () => - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - string text = texts[j].Text.ToString(); - texts[j].Text = text.Remove(text.Length - 1, 1); - } - }); - } - } - - [Test] - public void TestScaleText() - { - AddStep("reset...", () => - { - childContainers[0].GrowToFitContent = false; - childContainers[1].GrowToFitContent = false; - }); - - AddStep("setup...", () => - { - childContainers[0].GrowToFitContent = true; - childContainers[1].GrowToFitContent = false; - }); - - for (int i = 0; i < 1; i++) - { - AddStep("Big text", scaleUp); - - AddWaitStep("wait...", 5); - - AddStep("Small text", scaleDown); - } - } - - private void scaleUp() - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].ScaleTo(new Vector2(2, 2), 1000); - } - } - - private void scaleDown() - { - foreach (int j in Enumerable.Range(0, parentContainers.Count)) - { - texts[j].ScaleTo(new Vector2(1, 1), 1000); - } - } - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs new file mode 100644 index 0000000000..a5e70a54f8 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneSizePreservingSpriteText : OsuGridTestScene + { + private readonly List parentContainers = new List(); + private readonly List childContainers = new List(); + private readonly OsuSpriteText osuSpriteText = new OsuSpriteText(); + private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText(); + + + public TestSceneSizePreservingSpriteText() + : base(1, 2) + { + for (int i = 0; i < 2; i++) + { + UprightAspectMaintainingContainer childContainer; + Container parentContainer = new Container + { + Origin = Anchor.BottomRight, + Anchor = Anchor.BottomCentre, + AutoSizeAxes = Axes.Both, + Rotation = 45, + Y = -200, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Red, + }, + childContainer = new UprightAspectMaintainingContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Blue, + }, + } + }, + } + }; + + Container cellInfo = new Container + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Margin = new MarginPadding + { + Top = 100, + }, + Child = new OsuSpriteText + { + Text = (i == 0) ? "OsuSpriteText" : "SizePreservingSpriteText", + Font = OsuFont.GetFont(Typeface.Inter, weight: FontWeight.Bold, size: 40), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + }; + + parentContainers.Add(parentContainer); + childContainers.Add(childContainer); + Cell(i).Add(cellInfo); + Cell(i).Add(parentContainer); + } + + childContainers[0].Add(osuSpriteText); + childContainers[1].Add(sizePreservingSpriteText); + osuSpriteText.Font = sizePreservingSpriteText.Font = OsuFont.GetFont(Typeface.Venera, weight: FontWeight.Bold, size: 20); + } + + protected override void Update() + { + base.Update(); + osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(); + } + } +} From 921a9ef895f4e23f81775bb3f252c85916d9e670 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Wed, 31 Aug 2022 22:18:52 +0100 Subject: [PATCH 080/174] clean up --- .../Visual/UserInterface/TestSceneSizePreservingTextSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs index a5e70a54f8..69f5015af6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; @@ -21,7 +22,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly OsuSpriteText osuSpriteText = new OsuSpriteText(); private readonly SizePreservingSpriteText sizePreservingSpriteText = new SizePreservingSpriteText(); - public TestSceneSizePreservingSpriteText() : base(1, 2) { @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.UserInterface protected override void Update() { base.Update(); - osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(); + osuSpriteText.Text = sizePreservingSpriteText.Text = DateTime.Now.ToString(CultureInfo.InvariantCulture); } } } From 7a8fa5c2e4364f3c50523f155674119c98ad6394 Mon Sep 17 00:00:00 2001 From: HiddenNode Date: Fri, 2 Sep 2022 09:56:00 +0100 Subject: [PATCH 081/174] Fix filenames mismatch --- ...eservingTextSprite.cs => TestSceneSizePreservingSpriteText.cs} | 0 .../{SizePreservingTextSprite.cs => SizePreservingSpriteText.cs} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/UserInterface/{TestSceneSizePreservingTextSprite.cs => TestSceneSizePreservingSpriteText.cs} (100%) rename osu.Game/Graphics/Sprites/{SizePreservingTextSprite.cs => SizePreservingSpriteText.cs} (100%) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs similarity index 100% rename from osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingTextSprite.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs diff --git a/osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs similarity index 100% rename from osu.Game/Graphics/Sprites/SizePreservingTextSprite.cs rename to osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs From 40ff2d50dd6e51feefc17fb1a7bfbce0e18c2be1 Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Sat, 3 Sep 2022 02:31:58 +0800 Subject: [PATCH 082/174] Refactor UI and add drag support --- .../TestSceneCatchTouchInput.cs | 2 - osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 58 ++++++++++++++----- 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index 03d031a0a8..24afda4cfa 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index 771902df65..dbc8a51d9c 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; +using osu.Framework.Logging; namespace osu.Game.Rulesets.Catch.UI { @@ -39,10 +40,13 @@ namespace osu.Game.Rulesets.Catch.UI private ArrowHitbox leftDashBox = null!; private ArrowHitbox rightDashBox = null!; + // Force input to be prossed even when hidden. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { - Show(); Debug.Assert(catchInputManager.KeyBindingContainer != null); keyBindingContainer = catchInputManager.KeyBindingContainer; @@ -121,7 +125,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - PopOut(); + Hide(); return false; } @@ -143,19 +147,39 @@ namespace osu.Game.Rulesets.Catch.UI base.OnMouseUp(e); } - /* I plan to come back to this code to add touch support - * protected override void OnTouchMove(TouchMoveEvent e) + protected override bool OnDragStart(DragStartEvent e) + { + return true; + } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + } + + protected override void OnDrag(DragEvent e) + { + // I'm not sure if this is posible but let's be safe + if (!trackedActions.ContainsKey(e.Button)) + trackedActions.Add(e.Button, TouchCatchAction.None); + + trackedActions[e.Button] = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); + calculateActiveKeys(); + + base.OnDrag(e); + } + protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe if (!trackedActions.ContainsKey(e.Touch.Source)) trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.MousePosition); + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouchDownPosition); calculateActiveKeys(); base.OnTouchMove(e); - }*/ + } protected override bool OnTouchDown(TouchDownEvent e) { @@ -189,7 +213,7 @@ namespace osu.Game.Rulesets.Catch.UI private void handleDown(object source, Vector2 position) { - PopIn(); + Show(); TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -217,8 +241,10 @@ namespace osu.Game.Rulesets.Catch.UI if (leftBox.Contains(inputPosition)) return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) + { + Logger.Log(inputPosition.ToString()); return TouchCatchAction.MoveRight; - + } return TouchCatchAction.None; } @@ -255,20 +281,20 @@ namespace osu.Game.Rulesets.Catch.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - overlay = new Box - { - Alpha = 0, - Colour = colour.Multiply(1.4f).Darken(2.8f), - Blending = BlendingParameters.Additive, - Width = 1, - RelativeSizeAxes = Axes.Both, - }, new Box { Alpha = 0.8f, Colour = colour, Width = 1, RelativeSizeAxes = Axes.Both, + }, + overlay = new Box + { + Alpha = 0, + Colour = colour.Multiply(1.4f), + Blending = BlendingParameters.Additive, + Width = 1, + RelativeSizeAxes = Axes.Both, } } } From 161c54df1cf981fe000a35e8412153de0d9cc95a Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Sat, 3 Sep 2022 14:14:34 +0800 Subject: [PATCH 083/174] Refactor UI and add drag support --- osu.Game.Rulesets.Catch/UI/TouchInputField.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs index dbc8a51d9c..80453d6aa3 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/TouchInputField.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; -using osu.Framework.Logging; namespace osu.Game.Rulesets.Catch.UI { @@ -174,8 +173,7 @@ namespace osu.Game.Rulesets.Catch.UI if (!trackedActions.ContainsKey(e.Touch.Source)) trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouchDownPosition); - + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); base.OnTouchMove(e); @@ -183,7 +181,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnTouchDown(TouchDownEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); + handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); return true; } @@ -241,10 +239,7 @@ namespace osu.Game.Rulesets.Catch.UI if (leftBox.Contains(inputPosition)) return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) - { - Logger.Log(inputPosition.ToString()); return TouchCatchAction.MoveRight; - } return TouchCatchAction.None; } From 5f0832ead72c9964cdc40fa00b01f260ffa5a1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 01:58:57 +0900 Subject: [PATCH 084/174] refactor(osu.Game): separate `OsuColour.ForHitResult` by usage --- osu.Game/Graphics/OsuColour.cs | 30 ++++++++++++++++++- .../Leaderboards/LeaderboardScoreTooltip.cs | 2 +- .../Judgements/DefaultJudgementPiece.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 25 +--------------- .../Expanded/Statistics/HitResultStatistic.cs | 2 +- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 6e2f460930..022b1a363f 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -102,7 +102,7 @@ namespace osu.Game.Graphics /// /// Retrieves the colour for a . /// - public Color4 ForHitResult(HitResult judgement) + public Color4 TextForHitResult(HitResult judgement) { switch (judgement) { @@ -125,6 +125,34 @@ namespace osu.Game.Graphics } } + public Color4 DrawForHitResult(HitResult result) + { + switch (result) + { + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + case HitResult.Miss: + return Red; + + case HitResult.Meh: + return Yellow; + + case HitResult.Ok: + return Green; + + case HitResult.Good: + return GreenLight; + + case HitResult.SmallTickHit: + case HitResult.LargeTickHit: + case HitResult.Great: + return Blue; + + default: + return BlueLight; + } + } + /// /// Retrieves a colour for the given . /// A value indicates that a "background" shade from the local diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 2f3ece0e3b..23d4e64191 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -156,7 +156,7 @@ namespace osu.Game.Online.Leaderboards { Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), Text = displayName.ToUpper(), - Colour = colours.ForHitResult(result), + Colour = colours.TextForHitResult(result), }, new OsuSpriteText { diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index c2b27d4ce8..a854bf37f5 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Result.GetDescription().ToUpperInvariant(), - Colour = colours.ForHitResult(Result), + Colour = colours.TextForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 26befd659c..35d28b8e98 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -59,30 +59,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected Color4 GetColourForHitResult(HitResult result) { - switch (result) - { - case HitResult.SmallTickMiss: - case HitResult.LargeTickMiss: - case HitResult.Miss: - return colours.Red; - - case HitResult.Meh: - return colours.Yellow; - - case HitResult.Ok: - return colours.Green; - - case HitResult.Good: - return colours.GreenLight; - - case HitResult.SmallTickHit: - case HitResult.LargeTickHit: - case HitResult.Great: - return colours.Blue; - - default: - return colours.BlueLight; - } + return colours.DrawForHitResult(result); } /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index c23a5e668d..429b72c07c 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.ForHitResult(Result); + HeaderText.Colour = colours.TextForHitResult(Result); } } } From 074d2a7a3a64cb09b566e51399450da57bbc455e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 02:01:44 +0900 Subject: [PATCH 085/174] chore(osu.Game): provide ordering index for `HitResult` --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 5047fdea82..8078363212 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -4,10 +4,12 @@ #nullable disable using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Utils; namespace osu.Game.Rulesets.Scoring @@ -135,6 +137,8 @@ namespace osu.Game.Rulesets.Scoring #pragma warning disable CS0618 public static class HitResultExtensions { + private static readonly IList order = EnumExtensions.GetValuesInOrder().ToList(); + /// /// Whether a increases the combo. /// @@ -282,6 +286,16 @@ namespace osu.Game.Rulesets.Scoring Debug.Assert(minResult <= maxResult); return result > minResult && result < maxResult; } + + /// + /// Ordered index of a . Used for sorting. + /// + /// The to get the index of. + /// The index of . + public static int OrderingIndex(this HitResult result) + { + return order.IndexOf(result); + } } #pragma warning restore CS0618 } From 0af6b3dc0fa41e65480c670c7a8dbc53f11f6de9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 02:02:38 +0900 Subject: [PATCH 086/174] chore(osu.Game): colorize bars by OD on `HitEventTimingDistributionGraph` --- .../HitEventTimingDistributionGraph.cs | 109 ++++++++++++++---- 1 file changed, 84 insertions(+), 25 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index db69e270f6..3a696290b9 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -48,6 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; + [Resolved] + private OsuColour colours { get; set; } + /// /// Creates a new . /// @@ -57,7 +59,7 @@ namespace osu.Game.Screens.Ranking.Statistics this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } - private int[] bins; + private IDictionary[] bins; private double binSize; private double hitOffset; @@ -69,7 +71,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (hitEvents == null || hitEvents.Count == 0) return; - bins = new int[total_timing_distribution_bins]; + bins = Enumerable.Range(0, total_timing_distribution_bins).Select(_ => new Dictionary()).ToArray>(); binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); @@ -89,7 +91,8 @@ namespace osu.Game.Screens.Ranking.Statistics { bool roundUp = true; - Array.Clear(bins, 0, bins.Length); + foreach (var bin in bins) + bin.Clear(); foreach (var e in hitEvents) { @@ -110,23 +113,29 @@ namespace osu.Game.Screens.Ranking.Statistics // may be out of range when applying an offset. for such cases we can just drop the results. if (index >= 0 && index < bins.Length) - bins[index]++; + { + bins[index].TryGetValue(e.Result, out int value); + bins[index][e.Result] = ++value; + } } if (barDrawables != null) { for (int i = 0; i < barDrawables.Length; i++) { - barDrawables[i].UpdateOffset(bins[i]); + barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value)); } } else { - int maxCount = bins.Max(); + int maxCount = bins.Max(b => b.Values.Sum()); barDrawables = new Bar[total_timing_distribution_bins]; for (int i = 0; i < barDrawables.Length; i++) - barDrawables[i] = new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index); + { + IReadOnlyList values = bins[i].Select(b => new BarValue(b.Key.OrderingIndex(), b.Value, colours.DrawForHitResult(b.Key))).OrderBy(b => b.Index).ToList(); + barDrawables[i] = new Bar(values, maxCount, i == timing_distribution_centre_bin_index); + } Container axisFlow; @@ -207,52 +216,102 @@ namespace osu.Game.Screens.Ranking.Statistics } } + private readonly struct BarValue + { + public readonly int Index; + public readonly float Value; + public readonly Color4 Colour; + + public BarValue(int index, float value, Color4 colour) + { + Index = index; + Value = value; + Colour = colour; + } + } + private class Bar : CompositeDrawable { - private readonly float value; + private float totalValue => values.Sum(v => v.Value); + private float basalHeight => BoundingBox.Width / BoundingBox.Height; + private float availableHeight => 1 - basalHeight; + + private readonly IReadOnlyList values; private readonly float maxValue; - private readonly Circle boxOriginal; + private readonly Circle[] boxOriginals; private Circle boxAdjustment; - private const float minimum_height = 0.05f; - - public Bar(float value, float maxValue, bool isCentre) + public Bar(IReadOnlyList values, float maxValue, bool isCentre) { - this.value = value; + this.values = values; this.maxValue = maxValue; RelativeSizeAxes = Axes.Both; Masking = true; - InternalChildren = new Drawable[] + if (values.Any()) { - boxOriginal = new Circle + boxOriginals = values.Select(v => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : Color4Extensions.FromHex("#66FFCC"), - Height = minimum_height, - }, - }; + Colour = isCentre ? Color4.White : v.Colour, + Height = 0, + }).ToArray(); + InternalChildren = boxOriginals.Reverse().ToArray(); + } + else + { + InternalChildren = boxOriginals = new[] + { + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4.Gray, + Height = 0, + }, + }; + } } private const double duration = 300; + private float offsetForValue(float value) + { + return availableHeight * value / maxValue; + } + + private float heightForValue(float value) + { + return basalHeight + offsetForValue(value); + } + protected override void LoadComplete() { base.LoadComplete(); - float height = Math.Clamp(value / maxValue, minimum_height, 1); + float offsetValue = 0; - if (height > minimum_height) - boxOriginal.ResizeHeightTo(height, duration, Easing.OutQuint); + if (values.Any()) + { + for (int i = 0; i < values.Count; i++) + { + boxOriginals[i].Y = BoundingBox.Height * offsetForValue(offsetValue); + boxOriginals[i].Delay(duration * i).ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; + } + } + else + boxOriginals.Single().ResizeHeightTo(basalHeight, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) { - bool hasAdjustment = adjustment != value && adjustment / maxValue >= minimum_height; + bool hasAdjustment = adjustment != totalValue; if (boxAdjustment == null) { @@ -271,7 +330,7 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - boxAdjustment.ResizeHeightTo(Math.Clamp(adjustment / maxValue, minimum_height, 1), duration, Easing.OutQuint); + boxAdjustment.ResizeHeightTo(heightForValue(adjustment), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } } From b67fd3d8804a6461943057fc0cf6fb0adf5fbcbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 03:45:51 +0900 Subject: [PATCH 087/174] chore(osu.Game): split transform duration of bars on `HitTimingDistributionGraph` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 3a696290b9..900fd2c907 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,7 +278,9 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private const double duration = 300; + private const double total_duration = 300; + + private double duration => total_duration / Math.Max(values.Count, 1); private float offsetForValue(float value) { From 19ab1433c67b06b1a9f08bf5b64c14abcee5647d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 03:46:23 +0900 Subject: [PATCH 088/174] test(osu.Game): add more test cases for `HitTimingDistributionGraph` --- ...estSceneHitEventTimingDistributionGraph.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 44cb438a6b..9264ed7030 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -43,6 +43,50 @@ namespace osu.Game.Tests.Visual.Ranking createTest(Enumerable.Range(-150, 300).Select(i => new HitEvent(i / 50f, HitResult.Perfect, placeholder_object, placeholder_object, null)).ToList()); } + [Test] + public void TestSparse() + { + createTest(new List + { + new HitEvent(-7, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-6, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(-5, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(5, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(6, HitResult.Perfect, placeholder_object, placeholder_object, null), + new HitEvent(7, HitResult.Perfect, placeholder_object, placeholder_object, null), + }); + } + + [Test] + public void TestVariousTypesOfHitResult() + { + createTest(CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }).ToList()); + } + + [Test] + public void TestMultipleWindowsOfHitResult() + { + var wide = CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }); + var narrow = CreateDistributedHitEvents(0, 50).Select(h => + { + var offset = Math.Abs(h.TimeOffset); + var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); + }); + createTest(wide.Concat(narrow).ToList()); + } + + [Test] public void TestZeroTimeOffset() { From 7e77c9e8b47e87fa181db6912bfbc9a541f278d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 04:44:27 +0900 Subject: [PATCH 089/174] chore(osu.Game): only the first result should be white at zero position on `HitEventTimingDistributionGraph` --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 900fd2c907..1be32a94af 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -252,12 +252,12 @@ namespace osu.Game.Screens.Ranking.Statistics if (values.Any()) { - boxOriginals = values.Select(v => new Circle + boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : v.Colour, + Colour = isCentre && i == 0 ? Color4.White : v.Colour, Height = 0, }).ToArray(); InternalChildren = boxOriginals.Reverse().ToArray(); From 4ea7ca4c0748f1cead8465b6448510136255c289 Mon Sep 17 00:00:00 2001 From: Exanc <43091560+Exanc@users.noreply.github.com> Date: Mon, 5 Sep 2022 00:09:20 +0200 Subject: [PATCH 090/174] Slight tweak to the StarsSlider - MinimumStarsSlider now shows "0" when at it's minimum - Modified and NoResultsPlaceholder the tooltip to stay consistent with the changes --- .../Select/DifficultyRangeFilterControl.cs | 15 ++++++++++++++- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index a82c969805..eb5a797ac0 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -65,6 +65,10 @@ namespace osu.Game.Screens.Select private class MinimumStarsSlider : StarsSlider { + public MinimumStarsSlider() : base("0") { } + + public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); + protected override void LoadComplete() { base.LoadComplete(); @@ -82,6 +86,8 @@ namespace osu.Game.Screens.Select private class MaximumStarsSlider : StarsSlider { + public MaximumStarsSlider() : base("∞") { } + protected override void LoadComplete() { base.LoadComplete(); @@ -100,6 +106,13 @@ namespace osu.Game.Screens.Select ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); + protected readonly string DefaultValue; + + public StarsSlider(string defaultValue) + { + DefaultValue = defaultValue; + } + protected override bool OnHover(HoverEvent e) { base.OnHover(e); @@ -125,7 +138,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(current => { - currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : "∞"; + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : DefaultValue; }, true); } } diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index f3c3fb4d87..f44aa01588 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -127,10 +127,10 @@ namespace osu.Game.Screens.Select config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); }); - string lowerStar = filter.UserStarDifficulty.Min == null ? "∞" : $"{filter.UserStarDifficulty.Min:N1}"; + string lowerStar = filter.UserStarDifficulty.Min == null ? "0,0" : $"{filter.UserStarDifficulty.Min:N1}"; string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; - textFlow.AddText($" the {lowerStar}-{upperStar} star difficulty filter."); + textFlow.AddText($" the {lowerStar} - {upperStar} star difficulty filter."); } // TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch). From 6946015d17fe0f6feddcc5e5e3a974cc39bc54d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Mon, 5 Sep 2022 07:49:29 +0000 Subject: [PATCH 091/174] style(osu.Game): fix multiple blank lines --- .../Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 9264ed7030..fe4aba1317 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -86,7 +86,6 @@ namespace osu.Game.Tests.Visual.Ranking createTest(wide.Concat(narrow).ToList()); } - [Test] public void TestZeroTimeOffset() { From 2923c10cd808f555375c081b008a4558460ec4e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 1 Sep 2022 18:53:35 +0900 Subject: [PATCH 092/174] Rewrite rooms to store multiple active countdowns Update test to the new structure --- .../Multiplayer/TestSceneMatchStartControl.cs | 15 ++++++---- .../Countdown/CountdownChangedEvent.cs | 20 ------------- .../Countdown/CountdownStartedEvent.cs | 28 +++++++++++++++++++ .../Countdown/CountdownStoppedEvent.cs | 28 +++++++++++++++++++ .../Countdown/StopCountdownRequest.cs | 10 +++++++ .../Online/Multiplayer/MatchServerEvent.cs | 3 +- .../Online/Multiplayer/MultiplayerClient.cs | 10 +++++-- .../Multiplayer/MultiplayerCountdown.cs | 10 +++++-- .../Online/Multiplayer/MultiplayerRoom.cs | 4 +-- osu.Game/Online/SignalRWorkaroundTypes.cs | 3 +- .../Multiplayer/Match/MatchStartControl.cs | 10 +++++-- .../Match/MultiplayerCountdownButton.cs | 5 ++-- .../Match/MultiplayerReadyButton.cs | 25 ++++++----------- 13 files changed, 117 insertions(+), 54 deletions(-) delete mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs create mode 100644 osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs index a800b21bc9..12e7394c93 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs @@ -91,8 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer break; case StopCountdownRequest: - multiplayerRoom.Countdown = null; - raiseRoomUpdated(); + clearRoomCountdown(); break; } }); @@ -244,14 +243,14 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddStep("start countdown", () => multiplayerClient.Object.SendMatchRequest(new StartMatchCountdownRequest { Duration = TimeSpan.FromMinutes(1) }).WaitSafely()); - AddUntilStep("countdown started", () => multiplayerRoom.Countdown != null); + AddUntilStep("countdown started", () => multiplayerRoom.ActiveCountdowns.Any()); AddStep("transfer host to local user", () => transferHost(localUser)); AddUntilStep("local user is host", () => multiplayerRoom.Host?.Equals(multiplayerClient.Object.LocalUser) == true); ClickButtonWhenEnabled(); checkLocalUserState(MultiplayerUserState.Ready); - AddAssert("countdown still active", () => multiplayerRoom.Countdown != null); + AddAssert("countdown still active", () => multiplayerRoom.ActiveCountdowns.Any()); } [Test] @@ -392,7 +391,13 @@ namespace osu.Game.Tests.Visual.Multiplayer private void setRoomCountdown(TimeSpan duration) { - multiplayerRoom.Countdown = new MatchStartCountdown { TimeRemaining = duration }; + multiplayerRoom.ActiveCountdowns.Add(new MatchStartCountdown { TimeRemaining = duration }); + raiseRoomUpdated(); + } + + private void clearRoomCountdown() + { + multiplayerRoom.ActiveCountdowns.Clear(); raiseRoomUpdated(); } diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs deleted file mode 100644 index 649e3c8389..0000000000 --- a/osu.Game/Online/Multiplayer/Countdown/CountdownChangedEvent.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using MessagePack; - -namespace osu.Game.Online.Multiplayer.Countdown -{ - /// - /// Indicates a change to the 's countdown. - /// - [MessagePackObject] - public class CountdownChangedEvent : MatchServerEvent - { - /// - /// The new countdown. - /// - [Key(0)] - public MultiplayerCountdown? Countdown { get; set; } - } -} diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs new file mode 100644 index 0000000000..1dbb27bce6 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/CountdownStartedEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Indicates that a countdown started in the . + /// + [MessagePackObject] + public class CountdownStartedEvent : MatchServerEvent + { + /// + /// The countdown that was started. + /// + [Key(0)] + public readonly MultiplayerCountdown Countdown; + + [JsonConstructor] + [SerializationConstructor] + public CountdownStartedEvent(MultiplayerCountdown countdown) + { + Countdown = countdown; + } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs b/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs new file mode 100644 index 0000000000..b46ed0e5e0 --- /dev/null +++ b/osu.Game/Online/Multiplayer/Countdown/CountdownStoppedEvent.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using MessagePack; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer.Countdown +{ + /// + /// Indicates that a countdown was stopped in the . + /// + [MessagePackObject] + public class CountdownStoppedEvent : MatchServerEvent + { + /// + /// The identifier of the countdown that was stopped. + /// + [Key(0)] + public readonly int ID; + + [JsonConstructor] + [SerializationConstructor] + public CountdownStoppedEvent(int id) + { + ID = id; + } + } +} diff --git a/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs index bd0c381c0b..495252c044 100644 --- a/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs +++ b/osu.Game/Online/Multiplayer/Countdown/StopCountdownRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using MessagePack; +using Newtonsoft.Json; namespace osu.Game.Online.Multiplayer.Countdown { @@ -11,5 +12,14 @@ namespace osu.Game.Online.Multiplayer.Countdown [MessagePackObject] public class StopCountdownRequest : MatchUserRequest { + [Key(0)] + public readonly int ID; + + [JsonConstructor] + [SerializationConstructor] + public StopCountdownRequest(int id) + { + ID = id; + } } } diff --git a/osu.Game/Online/Multiplayer/MatchServerEvent.cs b/osu.Game/Online/Multiplayer/MatchServerEvent.cs index 20bf9e5141..376ff4d261 100644 --- a/osu.Game/Online/Multiplayer/MatchServerEvent.cs +++ b/osu.Game/Online/Multiplayer/MatchServerEvent.cs @@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer [Serializable] [MessagePackObject] // IMPORTANT: Add rules to SignalRUnionWorkaroundResolver for new derived types. - [Union(0, typeof(CountdownChangedEvent))] + [Union(0, typeof(CountdownStartedEvent))] + [Union(1, typeof(CountdownStoppedEvent))] public abstract class MatchServerEvent { } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 04b87c19da..5575113a87 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -550,8 +550,14 @@ namespace osu.Game.Online.Multiplayer switch (e) { - case CountdownChangedEvent countdownChangedEvent: - Room.Countdown = countdownChangedEvent.Countdown; + case CountdownStartedEvent countdownStartedEvent: + Room.ActiveCountdowns.Add(countdownStartedEvent.Countdown); + break; + + case CountdownStoppedEvent countdownStoppedEvent: + MultiplayerCountdown? countdown = Room.ActiveCountdowns.FirstOrDefault(countdown => countdown.ID == countdownStoppedEvent.ID); + if (countdown != null) + Room.ActiveCountdowns.Remove(countdown); break; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 621f9236fd..00bfa8919a 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -15,13 +15,19 @@ namespace osu.Game.Online.Multiplayer [Union(1, typeof(ForceGameplayStartCountdown))] public abstract class MultiplayerCountdown { + /// + /// A unique identifier for this countdown. + /// + [Key(0)] + public int ID { get; set; } + /// /// The amount of time remaining in the countdown. /// /// - /// This is only sent once from the server upon initial retrieval of the or via a . + /// This is only sent once from the server upon initial retrieval of the or via a . /// - [Key(0)] + [Key(1)] public TimeSpan TimeRemaining { get; set; } } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs index fb05c03256..00048fa931 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoom.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoom.cs @@ -53,10 +53,10 @@ namespace osu.Game.Online.Multiplayer public IList Playlist { get; set; } = new List(); /// - /// The currently-running countdown. + /// The currently running countdowns. /// [Key(7)] - public MultiplayerCountdown? Countdown { get; set; } + public IList ActiveCountdowns { get; set; } = new List(); [JsonConstructor] [SerializationConstructor] diff --git a/osu.Game/Online/SignalRWorkaroundTypes.cs b/osu.Game/Online/SignalRWorkaroundTypes.cs index 29e01e13ae..3518fbb4fe 100644 --- a/osu.Game/Online/SignalRWorkaroundTypes.cs +++ b/osu.Game/Online/SignalRWorkaroundTypes.cs @@ -23,7 +23,8 @@ namespace osu.Game.Online (typeof(ChangeTeamRequest), typeof(MatchUserRequest)), (typeof(StartMatchCountdownRequest), typeof(MatchUserRequest)), (typeof(StopCountdownRequest), typeof(MatchUserRequest)), - (typeof(CountdownChangedEvent), typeof(MatchServerEvent)), + (typeof(CountdownStartedEvent), typeof(MatchServerEvent)), + (typeof(CountdownStoppedEvent), typeof(MatchServerEvent)), (typeof(TeamVersusRoomState), typeof(MatchRoomState)), (typeof(TeamVersusUserState), typeof(MatchUserState)), (typeof(MatchStartCountdown), typeof(MultiplayerCountdown)), diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs index 501c76f65b..f048ae59cd 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MatchStartControl.cs @@ -109,7 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - if (isReady() && Client.IsHost && Room.Countdown == null) + if (isReady() && Client.IsHost && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) startMatch(); else toggleReady(); @@ -140,10 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void cancelCountdown() { + if (Client.Room == null) + return; + Debug.Assert(clickOperation == null); clickOperation = ongoingOperationTracker.BeginOperation(); - Client.SendMatchRequest(new StopCountdownRequest()).ContinueWith(_ => endOperation()); + MultiplayerCountdown countdown = Client.Room.ActiveCountdowns.Single(c => c is MatchStartCountdown); + Client.SendMatchRequest(new StopCountdownRequest(countdown.ID)).ContinueWith(_ => endOperation()); } private void endOperation() @@ -192,7 +196,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match // When the local user is the host and spectating the match, the ready button should be enabled only if any users are ready. if (localUser?.State == MultiplayerUserState.Spectating) - readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && Room.Countdown == null; + readyButton.Enabled.Value &= Client.IsHost && newCountReady > 0 && !Room.ActiveCountdowns.Any(c => c is MatchStartCountdown); if (newCountReady == countReady) return; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs index 14bf1a8375..cd94b47d9e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerCountdownButton.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void onRoomUpdated() => Scheduler.AddOnce(() => { - bool countdownActive = multiplayerClient.Room?.Countdown is MatchStartCountdown; + bool countdownActive = multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true; if (countdownActive) { @@ -121,7 +122,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match }); } - if (multiplayerClient.Room?.Countdown != null && multiplayerClient.IsHost) + if (multiplayerClient.Room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true && multiplayerClient.IsHost) { flow.Add(new OsuButton { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index c5d6da1ebc..b4ff34cbc2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -57,23 +57,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private void onRoomUpdated() => Scheduler.AddOnce(() => { - MultiplayerCountdown newCountdown; - - switch (room?.Countdown) - { - case MatchStartCountdown: - newCountdown = room.Countdown; - break; - - // Clear the countdown with any other (including non-null) countdown values. - default: - newCountdown = null; - break; - } + MultiplayerCountdown newCountdown = room?.ActiveCountdowns.SingleOrDefault(c => c is MatchStartCountdown); if (newCountdown != countdown) { - countdown = room?.Countdown; + countdown = newCountdown; countdownChangeTime = Time.Current; } @@ -213,7 +201,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match case MultiplayerUserState.Spectating: case MultiplayerUserState.Ready: - if (room?.Host?.Equals(localUser) == true && room.Countdown == null) + if (room?.Host?.Equals(localUser) == true && !room.ActiveCountdowns.Any(c => c is MatchStartCountdown)) setGreen(); else setYellow(); @@ -248,8 +236,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { get { - if (room?.Countdown != null && multiplayerClient.IsHost && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready && !room.Settings.AutoStartEnabled) + if (room?.ActiveCountdowns.Any(c => c is MatchStartCountdown) == true + && multiplayerClient.IsHost + && multiplayerClient.LocalUser?.State == MultiplayerUserState.Ready + && !room.Settings.AutoStartEnabled) + { return "Cancel countdown"; + } return base.TooltipText; } From b2f30fbf8cb7e76d02d8cb957d6a2eb1328f2b1e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Sep 2022 20:13:23 +0900 Subject: [PATCH 093/174] Add countdown exclusivity --- osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs | 3 ++- osu.Game/Online/Multiplayer/MatchStartCountdown.cs | 3 ++- osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs index 7f5c0f0a05..81ba56f35c 100644 --- a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs @@ -13,7 +13,8 @@ namespace osu.Game.Online.Multiplayer /// and forcing progression of any clients that are blocking load due to user interaction. /// [MessagePackObject] - public class ForceGameplayStartCountdown : MultiplayerCountdown + public sealed class ForceGameplayStartCountdown : MultiplayerCountdown { + public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs index 5d3365c947..b4c66e6f5b 100644 --- a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs @@ -9,7 +9,8 @@ namespace osu.Game.Online.Multiplayer /// A which will start the match after ending. /// [MessagePackObject] - public class MatchStartCountdown : MultiplayerCountdown + public sealed class MatchStartCountdown : MultiplayerCountdown { + public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index 00bfa8919a..e8e2365f7b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -29,5 +29,10 @@ namespace osu.Game.Online.Multiplayer /// [Key(1)] public TimeSpan TimeRemaining { get; set; } + + /// + /// Whether only a single instance of this type may be active at any one time. + /// + public virtual bool IsExclusive => false; } } From e33486a766044c17c2f254f5e8df6d72b29c341e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:20:02 +0900 Subject: [PATCH 094/174] Implement `IAdjustableAudioComponent` in `MasterGameplayClockContainer` --- .../Play/MasterGameplayClockContainer.cs | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 2f1ffa126f..57e67e6e26 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider, IAdjustableAudioComponent { /// /// Duration before gameplay start time required before skip button displays. @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Play private readonly WorkingBeatmap beatmap; + private readonly Track track; + private readonly double skipTargetTime; private readonly List> nonGameplayAdjustments = new List>(); @@ -66,6 +68,7 @@ namespace osu.Game.Screens.Play public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) : base(beatmap.Track, true) { + track = beatmap.Track; this.beatmap = beatmap; this.skipTargetTime = skipTargetTime; @@ -195,9 +198,6 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; - if (SourceClock is not Track track) - return; - track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -212,9 +212,6 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - if (SourceClock is not Track track) - return; - track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -234,5 +231,29 @@ namespace osu.Game.Screens.Play IClock IBeatSyncProvider.Clock => this; ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; + + void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => + track.AddAdjustment(type, adjustBindable); + + void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => + track.RemoveAdjustment(type, adjustBindable); + + public void RemoveAllAdjustments(AdjustableProperty type) + { + throw new NotImplementedException(); + } + + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + + public BindableNumber Volume => throw new NotImplementedException(); + public BindableNumber Balance => throw new NotImplementedException(); + public BindableNumber Frequency => throw new NotImplementedException(); + public BindableNumber Tempo => throw new NotImplementedException(); + + public IBindable AggregateVolume => throw new NotImplementedException(); + public IBindable AggregateBalance => throw new NotImplementedException(); + public IBindable AggregateFrequency => throw new NotImplementedException(); + public IBindable AggregateTempo => throw new NotImplementedException(); } } From 7084aeee0523af04c380ef1ee2cee143d18f2fcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:22:38 +0900 Subject: [PATCH 095/174] Add method flow to reset applied adjustments --- osu.Game/Screens/Play/GameplayClockContainer.cs | 4 +++- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 7 +++++-- osu.Game/Screens/Play/Player.cs | 9 ++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6de88d7ad0..490c2329ca 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -196,7 +196,9 @@ namespace osu.Game.Screens.Play void IAdjustableClock.Reset() => Reset(); - public void ResetSpeedAdjustments() => throw new NotImplementedException(); + public virtual void ResetSpeedAdjustments() + { + } double IAdjustableClock.Rate { diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 57e67e6e26..15e9a40fd3 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -238,11 +238,14 @@ namespace osu.Game.Screens.Play void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => track.RemoveAdjustment(type, adjustBindable); - public void RemoveAllAdjustments(AdjustableProperty type) + public override void ResetSpeedAdjustments() { - throw new NotImplementedException(); + track.RemoveAllAdjustments(AdjustableProperty.Frequency); + track.RemoveAllAdjustments(AdjustableProperty.Tempo); } + public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4afd04c335..21a02fbe0b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -999,9 +999,12 @@ namespace osu.Game.Screens.Play // Our mods are local copies of the global mods so they need to be re-applied to the track. // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. // Todo: In the future, player will receive in a track and will probably not have to worry about this... - musicController.ResetTrackAdjustments(); - foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(musicController.CurrentTrack); + if (GameplayClockContainer is IAdjustableAudioComponent adjustableClock) + { + GameplayClockContainer.ResetSpeedAdjustments(); + foreach (var mod in GameplayState.Mods.OfType()) + mod.ApplyToTrack(adjustableClock); + } updateGameplayState(); From 266eb758aa2471d1cdb54253ab564e02a27764a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:37:41 +0900 Subject: [PATCH 096/174] Use new flow to calcaulate `TrueGameplayRate` --- .../Screens/Play/GameplayClockContainer.cs | 19 +--------- .../Play/MasterGameplayClockContainer.cs | 35 ++++++++++++------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 490c2329ca..4d48675f94 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Beatmaps; namespace osu.Game.Screens.Play @@ -225,22 +224,6 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; - public double TrueGameplayRate - { - get - { - double baseRate = Rate; - - foreach (double adjustment in NonGameplayAdjustments) - { - if (Precision.AlmostEquals(adjustment, 0)) - return 0; - - baseRate /= adjustment; - } - - return baseRate; - } - } + public virtual double TrueGameplayRate => Rate; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 15e9a40fd3..bda8718dc6 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -45,8 +45,6 @@ namespace osu.Game.Screens.Play private readonly double skipTargetTime; - private readonly List> nonGameplayAdjustments = new List>(); - /// /// Stores the time at which the last call was triggered. /// This is used to ensure we resume from that precise point in time, ignoring the proceeding frequency ramp. @@ -58,8 +56,6 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; - public override IEnumerable NonGameplayAdjustments => nonGameplayAdjustments.Select(b => b.Value); - /// /// Create a new master gameplay clock container. /// @@ -201,9 +197,6 @@ namespace osu.Game.Screens.Play track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - nonGameplayAdjustments.Add(GameplayClock.ExternalPauseFrequencyAdjust); - nonGameplayAdjustments.Add(UserPlaybackRate); - speedAdjustmentsApplied = true; } @@ -215,9 +208,6 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); - nonGameplayAdjustments.Remove(GameplayClock.ExternalPauseFrequencyAdjust); - nonGameplayAdjustments.Remove(UserPlaybackRate); - speedAdjustmentsApplied = false; } @@ -232,11 +222,30 @@ namespace osu.Game.Screens.Play ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; - void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) => - track.AddAdjustment(type, adjustBindable); + private readonly List> speedAdjustments = new List>(); - void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) => + public override double TrueGameplayRate + { + get + { + double rate = Rate; + foreach (var a in speedAdjustments) + rate *= a.Value; + return rate; + } + } + + void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + speedAdjustments.Add(adjustBindable); + track.AddAdjustment(type, adjustBindable); + } + + void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) + { + speedAdjustments.Remove(adjustBindable); track.RemoveAdjustment(type, adjustBindable); + } public override void ResetSpeedAdjustments() { From 44b456e216741a814bc61018aa08c2642465ea74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Sep 2022 23:38:22 +0900 Subject: [PATCH 097/174] Use gameplay clock's `TrueGameplayRate` in `FrameStabilityContainer`? --- .../Rulesets/UI/FrameStabilityContainer.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 18d0ff0bed..4d817cd973 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -263,23 +263,7 @@ namespace osu.Game.Rulesets.UI public FrameTimeInfo TimeInfo => framedClock.TimeInfo; - public double TrueGameplayRate - { - get - { - double baseRate = Rate; - - foreach (double adjustment in NonGameplayAdjustments) - { - if (Precision.AlmostEquals(adjustment, 0)) - return 0; - - baseRate /= adjustment; - } - - return baseRate; - } - } + public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? Rate; public double StartTime => parentGameplayClock?.StartTime ?? 0; From b109e5de6ce537916252487ccd183ef870012afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Tue, 6 Sep 2022 00:04:10 +0900 Subject: [PATCH 098/174] chore(osu.Game): align height of bars on timing distribution graph to `basalHeight` first and combine their transitions into each one --- .../Statistics/HitEventTimingDistributionGraph.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 1be32a94af..5fbc07921a 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -278,9 +278,7 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private const double total_duration = 300; - - private double duration => total_duration / Math.Max(values.Count, 1); + private const double duration = 300; private float offsetForValue(float value) { @@ -296,19 +294,20 @@ namespace osu.Game.Screens.Ranking.Statistics { base.LoadComplete(); + foreach (var boxOriginal in boxOriginals) + boxOriginal.Height = basalHeight; + float offsetValue = 0; if (values.Any()) { for (int i = 0; i < values.Count; i++) { - boxOriginals[i].Y = BoundingBox.Height * offsetForValue(offsetValue); - boxOriginals[i].Delay(duration * i).ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); offsetValue -= values[i].Value; } } - else - boxOriginals.Single().ResizeHeightTo(basalHeight, duration, Easing.OutQuint); } public void UpdateOffset(float adjustment) From bc1212f4e6785670a9249aaf1fa5ccf501fd6f5b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 20:06:30 +0900 Subject: [PATCH 099/174] Change `NonGameplayAdjustments` to `GameplayAdjustments` and convert `TrueGameplayRate` to extension method --- .../Default/SpinnerRotationTracker.cs | 2 +- .../NonVisual/GameplayClockContainerTest.cs | 6 ++--- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Rulesets/UI/FrameStabilityContainer.cs | 5 +---- .../Screens/Play/GameplayClockContainer.cs | 4 +--- .../Screens/Play/GameplayClockExtensions.cs | 22 +++++++++++++++++++ osu.Game/Screens/Play/IGameplayClock.cs | 8 +------ .../Play/MasterGameplayClockContainer.cs | 11 +--------- 8 files changed, 31 insertions(+), 29 deletions(-) create mode 100644 osu.Game/Screens/Play/GameplayClockExtensions.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index 554ea3ac90..97cebc3123 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); + drawableSpinner.Result.RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.GetTrueGameplayRate() ?? Clock.Rate)); } private void resetState(DrawableHitObject obj) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index f9f4ead644..95bf1ab354 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -13,17 +13,17 @@ namespace osu.Game.Tests.NonVisual { [TestCase(0)] [TestCase(1)] - public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) + public void TestTrueGameplayRateWithGameplayAdjustment(double underlyingClockRate) { var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); var gameplayClock = new TestGameplayClockContainer(framedClock); - Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); + Assert.That(gameplayClock.GetTrueGameplayRate(), Is.EqualTo(2)); } private class TestGameplayClockContainer : GameplayClockContainer { - public override IEnumerable NonGameplayAdjustments => new[] { 0.0 }; + public override IEnumerable GameplayAdjustments => new[] { 2.0 }; public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index a6abdd7ee1..d0371acce7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -370,7 +370,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void confirmNoTrackAdjustments() { - AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + AddUntilStep("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value, () => Is.EqualTo(1)); } private void restart() => AddStep("restart", () => Player.Restart()); diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4d817cd973..4f4a2d908d 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Framework.Utils; using osu.Game.Input.Handlers; using osu.Game.Screens.Play; @@ -263,11 +262,9 @@ namespace osu.Game.Rulesets.UI public FrameTimeInfo TimeInfo => framedClock.TimeInfo; - public double TrueGameplayRate => parentGameplayClock?.TrueGameplayRate ?? Rate; - public double StartTime => parentGameplayClock?.StartTime ?? 0; - public IEnumerable NonGameplayAdjustments => parentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty(); + public IEnumerable GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? Enumerable.Empty(); #endregion diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4d48675f94..5dfaf2d584 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public virtual IEnumerable NonGameplayAdjustments => Enumerable.Empty(); + public virtual IEnumerable GameplayAdjustments => Enumerable.Empty(); private readonly BindableBool isPaused = new BindableBool(true); @@ -223,7 +223,5 @@ namespace osu.Game.Screens.Play public double FramesPerSecond => GameplayClock.FramesPerSecond; public FrameTimeInfo TimeInfo => GameplayClock.TimeInfo; - - public virtual double TrueGameplayRate => Rate; } } diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs new file mode 100644 index 0000000000..b683c61f63 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Screens.Play +{ + public static class GameplayClockExtensions + { + /// + /// The rate of gameplay when playback is at 100%. + /// This excludes any seeking / user adjustments. + /// + public static double GetTrueGameplayRate(this IGameplayClock clock) + { + double rate = Math.Sign(clock.Rate); + foreach (double a in clock.GameplayAdjustments) + rate *= a; + return rate; + } + } +} diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index ea567090ad..7c50b9d407 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -9,12 +9,6 @@ namespace osu.Game.Screens.Play { public interface IGameplayClock : IFrameBasedClock { - /// - /// The rate of gameplay when playback is at 100%. - /// This excludes any seeking / user adjustments. - /// - double TrueGameplayRate { get; } - /// /// The time from which the clock should start. Will be seeked to on calling . /// @@ -27,7 +21,7 @@ namespace osu.Game.Screens.Play /// /// All adjustments applied to this clock which don't come from gameplay or mods. /// - IEnumerable NonGameplayAdjustments { get; } + IEnumerable GameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index bda8718dc6..f1ee65dcd3 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -224,16 +224,7 @@ namespace osu.Game.Screens.Play private readonly List> speedAdjustments = new List>(); - public override double TrueGameplayRate - { - get - { - double rate = Rate; - foreach (var a in speedAdjustments) - rate *= a.Value; - return rate; - } - } + public override IEnumerable GameplayAdjustments => speedAdjustments.Select(bindable => bindable.Value); void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) { From 66c44f5913d24c95116d1a4605ed4f6e3d3522d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 6 Sep 2022 21:40:04 +0900 Subject: [PATCH 100/174] Delegate interface to valid target --- .../Play/MasterGameplayClockContainer.cs | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index f1ee65dcd3..7c30f86125 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -56,6 +57,11 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; + /// + /// Maintained solely to delegate pieces to (to maintain parent lookups). + /// + private readonly AudioContainer audioContainer; + /// /// Create a new master gameplay clock container. /// @@ -69,6 +75,8 @@ namespace osu.Game.Screens.Play this.skipTargetTime = skipTargetTime; StartTime = findEarliestStartTime(); + + AddInternal(audioContainer = new AudioContainer()); } private double findEarliestStartTime() @@ -238,25 +246,32 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(type, adjustBindable); } + void IAdjustableAudioComponent.RemoveAllAdjustments(AdjustableProperty type) => audioContainer.RemoveAllAdjustments(type); + + void IAdjustableAudioComponent.BindAdjustments(IAggregateAudioAdjustment component) => audioContainer.BindAdjustments(component); + + void IAdjustableAudioComponent.UnbindAdjustments(IAggregateAudioAdjustment component) => audioContainer.UnbindAdjustments(component); + + BindableNumber IAdjustableAudioComponent.Volume => audioContainer.Volume; + + BindableNumber IAdjustableAudioComponent.Balance => audioContainer.Balance; + + BindableNumber IAdjustableAudioComponent.Frequency => audioContainer.Frequency; + + BindableNumber IAdjustableAudioComponent.Tempo => audioContainer.Tempo; + public override void ResetSpeedAdjustments() { track.RemoveAllAdjustments(AdjustableProperty.Frequency); track.RemoveAllAdjustments(AdjustableProperty.Tempo); } - public void RemoveAllAdjustments(AdjustableProperty type) => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateVolume => audioContainer.AggregateVolume; - public void BindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); - public void UnbindAdjustments(IAggregateAudioAdjustment component) => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateBalance => audioContainer.AggregateBalance; - public BindableNumber Volume => throw new NotImplementedException(); - public BindableNumber Balance => throw new NotImplementedException(); - public BindableNumber Frequency => throw new NotImplementedException(); - public BindableNumber Tempo => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateFrequency => audioContainer.AggregateFrequency; - public IBindable AggregateVolume => throw new NotImplementedException(); - public IBindable AggregateBalance => throw new NotImplementedException(); - public IBindable AggregateFrequency => throw new NotImplementedException(); - public IBindable AggregateTempo => throw new NotImplementedException(); + IBindable IAggregateAudioAdjustment.AggregateTempo => audioContainer.AggregateTempo; } } From 3003fc1061f63b147a7e9bf0bc7eb03d0a42e7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 02:29:15 +0900 Subject: [PATCH 101/174] refactor(osu.Game): improve code quality --- .../TestSceneHitEventTimingDistributionGraph.cs | 6 +++--- .../Statistics/HitEventTimingDistributionGraph.cs | 11 ++++------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index fe4aba1317..4825cb7f80 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Ranking { createTest(CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }).ToList()); @@ -73,13 +73,13 @@ namespace osu.Game.Tests.Visual.Ranking { var wide = CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); var narrow = CreateDistributedHitEvents(0, 50).Select(h => { - var offset = Math.Abs(h.TimeOffset); + double offset = Math.Abs(h.TimeOffset); var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 5fbc07921a..d3c2716f8b 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -299,14 +299,11 @@ namespace osu.Game.Screens.Ranking.Statistics float offsetValue = 0; - if (values.Any()) + for (int i = 0; i < values.Count; i++) { - for (int i = 0; i < values.Count; i++) - { - boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); - boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); - offsetValue -= values[i].Value; - } + boxOriginals[i].MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + boxOriginals[i].ResizeHeightTo(heightForValue(values[i].Value), duration, Easing.OutQuint); + offsetValue -= values[i].Value; } } From cb1bb99208d24c7d15f6adb64e289f87ae0116b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 16:43:48 +0900 Subject: [PATCH 102/174] Tidy up test logic --- ...estSceneHitEventTimingDistributionGraph.cs | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index 4825cb7f80..198be4035b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - private HitEventTimingDistributionGraph graph; + private HitEventTimingDistributionGraph graph = null!; private static readonly HitObject placeholder_object = new HitCircle(); @@ -63,7 +61,12 @@ namespace osu.Game.Tests.Visual.Ranking createTest(CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 36 ? HitResult.Miss + : offset > 32 ? HitResult.Meh + : offset > 24 ? HitResult.Ok + : offset > 16 ? HitResult.Good + : offset > 8 ? HitResult.Great + : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }).ToList()); } @@ -74,13 +77,24 @@ namespace osu.Game.Tests.Visual.Ranking var wide = CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 36 ? HitResult.Miss : offset > 32 ? HitResult.Meh : offset > 24 ? HitResult.Ok : offset > 16 ? HitResult.Good : offset > 8 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 36 ? HitResult.Miss + : offset > 32 ? HitResult.Meh + : offset > 24 ? HitResult.Ok + : offset > 16 ? HitResult.Good + : offset > 8 ? HitResult.Great + : HitResult.Perfect; + return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); var narrow = CreateDistributedHitEvents(0, 50).Select(h => { double offset = Math.Abs(h.TimeOffset); - var result = offset > 25 ? HitResult.Miss : offset > 20 ? HitResult.Meh : offset > 15 ? HitResult.Ok : offset > 10 ? HitResult.Good : offset > 5 ? HitResult.Great : HitResult.Perfect; + HitResult result = offset > 25 ? HitResult.Miss + : offset > 20 ? HitResult.Meh + : offset > 15 ? HitResult.Ok + : offset > 10 ? HitResult.Good + : offset > 5 ? HitResult.Great + : HitResult.Perfect; return new HitEvent(h.TimeOffset, result, placeholder_object, placeholder_object, null); }); createTest(wide.Concat(narrow).ToList()); From 99ef0c95fec70b164c745f589fc8940726faf454 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 16:51:51 +0900 Subject: [PATCH 103/174] Simplify children assignment --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index d3c2716f8b..37765fe962 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -252,7 +252,7 @@ namespace osu.Game.Screens.Ranking.Statistics if (values.Any()) { - boxOriginals = values.Select((v, i) => new Circle + InternalChildren = boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -260,7 +260,6 @@ namespace osu.Game.Screens.Ranking.Statistics Colour = isCentre && i == 0 ? Color4.White : v.Colour, Height = 0, }).ToArray(); - InternalChildren = boxOriginals.Reverse().ToArray(); } else { From 75d0deef72ab44b075345f6f4aeddde4d1a52492 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 17:38:00 +0900 Subject: [PATCH 104/174] Apply proposed changes to remove inheritance from `MasterGameplayClockContainer` --- .../NonVisual/GameplayClockContainerTest.cs | 6 +- .../Rulesets/UI/FrameStabilityContainer.cs | 7 ++- .../Screens/Play/GameplayClockContainer.cs | 5 +- .../Screens/Play/GameplayClockExtensions.cs | 8 ++- osu.Game/Screens/Play/IGameplayClock.cs | 6 +- .../Play/MasterGameplayClockContainer.cs | 57 +------------------ osu.Game/Screens/Play/Player.cs | 7 ++- 7 files changed, 24 insertions(+), 72 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index 95bf1ab354..80f0aaeb55 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -1,8 +1,9 @@ // 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 NUnit.Framework; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -23,11 +24,10 @@ namespace osu.Game.Tests.NonVisual private class TestGameplayClockContainer : GameplayClockContainer { - public override IEnumerable GameplayAdjustments => new[] { 2.0 }; - public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) { + GameplayAdjustments.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 4f4a2d908d..f0c7a398eb 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,10 +2,9 @@ // 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 osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -264,7 +263,9 @@ namespace osu.Game.Rulesets.UI public double StartTime => parentGameplayClock?.StartTime ?? 0; - public IEnumerable GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? Enumerable.Empty(); + private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments(); + + public IAdjustableAudioComponent GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? gameplayAdjustments; #endregion diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 5dfaf2d584..e64c628fa0 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,9 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -45,7 +44,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public virtual IEnumerable GameplayAdjustments => Enumerable.Empty(); + public IAdjustableAudioComponent GameplayAdjustments { get; } = new AudioAdjustments(); private readonly BindableBool isPaused = new BindableBool(true); diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index b683c61f63..3cc12f7afe 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -13,10 +13,12 @@ namespace osu.Game.Screens.Play /// public static double GetTrueGameplayRate(this IGameplayClock clock) { + // To handle rewind, we still want to maintain the same direction as the underlying clock. double rate = Math.Sign(clock.Rate); - foreach (double a in clock.GameplayAdjustments) - rate *= a; - return rate; + + return rate + * clock.GameplayAdjustments.AggregateFrequency.Value + * clock.GameplayAdjustments.AggregateTempo.Value; } } } diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index 7c50b9d407..c58d2dbcac 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -1,7 +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.Audio; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Play double StartTime { get; } /// - /// All adjustments applied to this clock which don't come from gameplay or mods. + /// All adjustments applied to this clock which come from gameplay or mods. /// - IEnumerable GameplayAdjustments { get; } + IAdjustableAudioComponent GameplayAdjustments { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 7c30f86125..226ce8b0d8 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -2,13 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +23,7 @@ namespace osu.Game.Screens.Play /// /// This is intended to be used as a single controller for gameplay, or as a reference source for other s. /// - public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider, IAdjustableAudioComponent + public class MasterGameplayClockContainer : GameplayClockContainer, IBeatSyncProvider { /// /// Duration before gameplay start time required before skip button displays. @@ -57,11 +55,6 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; - /// - /// Maintained solely to delegate pieces to (to maintain parent lookups). - /// - private readonly AudioContainer audioContainer; - /// /// Create a new master gameplay clock container. /// @@ -75,8 +68,6 @@ namespace osu.Game.Screens.Play this.skipTargetTime = skipTargetTime; StartTime = findEarliestStartTime(); - - AddInternal(audioContainer = new AudioContainer()); } private double findEarliestStartTime() @@ -202,6 +193,7 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + track.BindAdjustments(GameplayAdjustments); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -213,6 +205,7 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; + track.UnbindAdjustments(GameplayAdjustments); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -229,49 +222,5 @@ namespace osu.Game.Screens.Play IClock IBeatSyncProvider.Clock => this; ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => beatmap.TrackLoaded ? beatmap.Track.CurrentAmplitudes : ChannelAmplitudes.Empty; - - private readonly List> speedAdjustments = new List>(); - - public override IEnumerable GameplayAdjustments => speedAdjustments.Select(bindable => bindable.Value); - - void IAdjustableAudioComponent.AddAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - speedAdjustments.Add(adjustBindable); - track.AddAdjustment(type, adjustBindable); - } - - void IAdjustableAudioComponent.RemoveAdjustment(AdjustableProperty type, IBindable adjustBindable) - { - speedAdjustments.Remove(adjustBindable); - track.RemoveAdjustment(type, adjustBindable); - } - - void IAdjustableAudioComponent.RemoveAllAdjustments(AdjustableProperty type) => audioContainer.RemoveAllAdjustments(type); - - void IAdjustableAudioComponent.BindAdjustments(IAggregateAudioAdjustment component) => audioContainer.BindAdjustments(component); - - void IAdjustableAudioComponent.UnbindAdjustments(IAggregateAudioAdjustment component) => audioContainer.UnbindAdjustments(component); - - BindableNumber IAdjustableAudioComponent.Volume => audioContainer.Volume; - - BindableNumber IAdjustableAudioComponent.Balance => audioContainer.Balance; - - BindableNumber IAdjustableAudioComponent.Frequency => audioContainer.Frequency; - - BindableNumber IAdjustableAudioComponent.Tempo => audioContainer.Tempo; - - public override void ResetSpeedAdjustments() - { - track.RemoveAllAdjustments(AdjustableProperty.Frequency); - track.RemoveAllAdjustments(AdjustableProperty.Tempo); - } - - IBindable IAggregateAudioAdjustment.AggregateVolume => audioContainer.AggregateVolume; - - IBindable IAggregateAudioAdjustment.AggregateBalance => audioContainer.AggregateBalance; - - IBindable IAggregateAudioAdjustment.AggregateFrequency => audioContainer.AggregateFrequency; - - IBindable IAggregateAudioAdjustment.AggregateTempo => audioContainer.AggregateTempo; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 21a02fbe0b..17cae05862 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -999,11 +999,12 @@ namespace osu.Game.Screens.Play // Our mods are local copies of the global mods so they need to be re-applied to the track. // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. // Todo: In the future, player will receive in a track and will probably not have to worry about this... - if (GameplayClockContainer is IAdjustableAudioComponent adjustableClock) + if (GameplayClockContainer is MasterGameplayClockContainer masterClock) { - GameplayClockContainer.ResetSpeedAdjustments(); + musicController.ResetTrackAdjustments(); + foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(adjustableClock); + mod.ApplyToTrack(masterClock.GameplayAdjustments); } updateGameplayState(); From fa15502384f86d6a323cf999eb6692497beb8a7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:12:16 +0900 Subject: [PATCH 105/174] Move full track adjustment flow inside `MasterGameplayClockContainer` --- .../Screens/Play/MasterGameplayClockContainer.cs | 7 +++++++ osu.Game/Screens/Play/Player.cs | 12 ++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 226ce8b0d8..26fb127e83 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; @@ -10,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; namespace osu.Game.Screens.Play { @@ -55,6 +57,9 @@ namespace osu.Game.Screens.Play /// private double? actualStopTime; + [Resolved] + private MusicController musicController { get; set; } = null!; + /// /// Create a new master gameplay clock container. /// @@ -193,6 +198,8 @@ namespace osu.Game.Screens.Play if (speedAdjustmentsApplied) return; + musicController.ResetTrackAdjustments(); + track.BindAdjustments(GameplayAdjustments); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 17cae05862..e93502da13 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -996,16 +996,8 @@ namespace osu.Game.Screens.Play foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToHUD(HUDOverlay); - // Our mods are local copies of the global mods so they need to be re-applied to the track. - // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. - // Todo: In the future, player will receive in a track and will probably not have to worry about this... - if (GameplayClockContainer is MasterGameplayClockContainer masterClock) - { - musicController.ResetTrackAdjustments(); - - foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(masterClock.GameplayAdjustments); - } + foreach (var mod in GameplayState.Mods.OfType()) + mod.ApplyToTrack(GameplayClockContainer.GameplayAdjustments); updateGameplayState(); From 1be3b74ff33367af48d34c7e42fb0bd7b530c94c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:12:34 +0900 Subject: [PATCH 106/174] Fix multiplayer spectator not getting gameplay adjustments applied --- .../Spectate/MultiSpectatorPlayer.cs | 11 ++++++++++- .../Spectate/MultiSpectatorScreen.cs | 19 +++++++++++++++++++ .../Spectate/SpectatorPlayerClock.cs | 3 +++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index d351d121c6..7e910b7946 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -53,6 +53,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new GameplayClockContainer(spectatorPlayerClock); + { + var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); + + // Directionality is important, as BindAdjustments is... not actually a bidirectional bind... + // We want to ensure that any adjustments applied by the Player instance are applied to the SpectatorPlayerClock + // so they can be consumed by the spectator screen (and applied to the master clock / track). + spectatorPlayerClock.GameplayAdjustments.BindAdjustments(gameplayClockContainer.GameplayAdjustments); + + return gameplayClockContainer; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index c2ece90472..b7c07372dc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -43,6 +44,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; + private AudioAdjustments? boundAdjustments; + private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer = null!; private SpectatorSyncManager syncManager = null!; @@ -157,6 +160,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate base.LoadComplete(); masterClockContainer.Reset(); + + // Start with adjustments from the first player to keep a sane state. + bindAudioAdjustments(instances.First()); } protected override void Update() @@ -169,11 +175,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate .OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)) .FirstOrDefault(); + // Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio. + if (currentAudioSource != null) + bindAudioAdjustments(currentAudioSource); + foreach (var instance in instances) instance.Mute = instance != currentAudioSource; } } + private void bindAudioAdjustments(PlayerArea first) + { + if (boundAdjustments != null) + masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); + + boundAdjustments = first.SpectatorPlayerClock.GameplayAdjustments; + masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); + } + private bool isCandidateAudioSource(SpectatorPlayerClock? clock) => clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs index 45615d4e19..5667be1f4b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Audio; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -19,6 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly GameplayClockContainer masterClock; + public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + public double CurrentTime { get; private set; } /// From e6b449fe0b602a76a7d61efb5bff0a609fb241cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 19:23:44 +0900 Subject: [PATCH 107/174] Fix case of zero rate calculating a zero true gameplay rate --- osu.Game/Screens/Play/GameplayClockExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index 3cc12f7afe..ec77a94ce9 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play public static double GetTrueGameplayRate(this IGameplayClock clock) { // To handle rewind, we still want to maintain the same direction as the underlying clock. - double rate = Math.Sign(clock.Rate); + double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate); return rate * clock.GameplayAdjustments.AggregateFrequency.Value From cb9bae1f5c87ac68fa621a7652db9a9e3dea11df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 19:05:53 +0900 Subject: [PATCH 108/174] Enable NRT --- .../Match/MultiplayerMatchSettingsOverlay.cs | 56 +++++++++---------- .../Playlists/PlaylistsSongSelect.cs | 2 - 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 3d6127e8e7..c2dafda592 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.ComponentModel; using System.Diagnostics; -using JetBrains.Annotations; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -15,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -30,12 +29,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { public class MultiplayerMatchSettingsOverlay : RoomSettingsOverlay { - private MatchSettings settings; + private MatchSettings settings = null!; protected override OsuButton SubmitButton => settings.ApplyButton; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; protected override bool IsLoading => ongoingOperationTracker.InProgress.Value; @@ -57,20 +56,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match { private const float disabled_alpha = 0.2f; - public Action SettingsApplied; + public Action? SettingsApplied; - public OsuTextBox NameField, MaxParticipantsField; - public MatchTypePicker TypePicker; - public OsuEnumDropdown QueueModeDropdown; - public OsuTextBox PasswordTextBox; - public OsuCheckbox AutoSkipCheckbox; - public TriangleButton ApplyButton; + public OsuTextBox NameField = null!; + public OsuTextBox MaxParticipantsField = null!; + public MatchTypePicker TypePicker = null!; + public OsuEnumDropdown QueueModeDropdown = null!; + public OsuTextBox PasswordTextBox = null!; + public OsuCheckbox AutoSkipCheckbox = null!; + public TriangleButton ApplyButton = null!; - public OsuSpriteText ErrorText; + public OsuSpriteText ErrorText = null!; - private OsuEnumDropdown startModeDropdown; - private OsuSpriteText typeLabel; - private LoadingLayer loadingLayer; + private OsuEnumDropdown startModeDropdown = null!; + private OsuSpriteText typeLabel = null!; + private LoadingLayer loadingLayer = null!; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; public void SelectBeatmap() { @@ -79,26 +82,23 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match } [Resolved] - private MultiplayerMatchSubScreen matchSubScreen { get; set; } + private MultiplayerMatchSubScreen matchSubScreen { get; set; } = null!; [Resolved] - private IRoomManager manager { get; set; } + private IRoomManager manager { get; set; } = null!; [Resolved] - private MultiplayerClient client { get; set; } + private MultiplayerClient client { get; set; } = null!; [Resolved] - private OngoingOperationTracker ongoingOperationTracker { get; set; } + private OngoingOperationTracker ongoingOperationTracker { get; set; } = null!; private readonly IBindable operationInProgress = new BindableBool(); - - [CanBeNull] - private IDisposable applyingSettingsOperation; - private readonly Room room; - private Drawable playlistContainer; - private DrawableRoomPlaylist drawablePlaylist; + private IDisposable? applyingSettingsOperation; + private Drawable playlistContainer = null!; + private DrawableRoomPlaylist drawablePlaylist = null!; public MatchSettings(Room room) { @@ -423,7 +423,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match else room.MaxParticipants.Value = null; - manager?.CreateRoom(room, onSuccess, onError); + manager.CreateRoom(room, onSuccess, onError); } } @@ -466,7 +466,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match public class CreateOrUpdateButton : TriangleButton { [Resolved(typeof(Room), nameof(Room.RoomID))] - private Bindable roomId { get; set; } + private Bindable roomId { get; set; } = null!; protected override void LoadComplete() { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs index 73765fc661..e3f7b5dfc4 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsSongSelect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Screens; using osu.Game.Online.API; From 770c1ade2fee41abc436f38dc24685c5c8560a25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 20:00:24 +0900 Subject: [PATCH 109/174] Add test coverage of track rate adjusting during multi spectator --- .../TestSceneMultiSpectatorScreen.cs | 21 ++++++++++++++++--- .../Multiplayer/TestMultiplayerClient.cs | 7 ++++--- .../Visual/Spectator/TestSpectatorClient.cs | 8 ++++++- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index a11a67aebd..70f498e7f2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -13,9 +13,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate; using osu.Game.Screens.Play; @@ -332,6 +334,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000); } + [Test] + public void TestGameplayRateAdjust() + { + start(getPlayerIds(4), mods: new[] { new APIMod(new OsuModDoubleTime()) }); + + loadSpectateScreen(); + + sendFrames(getPlayerIds(4), 300); + + AddUntilStep("wait for correct track speed", () => Beatmap.Value.Track.Rate, () => Is.EqualTo(1.5)); + } + [Test] public void TestPlayersLeaveWhileSpectating() { @@ -420,7 +434,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId); - private void start(int[] userIds, int? beatmapId = null) + private void start(int[] userIds, int? beatmapId = null, APIMod[]? mods = null) { AddStep("start play", () => { @@ -429,10 +443,11 @@ namespace osu.Game.Tests.Visual.Multiplayer var user = new MultiplayerRoomUser(id) { User = new APIUser { Id = id }, + Mods = mods ?? Array.Empty(), }; - OnlinePlayDependencies.MultiplayerClient.AddUser(user.User, true); - SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId); + OnlinePlayDependencies.MultiplayerClient.AddUser(user, true); + SpectatorClient.SendStartPlay(id, beatmapId ?? importedBeatmapId, mods); playingUsers.Add(user); } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 19b887eea5..84737bce3f 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -81,13 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer public void Disconnect() => isConnected.Value = false; public MultiplayerRoomUser AddUser(APIUser user, bool markAsPlaying = false) - { - var roomUser = new MultiplayerRoomUser(user.Id) { User = user }; + => AddUser(new MultiplayerRoomUser(user.Id) { User = user }, markAsPlaying); + public MultiplayerRoomUser AddUser(MultiplayerRoomUser roomUser, bool markAsPlaying = false) + { addUser(roomUser); if (markAsPlaying) - PlayingUserIds.Add(user.Id); + PlayingUserIds.Add(roomUser.UserID); return roomUser; } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index 2531f3c485..cb3711ebb5 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -37,6 +37,7 @@ namespace osu.Game.Tests.Visual.Spectator private readonly Dictionary lastReceivedUserFrames = new Dictionary(); private readonly Dictionary userBeatmapDictionary = new Dictionary(); + private readonly Dictionary userModsDictionary = new Dictionary(); private readonly Dictionary userNextFrameDictionary = new Dictionary(); [Resolved] @@ -52,9 +53,11 @@ namespace osu.Game.Tests.Visual.Spectator /// /// The user to start play for. /// The playing beatmap id. - public void SendStartPlay(int userId, int beatmapId) + /// The mods the user has applied. + public void SendStartPlay(int userId, int beatmapId, APIMod[]? mods = null) { userBeatmapDictionary[userId] = beatmapId; + userModsDictionary[userId] = mods ?? Array.Empty(); userNextFrameDictionary[userId] = 0; sendPlayingState(userId); } @@ -73,10 +76,12 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + Mods = userModsDictionary[userId], State = state }); userBeatmapDictionary.Remove(userId); + userModsDictionary.Remove(userId); } /// @@ -158,6 +163,7 @@ namespace osu.Game.Tests.Visual.Spectator { BeatmapID = userBeatmapDictionary[userId], RulesetID = 0, + Mods = userModsDictionary[userId], State = SpectatedUserState.Playing }); } From 208bd0f3914d77847fa49ed8815f67490d80b05c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 20:01:17 +0900 Subject: [PATCH 110/174] Give OnlinePlaySongSelect a reference PlaylistItem --- .../TestSceneMultiplayerMatchSongSelect.cs | 4 +- .../Match/MultiplayerMatchSettingsOverlay.cs | 5 -- .../Multiplayer/MultiplayerMatchSongSelect.cs | 23 ++---- .../Multiplayer/MultiplayerMatchSubScreen.cs | 14 +--- .../OnlinePlay/OnlinePlaySongSelect.cs | 72 +++++++++++++------ 5 files changed, 58 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2281235f25..0ecfc059e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -152,8 +152,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; - public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) - : base(room, null, beatmap, ruleset) + public TestMultiplayerMatchSongSelect(Room room) + : base(room) { } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index c2dafda592..75bd6eb04d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -4,7 +4,6 @@ using System; using System.ComponentModel; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -13,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -72,9 +70,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match private OsuSpriteText typeLabel = null!; private LoadingLayer loadingLayer = null!; - [Resolved] - private BeatmapManager beatmapManager { get; set; } = null!; - public void SelectBeatmap() { if (matchSubScreen.IsCurrentScreen()) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index dbd679104e..3fe236bd7a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -8,11 +8,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select; @@ -27,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private OngoingOperationTracker operationTracker { get; set; } = null!; private readonly IBindable operationInProgress = new Bindable(); - private readonly long? itemToEdit; + private readonly PlaylistItem? itemToEdit; private LoadingLayer loadingLayer = null!; private IDisposable? selectionOperation; @@ -37,21 +35,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// /// The room. /// The item to be edited. May be null, in which case a new item will be added to the playlist. - /// An optional initial beatmap selection to perform. - /// An optional initial ruleset selection to perform. - public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap? beatmap = null, RulesetInfo? ruleset = null) - : base(room) + public MultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null) + : base(room, itemToEdit) { this.itemToEdit = itemToEdit; - - if (beatmap != null || ruleset != null) - { - Schedule(() => - { - if (beatmap != null) Beatmap.Value = beatmap; - if (ruleset != null) Ruleset.Value = ruleset; - }); - } } [BackgroundDependencyLoader] @@ -80,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { if (operationInProgress.Value) { - Logger.Log($"{nameof(SelectedItem)} aborted due to {nameof(operationInProgress)}"); + Logger.Log($"{nameof(SelectItem)} aborted due to {nameof(operationInProgress)}"); return false; } @@ -92,7 +79,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var multiplayerItem = new MultiplayerPlaylistItem { - ID = itemToEdit ?? 0, + ID = itemToEdit?.ID ?? 0, BeatmapID = item.Beatmap.OnlineID, BeatmapChecksum = item.Beatmap.MD5Hash, RulesetID = item.RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index ceadfa1527..db752f2b42 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -49,11 +49,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - - private readonly IBindable isConnected = new Bindable(); - private AddItemButton addItemButton; public MultiplayerMatchSubScreen(Room room) @@ -227,12 +222,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.Last().Beatmap.OnlineID; - var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); - - var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); - - this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); @@ -424,7 +414,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, Room.Playlist.Single(item => item.ID == client.Room.Settings.PlaylistItemId))); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index f26480909e..e841d9c41e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Humanizer; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -35,32 +33,39 @@ namespace osu.Game.Screens.OnlinePlay public override bool AllowEditing => false; [Resolved(typeof(Room), nameof(Room.Playlist))] - protected BindableList Playlist { get; private set; } - - [CanBeNull] - [Resolved(CanBeNull = true)] - protected IBindable SelectedItem { get; private set; } + protected BindableList Playlist { get; private set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } = null!; protected override UserActivity InitialActivity => new UserActivity.InLobby(room); protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); private readonly Room room; + private readonly PlaylistItem? initialItem; + private readonly FreeModSelectOverlay freeModSelectOverlay; - private WorkingBeatmap initialBeatmap; - private RulesetInfo initialRuleset; - private IReadOnlyList initialMods; + private WorkingBeatmap initialBeatmap = null!; + private RulesetInfo initialRuleset = null!; + private IReadOnlyList initialMods = null!; private bool itemSelected; - private readonly FreeModSelectOverlay freeModSelectOverlay; - private IDisposable freeModSelectOverlayRegistration; + private IDisposable? freeModSelectOverlayRegistration; - protected OnlinePlaySongSelect(Room room) + /// + /// Creates a new . + /// + /// The room. + /// An optional initial to use for the initial beatmap/ruleset/mods. + /// If null, the last in the room will be used. + protected OnlinePlaySongSelect(Room room, PlaylistItem? initialItem = null) { this.room = room; + this.initialItem = initialItem ?? room.Playlist.LastOrDefault(); Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; @@ -76,6 +81,7 @@ namespace osu.Game.Screens.OnlinePlay { LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT }; + // Store the initial beatmap/ruleset/mods at the point of entering song select, so they can be reverted to upon exit. initialBeatmap = Beatmap.Value; initialRuleset = Ruleset.Value; initialMods = Mods.Value.ToList(); @@ -87,14 +93,35 @@ namespace osu.Game.Screens.OnlinePlay { base.LoadComplete(); - var rulesetInstance = SelectedItem?.Value?.RulesetID == null ? null : rulesets.GetRuleset(SelectedItem.Value.RulesetID)?.CreateInstance(); - - if (rulesetInstance != null) + if (initialItem != null) { - // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. - // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = SelectedItem.Value.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); - FreeMods.Value = SelectedItem.Value.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + // Prefer using a local databased beatmap lookup since OnlineId may be -1 for an invalid beatmap selection. + BeatmapInfo? beatmapInfo = initialItem.Beatmap as BeatmapInfo; + + // And in the case that this isn't a local databased beatmap, query by online ID. + if (beatmapInfo == null) + { + int onlineId = initialItem.Beatmap.OnlineID; + beatmapInfo = beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId); + } + + if (beatmapInfo != null) + Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo); + + RulesetInfo? ruleset = rulesets.GetRuleset(initialItem.RulesetID); + + if (ruleset != null) + { + Ruleset.Value = ruleset; + + var rulesetInstance = ruleset.CreateInstance(); + Debug.Assert(rulesetInstance != null); + + // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. + // Similarly, freeMods is currently empty but should only contain the allowed mods. + Mods.Value = initialItem.RequiredMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + FreeMods.Value = initialItem.AllowedMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + } } Mods.BindValueChanged(onModsChanged); @@ -199,7 +226,6 @@ namespace osu.Game.Screens.OnlinePlay protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - freeModSelectOverlayRegistration?.Dispose(); } } From fcea244537db30790f77c758cce7717d44ef7d11 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 21:21:19 +0900 Subject: [PATCH 111/174] Remove initial selection from OnlinePlaySongSelect This stuff never really worked anyway - every case except with an already created multiplayer room was broken anyway. --- .../OnlinePlay/OnlinePlaySongSelect.cs | 27 +------------------ 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index e841d9c41e..ea20270c1e 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -49,11 +49,6 @@ namespace osu.Game.Screens.OnlinePlay private readonly PlaylistItem? initialItem; private readonly FreeModSelectOverlay freeModSelectOverlay; - private WorkingBeatmap initialBeatmap = null!; - private RulesetInfo initialRuleset = null!; - private IReadOnlyList initialMods = null!; - private bool itemSelected; - private IDisposable? freeModSelectOverlayRegistration; /// @@ -80,12 +75,6 @@ namespace osu.Game.Screens.OnlinePlay private void load() { LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT }; - - // Store the initial beatmap/ruleset/mods at the point of entering song select, so they can be reverted to upon exit. - initialBeatmap = Beatmap.Value; - initialRuleset = Ruleset.Value; - initialMods = Mods.Value.ToList(); - LoadComponent(freeModSelectOverlay); } @@ -152,13 +141,7 @@ namespace osu.Game.Screens.OnlinePlay AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray() }; - if (SelectItem(item)) - { - itemSelected = true; - return true; - } - - return false; + return SelectItem(item); } /// @@ -181,15 +164,7 @@ namespace osu.Game.Screens.OnlinePlay public override bool OnExiting(ScreenExitEvent e) { - if (!itemSelected) - { - Beatmap.Value = initialBeatmap; - Ruleset.Value = initialRuleset; - Mods.Value = initialMods; - } - freeModSelectOverlay.Hide(); - return base.OnExiting(e); } From 83c0cb1acce2fe9e6c27080651e82b413add1c5e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Sep 2022 21:22:26 +0900 Subject: [PATCH 112/174] Fix beatmap set to null after exiting song select --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 03216180fb..00c819e5e4 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -433,6 +433,9 @@ namespace osu.Game.Screens.OnlinePlay.Match private void updateWorkingBeatmap() { + if (SelectedItem.Value == null || !this.IsCurrentScreen()) + return; + var beatmap = SelectedItem.Value?.Beatmap; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info From b560b6f7454983bf2275a76974a6c4c38e828ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:29:10 +0900 Subject: [PATCH 113/174] refactor(osu.Game): arrange the code for the timing distribution graph --- .../HitEventTimingDistributionGraph.cs | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 37765fe962..ad2f979138 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -47,9 +47,6 @@ namespace osu.Game.Screens.Ranking.Statistics /// private readonly IReadOnlyList hitEvents; - [Resolved] - private OsuColour colours { get; set; } - /// /// Creates a new . /// @@ -129,13 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics else { int maxCount = bins.Max(b => b.Values.Sum()); - barDrawables = new Bar[total_timing_distribution_bins]; - - for (int i = 0; i < barDrawables.Length; i++) - { - IReadOnlyList values = bins[i].Select(b => new BarValue(b.Key.OrderingIndex(), b.Value, colours.DrawForHitResult(b.Key))).OrderBy(b => b.Index).ToList(); - barDrawables[i] = new Bar(values, maxCount, i == timing_distribution_centre_bin_index); - } + barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray(); Container axisFlow; @@ -216,53 +207,53 @@ namespace osu.Game.Screens.Ranking.Statistics } } - private readonly struct BarValue - { - public readonly int Index; - public readonly float Value; - public readonly Color4 Colour; - - public BarValue(int index, float value, Color4 colour) - { - Index = index; - Value = value; - Colour = colour; - } - } - private class Bar : CompositeDrawable { private float totalValue => values.Sum(v => v.Value); private float basalHeight => BoundingBox.Width / BoundingBox.Height; private float availableHeight => 1 - basalHeight; - private readonly IReadOnlyList values; + private readonly IReadOnlyList> values; private readonly float maxValue; + private readonly bool isCentre; - private readonly Circle[] boxOriginals; + private Circle[] boxOriginals; private Circle boxAdjustment; - public Bar(IReadOnlyList values, float maxValue, bool isCentre) + [Resolved] + private OsuColour colours { get; set; } + + public Bar(IDictionary values, float maxValue, bool isCentre) { - this.values = values; + this.values = values.OrderBy(v => v.Key.OrderingIndex()).ToList(); this.maxValue = maxValue; + this.isCentre = isCentre; RelativeSizeAxes = Axes.Both; Masking = true; + } + [BackgroundDependencyLoader] + private void load() + { if (values.Any()) { - InternalChildren = boxOriginals = values.Select((v, i) => new Circle + boxOriginals = values.Select((v, i) => new Circle { RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre && i == 0 ? Color4.White : v.Colour, + Colour = isCentre && i == 0 ? Color4.White : colours.DrawForHitResult(v.Key), Height = 0, }).ToArray(); + // The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position, + // to the top, and the bottom bar should be drawn more toward the front by design, + // while the drawing order is from the back to the front, so the order passed to `InternalChildren` is the opposite. + InternalChildren = boxOriginals.Reverse().ToArray(); } else { + // A bin with no value draws a grey dot instead. InternalChildren = boxOriginals = new[] { new Circle From 54f0bb797e46bb581017482876bea3ca4aec83a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:32:45 +0900 Subject: [PATCH 114/174] refactor(osu.Game): remove nullable optouts in HitResult.cs --- osu.Game/Rulesets/Scoring/HitResult.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 8078363212..3349bcf245 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.ComponentModel; From 267465df182b67e783d13b015c646c4d501ad7b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Acid=20Chicken=20=28=E7=A1=AB=E9=85=B8=E9=B6=8F=29?= Date: Wed, 7 Sep 2022 23:34:46 +0900 Subject: [PATCH 115/174] chore(osu.Game): combine `Osu.Colour.{Draw,Text}ForHitResult` into `OsuColour.ForHitResult` --- osu.Game/Graphics/OsuColour.cs | 25 +------------------ .../Leaderboards/LeaderboardScoreTooltip.cs | 2 +- .../Judgements/DefaultJudgementPiece.cs | 2 +- .../Play/HUD/HitErrorMeters/HitErrorMeter.cs | 2 +- .../Expanded/Statistics/HitResultStatistic.cs | 2 +- .../HitEventTimingDistributionGraph.cs | 2 +- 6 files changed, 6 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 022b1a363f..91161d5c71 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -102,30 +102,7 @@ namespace osu.Game.Graphics /// /// Retrieves the colour for a . /// - public Color4 TextForHitResult(HitResult judgement) - { - switch (judgement) - { - case HitResult.Perfect: - case HitResult.Great: - return Blue; - - case HitResult.Ok: - case HitResult.Good: - return Green; - - case HitResult.Meh: - return Yellow; - - case HitResult.Miss: - return Red; - - default: - return Color4.White; - } - } - - public Color4 DrawForHitResult(HitResult result) + public Color4 ForHitResult(HitResult result) { switch (result) { diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 23d4e64191..2f3ece0e3b 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -156,7 +156,7 @@ namespace osu.Game.Online.Leaderboards { Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), Text = displayName.ToUpper(), - Colour = colours.TextForHitResult(result), + Colour = colours.ForHitResult(result), }, new OsuSpriteText { diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index a854bf37f5..c2b27d4ce8 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = Result.GetDescription().ToUpperInvariant(), - Colour = colours.TextForHitResult(Result), + Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), } diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 35d28b8e98..dda17c25e6 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters protected Color4 GetColourForHitResult(HitResult result) { - return colours.DrawForHitResult(result); + return colours.ForHitResult(result); } /// diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index 429b72c07c..c23a5e668d 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics [BackgroundDependencyLoader] private void load(OsuColour colours) { - HeaderText.Colour = colours.TextForHitResult(Result); + HeaderText.Colour = colours.ForHitResult(Result); } } } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index ad2f979138..52682c35a0 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -243,7 +243,7 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Colour = isCentre && i == 0 ? Color4.White : colours.DrawForHitResult(v.Key), + Colour = isCentre && i == 0 ? Color4.White : colours.ForHitResult(v.Key), Height = 0, }).ToArray(); // The bars of the stacked bar graph will be processed (stacked) from the bottom, which is the base position, From 68ea5a765f25f8d6a0df05d95076d9ff21ee8598 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Sep 2022 23:56:45 +0900 Subject: [PATCH 116/174] 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 219912425f..cd4dfec3d7 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 43e3076f5c..6d0827a107 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 0f0bf2848c..3316bf5a49 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 50923b6e5b5722f59e4bbaaa8ef3f218117bd414 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 8 Sep 2022 00:25:55 +0300 Subject: [PATCH 117/174] Move track assignment below --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 26fb127e83..20a5c95605 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -68,10 +68,11 @@ namespace osu.Game.Screens.Play public MasterGameplayClockContainer(WorkingBeatmap beatmap, double skipTargetTime) : base(beatmap.Track, true) { - track = beatmap.Track; this.beatmap = beatmap; this.skipTargetTime = skipTargetTime; + track = beatmap.Track; + StartTime = findEarliestStartTime(); } From 3b116a1a474c786eb7beba91317dc43f31a9b93d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 13:17:27 +0900 Subject: [PATCH 118/174] Fix mods not being set on `BeginPlayingInternal` --- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index cb3711ebb5..e6d8e473bb 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -130,6 +131,7 @@ namespace osu.Game.Tests.Visual.Spectator // Track the local user's playing beatmap ID. Debug.Assert(state.BeatmapID != null); userBeatmapDictionary[api.LocalUser.Value.Id] = state.BeatmapID.Value; + userModsDictionary[api.LocalUser.Value.Id] = state.Mods.ToArray(); return ((ISpectatorClient)this).UserBeganPlaying(api.LocalUser.Value.Id, state); } From 6aac0bd4e96e9da903c22a86162b596108847335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 13:43:37 +0900 Subject: [PATCH 119/174] Update test to account for the fact that SSDQ is not zero anymore --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 34723e3329..5c9e9043a7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -78,8 +78,9 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true); AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); + AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("zero width", () => sprites.All(s => s.ScreenSpaceDrawQuad.Width == 0)); + AddAssert("sprites not present", () => sprites.All(s => s.IsPresent)); } [Test] From 3d9bf25c8afde4a8225035175f1ac4109955d757 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 14:49:43 +0900 Subject: [PATCH 120/174] Apply NRT to `TestScenePlaySongSelect` Very mess. --- .../SongSelect/TestScenePlaySongSelect.cs | 309 ++++++++++-------- 1 file changed, 172 insertions(+), 137 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 5db46e3097..cc8746959b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using NUnit.Framework; @@ -13,6 +12,7 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; @@ -46,11 +46,12 @@ namespace osu.Game.Tests.Visual.SongSelect [TestFixture] public class TestScenePlaySongSelect : ScreenTestScene { - private BeatmapManager manager; - private RulesetStore rulesets; - private MusicController music; - private WorkingBeatmap defaultBeatmap; - private TestSongSelect songSelect; + private BeatmapManager manager = null!; + private RulesetStore rulesets = null!; + private MusicController music = null!; + private WorkingBeatmap defaultBeatmap = null!; + private OsuConfigManager config = null!; + private TestSongSelect? songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -69,8 +70,6 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } - private OsuConfigManager config; - public override void SetUpSteps() { base.SetUpSteps(); @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect songSelect = null; }); - AddStep("delete all beatmaps", () => manager?.Delete()); + AddStep("delete all beatmaps", () => manager.Delete()); } [Test] @@ -98,7 +97,7 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden); - AddStep("delete all beatmaps", () => manager?.Delete()); + AddStep("delete all beatmaps", () => manager.Delete()); AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible); } @@ -144,7 +143,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + AddAssert("filter count is 1", () => songSelect?.FilterCount == 1); } [Test] @@ -156,7 +155,7 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); @@ -166,7 +165,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -179,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); @@ -189,7 +188,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Key(Key.Down); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -202,23 +201,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); - AddUntilStep("wait for beatmaps to load", () => songSelect.Carousel.ChildrenOfType().Any()); + AddUntilStep("wait for beatmaps to load", () => songSelect!.Carousel.ChildrenOfType().Any()); AddStep("select next and enter", () => { - InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); + InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); InputManager.Click(MouseButton.Left); InputManager.Key(Key.Enter); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection changed", () => selected != Beatmap.Value); } @@ -231,14 +230,14 @@ namespace osu.Game.Tests.Visual.SongSelect waitForInitialSelection(); - WorkingBeatmap selected = null; + WorkingBeatmap? selected = null; AddStep("store selected beatmap", () => selected = Beatmap.Value); AddStep("select next and enter", () => { - InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect.Carousel.SelectedBeatmapInfo))); + InputManager.MoveMouseTo(songSelect!.Carousel.ChildrenOfType() + .First(b => !((CarouselBeatmap)b.Item).BeatmapInfo.Equals(songSelect!.Carousel.SelectedBeatmapInfo))); InputManager.PressButton(MouseButton.Left); @@ -247,7 +246,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.ReleaseButton(MouseButton.Left); }); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddAssert("ensure selection didn't change", () => selected == Beatmap.Value); } @@ -260,11 +259,11 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("filter count is 1", () => songSelect.FilterCount == 1); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("filter count is 1", () => songSelect!.FilterCount == 1); } [Test] @@ -278,13 +277,13 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("filter count is 2", () => songSelect.FilterCount == 2); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("filter count is 2", () => songSelect!.FilterCount == 2); } [Test] @@ -295,7 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); - AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for not current", () => !songSelect!.IsCurrentScreen()); AddStep("update beatmap", () => { @@ -304,9 +303,9 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap); }); - AddStep("return", () => songSelect.MakeCurrent()); - AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); - AddAssert("carousel updated", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(Beatmap.Value.BeatmapInfo)); + AddStep("return", () => songSelect!.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect!.IsCurrentScreen()); + AddAssert("carousel updated", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(Beatmap.Value.BeatmapInfo) == true); } [Test] @@ -318,15 +317,15 @@ namespace osu.Game.Tests.Visual.SongSelect addRulesetImportStep(0); checkMusicPlaying(true); - AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First())); + AddStep("select first", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.First())); checkMusicPlaying(true); AddStep("manual pause", () => music.TogglePause()); checkMusicPlaying(false); - AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false)); + AddStep("select next difficulty", () => songSelect!.Carousel.SelectNext(skipDifficulties: false)); checkMusicPlaying(false); - AddStep("select next set", () => songSelect.Carousel.SelectNext()); + AddStep("select next set", () => songSelect!.Carousel.SelectNext()); checkMusicPlaying(true); } @@ -366,13 +365,13 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestDummy() { createSongSelect(); - AddUntilStep("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); + AddUntilStep("dummy selected", () => songSelect!.CurrentBeatmap == defaultBeatmap); - AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); + AddUntilStep("dummy shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); } [Test] @@ -381,7 +380,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); addManyTestMaps(); - AddUntilStep("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); + AddUntilStep("random map selected", () => songSelect!.CurrentBeatmap != defaultBeatmap); AddStep(@"Sort by Artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); AddStep(@"Sort by Title", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title)); @@ -398,7 +397,7 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addRulesetImportStep(2); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -408,13 +407,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 1); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 1); changeRuleset(0); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -423,7 +422,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); changeRuleset(0); - Live original = null!; + Live? original = null; int originalOnlineSetID = 0; AddStep(@"Sort by artist", () => config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist)); @@ -431,12 +430,17 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import original", () => { original = manager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely(); - originalOnlineSetID = original!.Value.OnlineID; + + Debug.Assert(original != null); + + originalOnlineSetID = original.Value.OnlineID; }); // This will move the beatmap set to a different location in the carousel. AddStep("Update original with bogus info", () => { + Debug.Assert(original != null); + original.PerformWrite(set => { foreach (var beatmap in set.Beatmaps) @@ -457,13 +461,19 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(testBeatmapSetInfo); }, 10); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); - Task> updateTask = null!; - AddStep("update beatmap", () => updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value)); + Task?> updateTask = null!; + + AddStep("update beatmap", () => + { + Debug.Assert(original != null); + + updateTask = manager.ImportAsUpdate(new ProgressNotification(), new ImportTask(TestResources.GetQuickTestBeatmapForImport()), original.Value); + }); AddUntilStep("wait for update completion", () => updateTask.IsCompleted); - AddUntilStep("retained selection", () => songSelect.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); + AddUntilStep("retained selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == originalOnlineSetID); } [Test] @@ -473,13 +483,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); addRulesetImportStep(0); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap/ruleset externally", () => { @@ -490,10 +500,10 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); + AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -503,13 +513,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Ruleset.OnlineID == 2); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Ruleset.OnlineID == 2); addRulesetImportStep(0); addRulesetImportStep(0); addRulesetImportStep(0); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap/ruleset externally", () => { @@ -520,12 +530,12 @@ namespace osu.Game.Tests.Visual.SongSelect Ruleset.Value = rulesets.AvailableRulesets.First(r => r.OnlineID == 0); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(target) == true); AddUntilStep("has correct ruleset", () => Ruleset.Value.OnlineID == 0); // this is an important check, to make sure updateComponentFromBeatmap() was actually run - AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); + AddUntilStep("selection shown on wedge", () => songSelect!.CurrentBeatmapDetailsBeatmap.BeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -543,12 +553,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("change ruleset", () => { SelectedMods.ValueChanged += onModChange; - songSelect.Ruleset.ValueChanged += onRulesetChange; + songSelect!.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; SelectedMods.ValueChanged -= onModChange; - songSelect.Ruleset.ValueChanged -= onRulesetChange; + songSelect!.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); @@ -579,18 +589,18 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addManyTestMaps(); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); bool startRequested = false; AddStep("set filter and finalize", () => { - songSelect.StartRequested = () => startRequested = true; + songSelect!.StartRequested = () => startRequested = true; - songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); - songSelect.FinaliseSelection(); + songSelect!.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); + songSelect!.FinaliseSelection(); - songSelect.StartRequested = null; + songSelect!.StartRequested = null; }); AddAssert("start not requested", () => !startRequested); @@ -610,15 +620,15 @@ namespace osu.Game.Tests.Visual.SongSelect // used for filter check below AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - BeatmapInfo target = null; + BeatmapInfo? target = null; int targetRuleset = differentRuleset ? 1 : 0; @@ -632,24 +642,24 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); AddAssert("selected only shows expected ruleset (plus converts)", () => { - var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); + var selectedPanel = songSelect!.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected); // special case for converts checked here. return selectedPanel.ChildrenOfType().All(i => i.IsFiltered || i.Item.BeatmapInfo.Ruleset.OnlineID == targetRuleset || i.Item.BeatmapInfo.Ruleset.OnlineID == 0); }); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); + AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty); + AddStep("reset filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = string.Empty); AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.MatchesOnlineID(target) == true); - AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); + AddAssert("carousel still correct", () => songSelect!.Carousel.SelectedBeatmapInfo.MatchesOnlineID(target)); } [Test] @@ -662,15 +672,15 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(0); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddUntilStep("has no selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); - BeatmapInfo target = null; + BeatmapInfo? target = null; AddStep("select beatmap externally", () => { @@ -682,15 +692,15 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); + AddUntilStep("has selection", () => songSelect!.Carousel.SelectedBeatmapInfo != null); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); + AddUntilStep("carousel has correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.MatchesOnlineID(target) == true); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(target)); - AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo"); + AddStep("set filter text", () => songSelect!.FilterControl.ChildrenOfType().First().Text = "nononoo"); AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap); - AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); + AddAssert("carousel lost selection", () => songSelect!.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -711,11 +721,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("no mods selected", () => songSelect.Mods.Value.Count == 0); + AddAssert("no mods selected", () => songSelect!.Mods.Value.Count == 0); } [Test] @@ -738,11 +748,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("autoplay still selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("autoplay still selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); } [Test] @@ -765,11 +775,11 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); - AddAssert("only autoplay selected", () => songSelect.Mods.Value.Single() is ModAutoplay); + AddAssert("only autoplay selected", () => songSelect!.Mods.Value.Single() is ModAutoplay); - AddUntilStep("wait for return to ss", () => songSelect.IsCurrentScreen()); + AddUntilStep("wait for return to ss", () => songSelect!.IsCurrentScreen()); - AddAssert("relax returned", () => songSelect.Mods.Value.Single() is ModRelax); + AddAssert("relax returned", () => songSelect!.Mods.Value.Single() is ModRelax); } [Test] @@ -778,10 +788,10 @@ namespace osu.Game.Tests.Visual.SongSelect Guid? previousID = null; createSongSelect(); addRulesetImportStep(0); - AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last())); - AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmapInfo.ID); - AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First())); - AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmapInfo.ID == previousID); + AddStep("Move to last difficulty", () => songSelect!.Carousel.SelectBeatmap(songSelect!.Carousel.BeatmapSets.First().Beatmaps.Last())); + AddStep("Store current ID", () => previousID = songSelect!.Carousel.SelectedBeatmapInfo!.ID); + AddStep("Hide first beatmap", () => manager.Hide(songSelect!.Carousel.SelectedBeatmapSet!.Beatmaps.First())); + AddAssert("Selected beatmap has not changed", () => songSelect!.Carousel.SelectedBeatmapInfo?.ID == previousID); } [Test] @@ -792,17 +802,24 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for selection", () => !Beatmap.IsDefault); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet set = null!; AddStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().First(); + set = songSelect!.Carousel.ChildrenOfType().First(); }); - FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon difficultyIcon = null!; + AddUntilStep("Find an icon", () => { - return (difficultyIcon = set.ChildrenOfType() - .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex())) != null; + var foundIcon = set.ChildrenOfType() + .FirstOrDefault(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex()); + + if (foundIcon == null) + return false; + + difficultyIcon = foundIcon; + return true; }); AddStep("Click on a difficulty", () => @@ -815,21 +832,24 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon)); double? maxBPM = null; - AddStep("Filter some difficulties", () => songSelect.Carousel.Filter(new FilterCriteria + AddStep("Filter some difficulties", () => songSelect!.Carousel.Filter(new FilterCriteria { BPM = new FilterCriteria.OptionalRange { - Min = maxBPM = songSelect.Carousel.SelectedBeatmapSet.MaxBPM, + Min = maxBPM = songSelect!.Carousel.SelectedBeatmapSet!.MaxBPM, IsLowerInclusive = true } })); - BeatmapInfo filteredBeatmap = null; - FilterableDifficultyIcon filteredIcon = null; + BeatmapInfo? filteredBeatmap = null; + FilterableDifficultyIcon? filteredIcon = null; AddStep("Get filtered icon", () => { - var selectedSet = songSelect.Carousel.SelectedBeatmapSet; + var selectedSet = songSelect!.Carousel.SelectedBeatmapSet; + + Debug.Assert(selectedSet != null); + filteredBeatmap = selectedSet.Beatmaps.First(b => b.BPM < maxBPM); int filteredBeatmapIndex = getBeatmapIndex(selectedSet, filteredBeatmap); filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex); @@ -842,7 +862,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(filteredBeatmap)); + AddAssert("Selected beatmap correct", () => songSelect!.Carousel.SelectedBeatmapInfo?.Equals(filteredBeatmap) == true); } [Test] @@ -907,14 +927,14 @@ namespace osu.Game.Tests.Visual.SongSelect manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)); }); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet? set = null; AddUntilStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().FirstOrDefault(); + set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); return set != null; }); - FilterableDifficultyIcon difficultyIcon = null; + FilterableDifficultyIcon? difficultyIcon = null; AddUntilStep("Find an icon for different ruleset", () => { difficultyIcon = set.ChildrenOfType() @@ -937,7 +957,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.OnlineID == 3); - AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet?.OnlineID == previousSetID); + AddAssert("Selected beatmap still same set", () => songSelect!.Carousel.SelectedBeatmapInfo?.BeatmapSet?.OnlineID == previousSetID); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID == 3); } @@ -948,7 +968,7 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); - BeatmapSetInfo imported = null; + BeatmapSetInfo? imported = null; AddStep("import huge difficulty count map", () => { @@ -956,20 +976,27 @@ namespace osu.Game.Tests.Visual.SongSelect imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets))?.Value; }); - AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); + AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported?.Beatmaps.First())); - DrawableCarouselBeatmapSet set = null; + DrawableCarouselBeatmapSet? set = null; AddUntilStep("Find the DrawableCarouselBeatmapSet", () => { - set = songSelect.Carousel.ChildrenOfType().FirstOrDefault(); + set = songSelect!.Carousel.ChildrenOfType().FirstOrDefault(); return set != null; }); - GroupedDifficultyIcon groupIcon = null; + GroupedDifficultyIcon groupIcon = null!; + AddUntilStep("Find group icon for different ruleset", () => { - return (groupIcon = set.ChildrenOfType() - .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null; + var foundIcon = set.ChildrenOfType() + .FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3); + + if (foundIcon == null) + return false; + + groupIcon = foundIcon; + return true; }); AddAssert("Check ruleset is osu!", () => Ruleset.Value.OnlineID == 0); @@ -1004,7 +1031,7 @@ namespace osu.Game.Tests.Visual.SongSelect // this ruleset change should be overridden by the present. Ruleset.Value = getSwitchBeatmap().Ruleset; - songSelect.PresentScore(new ScoreInfo + songSelect!.PresentScore(new ScoreInfo { User = new APIUser { Username = "woo" }, BeatmapInfo = getPresentBeatmap(), @@ -1012,7 +1039,7 @@ namespace osu.Game.Tests.Visual.SongSelect }); }); - AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1038,10 +1065,10 @@ namespace osu.Game.Tests.Visual.SongSelect // this beatmap change should be overridden by the present. Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); - songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); + songSelect!.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); }); - AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); + AddUntilStep("wait for results screen presented", () => !songSelect!.IsCurrentScreen()); AddAssert("check beatmap is correct for score", () => Beatmap.Value.BeatmapInfo.MatchesOnlineID(getPresentBeatmap())); AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0); @@ -1054,23 +1081,29 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible); + AddUntilStep("mod overlay shown", () => songSelect!.ModSelect.State.Value == Visibility.Visible); AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1)); - AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden); + AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden); } private void waitForInitialSelection() { AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); - AddUntilStep("wait for difficulty panels visible", () => songSelect.Carousel.ChildrenOfType().Any()); + AddUntilStep("wait for difficulty panels visible", () => songSelect!.Carousel.ChildrenOfType().Any()); } private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info); - private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType().FirstOrDefault(); + private NoResultsPlaceholder? getPlaceholder() => songSelect!.ChildrenOfType().FirstOrDefault(); - private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); + private int getCurrentBeatmapIndex() + { + Debug.Assert(songSelect!.Carousel.SelectedBeatmapSet != null); + Debug.Assert(songSelect!.Carousel.SelectedBeatmapInfo != null); + + return getBeatmapIndex(songSelect!.Carousel.SelectedBeatmapSet, songSelect!.Carousel.SelectedBeatmapInfo); + } private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) { @@ -1079,14 +1112,14 @@ namespace osu.Game.Tests.Visual.SongSelect private void addRulesetImportStep(int id) { - Live imported = null; + Live? imported = null; AddStep($"import test map for ruleset {id}", () => imported = importForRuleset(id)); // This is specifically for cases where the add is happening post song select load. // For cases where song select is null, the assertions are provided by the load checks. - AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); + AddUntilStep("wait for imported to arrive in carousel", () => songSelect == null || songSelect!.Carousel.BeatmapSets.Any(s => s.ID == imported?.ID)); } - private Live importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); + private Live? importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())); private void checkMusicPlaying(bool playing) => AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); @@ -1098,8 +1131,8 @@ namespace osu.Game.Tests.Visual.SongSelect private void createSongSelect() { AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); - AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); - AddUntilStep("wait for carousel loaded", () => songSelect.Carousel.IsAlive); + AddUntilStep("wait for present", () => songSelect!.IsCurrentScreen()); + AddUntilStep("wait for carousel loaded", () => songSelect!.Carousel.IsAlive); } /// @@ -1123,12 +1156,14 @@ namespace osu.Game.Tests.Visual.SongSelect protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - rulesets?.Dispose(); + + if (rulesets.IsNotNull()) + rulesets.Dispose(); } private class TestSongSelect : PlaySongSelect { - public Action StartRequested; + public Action? StartRequested; public new Bindable Ruleset => base.Ruleset; From 7b079c134edae38793d3c98cd14ff2f205888525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:48:04 +0900 Subject: [PATCH 121/174] Update test to actually test what was intended --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 5c9e9043a7..54b0bc2080 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("sprites not present", () => sprites.All(s => s.IsPresent)); + AddAssert("sprites not present", () => !sprites.All(s => s.IsPresent)); } [Test] From 9ead5e59d3262d1e18ec9ba5dce893f275385d0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:51:15 +0900 Subject: [PATCH 122/174] Fix incorrectly displaying minimum value in placeholder messaging --- osu.Game/Screens/Select/NoResultsPlaceholder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/NoResultsPlaceholder.cs b/osu.Game/Screens/Select/NoResultsPlaceholder.cs index f44aa01588..73b53defe0 100644 --- a/osu.Game/Screens/Select/NoResultsPlaceholder.cs +++ b/osu.Game/Screens/Select/NoResultsPlaceholder.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Select config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1); }); - string lowerStar = filter.UserStarDifficulty.Min == null ? "0,0" : $"{filter.UserStarDifficulty.Min:N1}"; + string lowerStar = $"{filter.UserStarDifficulty.Min ?? 0:N1}"; string upperStar = filter.UserStarDifficulty.Max == null ? "∞" : $"{filter.UserStarDifficulty.Max:N1}"; textFlow.AddText($" the {lowerStar} - {upperStar} star difficulty filter."); From d523a2ac339c88ade0176df1d9dbe26b678cbc7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 15:53:08 +0900 Subject: [PATCH 123/174] Rename default value field and make `private` --- .../Select/DifficultyRangeFilterControl.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs index eb5a797ac0..45e7ff4caa 100644 --- a/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs +++ b/osu.Game/Screens/Select/DifficultyRangeFilterControl.cs @@ -65,7 +65,10 @@ namespace osu.Game.Screens.Select private class MinimumStarsSlider : StarsSlider { - public MinimumStarsSlider() : base("0") { } + public MinimumStarsSlider() + : base("0") + { + } public override LocalisableString TooltipText => Current.Value.ToString(@"0.## stars"); @@ -86,7 +89,10 @@ namespace osu.Game.Screens.Select private class MaximumStarsSlider : StarsSlider { - public MaximumStarsSlider() : base("∞") { } + public MaximumStarsSlider() + : base("∞") + { + } protected override void LoadComplete() { @@ -102,15 +108,15 @@ namespace osu.Game.Screens.Select private class StarsSlider : OsuSliderBar { + private readonly string defaultString; + public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : Current.Value.ToString(@"0.## stars"); - protected readonly string DefaultValue; - - public StarsSlider(string defaultValue) + protected StarsSlider(string defaultString) { - DefaultValue = defaultValue; + this.defaultString = defaultString; } protected override bool OnHover(HoverEvent e) @@ -138,7 +144,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(current => { - currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : DefaultValue; + currentDisplay.Text = current.NewValue != Current.Default ? current.NewValue.ToString("N1") : defaultString; }, true); } } From 9e42d6167fdd630c360ec6706a6cc5f20f9ce836 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 16:07:21 +0900 Subject: [PATCH 124/174] Fix tournament match scores resetting if `StartMatch` is called on an in-progress match --- osu.Game.Tournament/Models/TournamentMatch.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs index 2f72dc9257..97c2060f2c 100644 --- a/osu.Game.Tournament/Models/TournamentMatch.cs +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -106,13 +106,16 @@ namespace osu.Game.Tournament.Models } /// - /// Initialise this match with zeroed scores. Will be a noop if either team is not present. + /// Initialise this match with zeroed scores. Will be a noop if either team is not present or if either of the scores are non-zero. /// public void StartMatch() { if (Team1.Value == null || Team2.Value == null) return; + if (Team1Score.Value > 0 || Team2Score.Value > 0) + return; + Team1Score.Value = 0; Team2Score.Value = 0; } From b0b4da533a399327da175f01da06668f821b95e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 16:59:20 +0900 Subject: [PATCH 125/174] Expose gameplay adjustments via MultiSpectatorPlayer instead --- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 14 ++++---------- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 8 ++++++++ .../Multiplayer/Spectate/SpectatorPlayerClock.cs | 3 --- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 7e910b7946..32d0e66d90 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; @@ -13,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class MultiSpectatorPlayer : SpectatorPlayer { + public IAdjustableAudioComponent GameplayAdjustments => GameplayClockContainer.GameplayAdjustments; + private readonly SpectatorPlayerClock spectatorPlayerClock; /// @@ -53,15 +56,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - { - var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); - - // Directionality is important, as BindAdjustments is... not actually a bidirectional bind... - // We want to ensure that any adjustments applied by the Player instance are applied to the SpectatorPlayerClock - // so they can be consumed by the spectator screen (and applied to the master clock / track). - spectatorPlayerClock.GameplayAdjustments.BindAdjustments(gameplayClockContainer.GameplayAdjustments); - - return gameplayClockContainer; - } + => new GameplayClockContainer(spectatorPlayerClock); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index b7c07372dc..617977bdde 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -189,7 +189,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (boundAdjustments != null) masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); - boundAdjustments = first.SpectatorPlayerClock.GameplayAdjustments; + boundAdjustments = first.GameplayAdjustments; masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 36f6631ebf..8ecb5adb76 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -42,6 +42,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public readonly SpectatorPlayerClock SpectatorPlayerClock; + /// + /// The gameplay adjustments applied by the loaded in this area. + /// + public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + /// /// The currently-loaded score. /// @@ -97,6 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); + + GameplayAdjustments.BindAdjustments(player.GameplayAdjustments); + return player; })); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs index 5667be1f4b..45615d4e19 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/SpectatorPlayerClock.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio; using osu.Framework.Timing; using osu.Game.Screens.Play; @@ -20,8 +19,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private readonly GameplayClockContainer masterClock; - public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); - public double CurrentTime { get; private set; } /// From ed81297611bbe18d3df0beba6b6e07448df0b282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 16:48:43 +0900 Subject: [PATCH 126/174] Fix playlist items showing download button briefly during initial local presence checks --- .../Beatmaps/TestSceneBeatmapCardDownloadButton.cs | 2 ++ .../Gameplay/TestScenePlayerLocalScoreImport.cs | 4 +++- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Drawables/Cards/Buttons/DownloadButton.cs | 12 ++++++++---- osu.Game/Online/DownloadState.cs | 1 + .../Rooms/OnlinePlayBeatmapAvailabilityTracker.cs | 1 + .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++++ osu.Game/Screens/Play/SaveFailedScoreButton.cs | 3 +-- 8 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs index 3308ffe714..10515fd95f 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardDownloadButton.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Configuration; +using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Resources.Localisation.Web; @@ -58,6 +59,7 @@ namespace osu.Game.Tests.Visual.Beatmaps { Anchor = Anchor.Centre, Origin = Anchor.Centre, + State = { Value = DownloadState.NotDownloaded }, Scale = new Vector2(2) }; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index f3e436e31f..247b822dc3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -81,9 +81,11 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); AddUntilStep("fail screen displayed", () => Player.ChildrenOfType().First().State.Value == Visibility.Visible); + AddUntilStep("wait for button clickable", () => Player.ChildrenOfType().First().ChildrenOfType().First().Enabled.Value); + AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) == null)); AddStep("click save button", () => Player.ChildrenOfType().First().ChildrenOfType().First().TriggerClick()); - AddUntilStep("score not in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 9d70d1ef33..cd227630c1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for load", () => downloadButton.IsLoaded); - AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown); AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index fdb43cb47e..1b15b2498c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -17,8 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { public class DownloadButton : BeatmapCardIconButton { - public IBindable State => state; - private readonly Bindable state = new Bindable(); + public Bindable State { get; } = new Bindable(); private readonly APIBeatmapSet beatmapSet; @@ -48,14 +47,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons { base.LoadComplete(); preferNoVideo.BindValueChanged(_ => updateState()); - state.BindValueChanged(_ => updateState(), true); + State.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } private void updateState() { - switch (state.Value) + switch (State.Value) { + case DownloadState.Unknown: + Action = null; + TooltipText = string.Empty; + break; + case DownloadState.Downloading: case DownloadState.Importing: Action = null; diff --git a/osu.Game/Online/DownloadState.cs b/osu.Game/Online/DownloadState.cs index a58c40d16a..f4ecb28b90 100644 --- a/osu.Game/Online/DownloadState.cs +++ b/osu.Game/Online/DownloadState.cs @@ -5,6 +5,7 @@ namespace osu.Game.Online { public enum DownloadState { + Unknown, NotDownloaded, Downloading, Importing, diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index bb8ec4f6ff..7f8f9703e4 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -114,6 +114,7 @@ namespace osu.Game.Online.Rooms switch (downloadTracker.State.Value) { + case DownloadState.Unknown: case DownloadState.NotDownloaded: availability.Value = BeatmapAvailability.NotDownloaded(); break; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index beecd56b52..bda616d5c3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -561,6 +561,10 @@ namespace osu.Game.Screens.OnlinePlay { switch (state.NewValue) { + case DownloadState.Unknown: + // Ignore initial state to ensure the button doesn't briefly appear. + break; + case DownloadState.LocallyAvailable: // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching. if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null) diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 3f6e741dff..7358ff3de4 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -63,8 +63,7 @@ namespace osu.Game.Screens.Play if (player != null) { importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); - if (importedScore != null) - state.Value = DownloadState.LocallyAvailable; + state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; } state.BindValueChanged(state => From b559d4ecdf8dc6c4df4b6582bc34710d734d3b35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:14:06 +0900 Subject: [PATCH 127/174] Rename GameplayAdjustments -> AdjustmentsFromMods --- .../NonVisual/GameplayClockContainerTest.cs | 2 +- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 11 +++++++++-- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 6 +++--- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 6 +++--- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- osu.Game/Screens/Play/GameplayClockExtensions.cs | 4 ++-- osu.Game/Screens/Play/IGameplayClock.cs | 4 ++-- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 10 files changed, 25 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs index 80f0aaeb55..363c7a459e 100644 --- a/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs +++ b/osu.Game.Tests/NonVisual/GameplayClockContainerTest.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual public TestGameplayClockContainer(IFrameBasedClock underlyingClock) : base(underlyingClock) { - GameplayAdjustments.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); + AdjustmentsFromMods.AddAdjustment(AdjustableProperty.Frequency, new BindableDouble(2.0)); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index f0c7a398eb..6f57cfe2f7 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.UI private readonly AudioAdjustments gameplayAdjustments = new AudioAdjustments(); - public IAdjustableAudioComponent GameplayAdjustments => parentGameplayClock?.GameplayAdjustments ?? gameplayAdjustments; + public IAdjustableAudioComponent AdjustmentsFromMods => parentGameplayClock?.AdjustmentsFromMods ?? gameplayAdjustments; #endregion diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 32d0e66d90..2d32bc7516 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -14,7 +14,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public class MultiSpectatorPlayer : SpectatorPlayer { - public IAdjustableAudioComponent GameplayAdjustments => GameplayClockContainer.GameplayAdjustments; + /// + /// All adjustments applied to the clock of this which come from mods. + /// + public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); private readonly SpectatorPlayerClock spectatorPlayerClock; @@ -56,6 +59,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate } protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) - => new GameplayClockContainer(spectatorPlayerClock); + { + var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); + ClockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); + return gameplayClockContainer; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 617977bdde..42a946a023 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -187,10 +187,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate private void bindAudioAdjustments(PlayerArea first) { if (boundAdjustments != null) - masterClockContainer.GameplayAdjustments.UnbindAdjustments(boundAdjustments); + masterClockContainer.AdjustmentsFromMods.UnbindAdjustments(boundAdjustments); - boundAdjustments = first.GameplayAdjustments; - masterClockContainer.GameplayAdjustments.BindAdjustments(boundAdjustments); + boundAdjustments = first.ClockAdjustmentsFromMods; + masterClockContainer.AdjustmentsFromMods.BindAdjustments(boundAdjustments); } private bool isCandidateAudioSource(SpectatorPlayerClock? clock) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 8ecb5adb76..585d464a2a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -43,9 +43,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate public readonly SpectatorPlayerClock SpectatorPlayerClock; /// - /// The gameplay adjustments applied by the loaded in this area. + /// The clock adjustments applied by the loaded in this area. /// - public readonly AudioAdjustments GameplayAdjustments = new AudioAdjustments(); + public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); /// /// The currently-loaded score. @@ -103,7 +103,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - GameplayAdjustments.BindAdjustments(player.GameplayAdjustments); + ClockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); return player; })); diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e64c628fa0..35b79fd628 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Play /// public double StartTime { get; protected set; } - public IAdjustableAudioComponent GameplayAdjustments { get; } = new AudioAdjustments(); + public IAdjustableAudioComponent AdjustmentsFromMods { get; } = new AudioAdjustments(); private readonly BindableBool isPaused = new BindableBool(true); diff --git a/osu.Game/Screens/Play/GameplayClockExtensions.cs b/osu.Game/Screens/Play/GameplayClockExtensions.cs index ec77a94ce9..5e88b41080 100644 --- a/osu.Game/Screens/Play/GameplayClockExtensions.cs +++ b/osu.Game/Screens/Play/GameplayClockExtensions.cs @@ -17,8 +17,8 @@ namespace osu.Game.Screens.Play double rate = clock.Rate == 0 ? 1 : Math.Sign(clock.Rate); return rate - * clock.GameplayAdjustments.AggregateFrequency.Value - * clock.GameplayAdjustments.AggregateTempo.Value; + * clock.AdjustmentsFromMods.AggregateFrequency.Value + * clock.AdjustmentsFromMods.AggregateTempo.Value; } } } diff --git a/osu.Game/Screens/Play/IGameplayClock.cs b/osu.Game/Screens/Play/IGameplayClock.cs index c58d2dbcac..83ba5f3474 100644 --- a/osu.Game/Screens/Play/IGameplayClock.cs +++ b/osu.Game/Screens/Play/IGameplayClock.cs @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Play double StartTime { get; } /// - /// All adjustments applied to this clock which come from gameplay or mods. + /// All adjustments applied to this clock which come from mods. /// - IAdjustableAudioComponent GameplayAdjustments { get; } + IAdjustableAudioComponent AdjustmentsFromMods { get; } IBindable IsPaused { get; } } diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 20a5c95605..047f25a111 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -201,7 +201,7 @@ namespace osu.Game.Screens.Play musicController.ResetTrackAdjustments(); - track.BindAdjustments(GameplayAdjustments); + track.BindAdjustments(AdjustmentsFromMods); track.AddAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); @@ -213,7 +213,7 @@ namespace osu.Game.Screens.Play if (!speedAdjustmentsApplied) return; - track.UnbindAdjustments(GameplayAdjustments); + track.UnbindAdjustments(AdjustmentsFromMods); track.RemoveAdjustment(AdjustableProperty.Frequency, GameplayClock.ExternalPauseFrequencyAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e93502da13..1732c6533e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -997,7 +997,7 @@ namespace osu.Game.Screens.Play mod.ApplyToHUD(HUDOverlay); foreach (var mod in GameplayState.Mods.OfType()) - mod.ApplyToTrack(GameplayClockContainer.GameplayAdjustments); + mod.ApplyToTrack(GameplayClockContainer.AdjustmentsFromMods); updateGameplayState(); From c61c596c1f392c894e9511a3d2c4576e6ede0838 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:37:02 +0900 Subject: [PATCH 128/174] Expose as readonly IAggregateAudioAdjustment --- .../Multiplayer/Spectate/MultiSpectatorPlayer.cs | 5 +++-- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 10 +++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 2d32bc7516..8e79c89685 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -17,8 +17,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// All adjustments applied to the clock of this which come from mods. /// - public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); + public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods; + private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments(); private readonly SpectatorPlayerClock spectatorPlayerClock; /// @@ -61,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { var gameplayClockContainer = new GameplayClockContainer(spectatorPlayerClock); - ClockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); + clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); return gameplayClockContainer; } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 42a946a023..1fd04d35f8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private MultiplayerClient multiplayerClient { get; set; } = null!; - private AudioAdjustments? boundAdjustments; + private IAggregateAudioAdjustment? boundAdjustments; private readonly PlayerArea[] instances; private MasterGameplayClockContainer masterClockContainer = null!; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 585d464a2a..8eee3d0fb6 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// The clock adjustments applied by the loaded in this area. /// - public readonly AudioAdjustments ClockAdjustmentsFromMods = new AudioAdjustments(); + public IAggregateAudioAdjustment ClockAdjustmentsFromMods => clockAdjustmentsFromMods; /// /// The currently-loaded score. @@ -55,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [Resolved] private IBindable beatmap { get; set; } = null!; + private readonly AudioAdjustments clockAdjustmentsFromMods = new AudioAdjustments(); private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; @@ -101,9 +102,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); - player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - ClockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); + player.OnGameplayStarted += () => + { + clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); + OnGameplayStarted?.Invoke(); + }; return player; })); From 76eae73fa4b66268c2bc87a82d330bbc130a9c7f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 17:41:23 +0900 Subject: [PATCH 129/174] Revert unintended change --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 8eee3d0fb6..96f134568d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -102,12 +102,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate stack.Push(new MultiSpectatorPlayerLoader(Score, () => { var player = new MultiSpectatorPlayer(Score, SpectatorPlayerClock); + player.OnGameplayStarted += () => OnGameplayStarted?.Invoke(); - player.OnGameplayStarted += () => - { - clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); - OnGameplayStarted?.Invoke(); - }; + clockAdjustmentsFromMods.BindAdjustments(player.ClockAdjustmentsFromMods); return player; })); From c6521e4c7274e3cdd7a681a817622bb01e719f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 17:50:27 +0900 Subject: [PATCH 130/174] Rename ordering helper method --- osu.Game/Rulesets/Scoring/HitResult.cs | 7 ++----- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 3349bcf245..96e13e5861 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -286,14 +286,11 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Ordered index of a . Used for sorting. + /// Ordered index of a . Used for consistent order when displaying hit results to the user. /// /// The to get the index of. /// The index of . - public static int OrderingIndex(this HitResult result) - { - return order.IndexOf(result); - } + public static int GetIndexForOrderedDisplay(this HitResult result) => order.IndexOf(result); } #pragma warning restore CS0618 } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 52682c35a0..5335d77243 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Ranking.Statistics public Bar(IDictionary values, float maxValue, bool isCentre) { - this.values = values.OrderBy(v => v.Key.OrderingIndex()).ToList(); + this.values = values.OrderBy(v => v.Key.GetIndexForOrderedDisplay()).ToList(); this.maxValue = maxValue; this.isCentre = isCentre; From 0de220c45c502e618cf857b76f963471e54b8c26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 17:54:29 +0900 Subject: [PATCH 131/174] Change `IsExclusive` default value to `true` --- osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs | 1 - osu.Game/Online/Multiplayer/MatchStartCountdown.cs | 1 - osu.Game/Online/Multiplayer/MultiplayerCountdown.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs index 81ba56f35c..bbfc5a02c6 100644 --- a/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/ForceGameplayStartCountdown.cs @@ -15,6 +15,5 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] public sealed class ForceGameplayStartCountdown : MultiplayerCountdown { - public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs index b4c66e6f5b..fe65ebb059 100644 --- a/osu.Game/Online/Multiplayer/MatchStartCountdown.cs +++ b/osu.Game/Online/Multiplayer/MatchStartCountdown.cs @@ -11,6 +11,5 @@ namespace osu.Game.Online.Multiplayer [MessagePackObject] public sealed class MatchStartCountdown : MultiplayerCountdown { - public override bool IsExclusive => true; } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs index e8e2365f7b..61637ae970 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerCountdown.cs @@ -33,6 +33,6 @@ namespace osu.Game.Online.Multiplayer /// /// Whether only a single instance of this type may be active at any one time. /// - public virtual bool IsExclusive => false; + public virtual bool IsExclusive => true; } } From 53219ae4e7c7d71dd38dcf12c25b95faeaf7fca8 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 16:59:48 +0800 Subject: [PATCH 132/174] show fixed speed in mania scroll speed settings --- .../Configuration/ManiaRulesetConfigManager.cs | 2 -- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 10 +++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 35ad21442e..8e09a01469 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Configuration.Tracking; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 43d4aa77a2..51a45abd70 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.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. -#nullable disable - +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Mania LabelText = "Scrolling direction", Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, - new SettingsSlider + new SettingsSlider { LabelText = "Scroll speed", Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), @@ -47,5 +46,10 @@ namespace osu.Game.Rulesets.Mania } }; } + + private class ManiaScorllSlider : OsuSliderBar + { + public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; + } } } From f0850c42e536178aceeb17a965c9b4e56aea0560 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 18:16:23 +0900 Subject: [PATCH 133/174] fix typo `ManiaScorllSlider` -> `ManiaScrollSlider` --- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 51a45abd70..622450ac4a 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania LabelText = "Scrolling direction", Current = config.GetBindable(ManiaRulesetSetting.ScrollDirection) }, - new SettingsSlider + new SettingsSlider { LabelText = "Scroll speed", Current = config.GetBindable(ManiaRulesetSetting.ScrollTime), @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania }; } - private class ManiaScorllSlider : OsuSliderBar + private class ManiaScrollSlider : OsuSliderBar { public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; } From 5c2fb3e43453febce253e83c74f5e78e0659bdcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 18:22:53 +0900 Subject: [PATCH 134/174] Simplify calculation method --- .../ClicksPerSecondCalculator.cs | 55 ++++++++----------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 20ac923b82..b8cf20ee8f 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.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.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; @@ -11,56 +10,48 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond { public class ClicksPerSecondCalculator : Component { - private readonly List timestamps; + private readonly List timestamps = new List(); [Resolved] private IGameplayClock gameplayClock { get; set; } = null!; - [Resolved] - private DrawableRuleset drawableRuleset { get; set; } = null!; - - private double rate; - - // The latest timestamp GC seeked. Does not affect normal gameplay - // but prevents duplicate inputs on replays. - private double latestTime = double.NegativeInfinity; + [Resolved(canBeNull: true)] + private DrawableRuleset? drawableRuleset { get; set; } public int Value { get; private set; } + private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock; + public ClicksPerSecondCalculator() { RelativeSizeAxes = Axes.Both; - timestamps = new List(); } + public void AddInputTimestamp() => timestamps.Add(clock.CurrentTime); + protected override void Update() { base.Update(); - // When pausing in replays (using the space bar) GC.TrueGameplayRate returns 0 - // To prevent CPS value being 0, we store and use the last non-zero TrueGameplayRate - if (gameplayClock.TrueGameplayRate > 0) + double latestValidTime = clock.CurrentTime; + double earliestTimeValid = latestValidTime - 1000 * gameplayClock.TrueGameplayRate; + + int count = 0; + + for (int i = timestamps.Count - 1; i >= 0; i--) { - rate = gameplayClock.TrueGameplayRate; + // handle rewinding by removing future timestamps as we go + if (timestamps[i] > latestValidTime) + { + timestamps.RemoveAt(i); + continue; + } + + if (timestamps[i] >= earliestTimeValid) + count++; } - Value = timestamps.Count(timestamp => - { - double window = 1000 * rate; - double relativeTime = drawableRuleset.FrameStableClock.CurrentTime - timestamp; - return relativeTime > 0 && relativeTime <= window; - }); - } - - public void AddTimestamp() - { - // Discard inputs if current gameplay time is not the latest - // to prevent duplicate inputs - if (drawableRuleset.FrameStableClock.CurrentTime >= latestTime) - { - timestamps.Add(drawableRuleset.FrameStableClock.CurrentTime); - latestTime = drawableRuleset.FrameStableClock.CurrentTime; - } + Value = count; } } } From 6729bb3e1a24e38c92caa542c3e67ddc1c59ad47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 18:23:54 +0900 Subject: [PATCH 135/174] Change `FrameStableClock` to inherit `IGameplayClock` --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 2 +- osu.Game/Rulesets/UI/IFrameStableClock.cs | 4 ++-- osu.Game/Rulesets/UI/RulesetInputManager.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 18d0ff0bed..9446ba946b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.UI /// [Cached(typeof(IGameplayClock))] [Cached(typeof(IFrameStableClock))] - public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock, IGameplayClock + public sealed class FrameStabilityContainer : Container, IHasReplayHandler, IFrameStableClock { public ReplayInputHandler? ReplayInputHandler { get; set; } diff --git a/osu.Game/Rulesets/UI/IFrameStableClock.cs b/osu.Game/Rulesets/UI/IFrameStableClock.cs index 569ef5e06c..4e50d059e9 100644 --- a/osu.Game/Rulesets/UI/IFrameStableClock.cs +++ b/osu.Game/Rulesets/UI/IFrameStableClock.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Timing; +using osu.Game.Screens.Play; namespace osu.Game.Rulesets.UI { - public interface IFrameStableClock : IFrameBasedClock + public interface IFrameStableClock : IGameplayClock { IBindable IsCatchingUp { get; } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index dcd2c4fb5d..1a97153f2f 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.UI public bool OnPressed(KeyBindingPressEvent e) { - calculator.AddTimestamp(); + calculator.AddInputTimestamp(); return false; } From ee094e3a858e409f21b20602438595d8a770d372 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:05:13 +0900 Subject: [PATCH 136/174] Rewrite tests --- .../Gameplay/TestSceneClicksPerSecond.cs | 292 +++--------------- 1 file changed, 50 insertions(+), 242 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs index a140460251..ed5102e3b3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs @@ -3,22 +3,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Timing; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.UI; -using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD.ClicksPerSecond; using osuTK; @@ -27,290 +16,109 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneClicksPerSecond : OsuTestScene { - private DependencyProvidingContainer dependencyContainer = null!; private ClicksPerSecondCalculator calculator = null!; - private GameplayClockContainer gameplayClockContainer = null!; - private ManualClock manualClock = null!; - private DrawableRuleset? drawableRuleset; - private IFrameStableClock? frameStableClock; + + private TestGameplayClock manualGameplayClock = null!; [SetUpSteps] public void SetUpSteps() { AddStep("create components", () => { - var ruleset = CreateRuleset(); + manualGameplayClock = new TestGameplayClock(); - Debug.Assert(ruleset != null); - - Child = gameplayClockContainer = new GameplayClockContainer(manualClock = new ManualClock()); - gameplayClockContainer.AddRange(new Drawable[] + Child = new DependencyProvidingContainer { - drawableRuleset = new TestDrawableRuleset(frameStableClock = new TestFrameStableClock(manualClock)), - dependencyContainer = new DependencyProvidingContainer + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + calculator = new ClicksPerSecondCalculator(), + new DependencyProvidingContainer { - (typeof(DrawableRuleset), drawableRuleset), - (typeof(IGameplayClock), gameplayClockContainer) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, + Child = new ClicksPerSecondCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + } } - } - }); + }, + }; }); } [Test] public void TestBasicConsistency() { - createCalculator(); - startClock(); - - AddStep("Create gradually increasing KPS inputs", () => - { - addInputs(generateGraduallyIncreasingKps()); - }); - - for (int i = 0; i < 10; i++) - { - seek(i * 10000); - advanceForwards(2); - int kps = i + 1; - AddAssert($"{kps} KPS", () => calculator.Value == kps); - } + seek(1000); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); } [Test] public void TestRateAdjustConsistency() { - createCalculator(); - startClock(); - - AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); - - advanceForwards(2); - - for (double i = 1; i <= 2; i += 0.25) - { - changeRate(i); - double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); - } - - for (double i = 1; i >= 0.5; i -= 0.25) - { - changeRate(i); - double rate = i; - AddAssert($"KPS approx. = {i}", () => MathHelper.ApproximatelyEquivalent(calculator.Value, 10 * rate, 0.5)); - } + seek(1000); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); + AddStep("set rate 0.5x", () => manualGameplayClock.TrueGameplayRate = 0.5); + checkClicksPerSecondValue(5); } [Test] public void TestInputsDiscardedOnRewind() { - createCalculator(); - startClock(); - - AddStep("Create consistent KPS inputs", () => addInputs(generateConsistentKps(10))); seek(1000); - - AddAssert("KPS = 10", () => calculator.Value == 10); - - AddStep("Create delayed inputs", () => addInputs(generateConsistentKps(10, 50))); + AddStep("add inputs in past", () => addInputs(new double[] { 0, 100, 200, 300, 400, 500, 600, 700, 800, 900 })); + checkClicksPerSecondValue(10); + seek(500); + checkClicksPerSecondValue(6); seek(1000); - AddAssert("KPS didn't changed", () => calculator.Value == 10); + checkClicksPerSecondValue(6); } - private void seekAllClocks(double time) - { - gameplayClockContainer.Seek(time); - manualClock.CurrentTime = time; - } + private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i)); - protected override Ruleset CreateRuleset() => new OsuRuleset(); + private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time; - #region Quick steps methods - - private void createCalculator() - { - AddStep("create calculator", () => - { - dependencyContainer.Children = new Drawable[] - { - calculator = new ClicksPerSecondCalculator(), - new DependencyProvidingContainer - { - RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, - Child = new ClicksPerSecondCounter // For visual debugging, has no real purpose in the tests - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - } - } - }; - }); - } - - private void seek(double time) => AddStep($"Seek clocks to {time}ms", () => seekAllClocks(time)); - - private void changeRate(double rate) => AddStep($"Change rate to x{rate}", () => manualClock.Rate = rate); - - private void advanceForwards(double time) => - AddStep($"Advance clocks {time} seconds forward.", () => - { - gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime + time * manualClock.Rate); - - for (int i = 0; i < time; i++) - { - frameStableClock?.ProcessFrame(); - } - }); - - private void startClock() => AddStep("Start clocks", () => - { - gameplayClockContainer.Start(); - manualClock.Rate = 1; - }); - - #endregion - - #region Input generation + private void seek(double time) => AddStep($"Seek to {time}ms", () => seekClockImmediately(time)); private void addInputs(IEnumerable inputs) { - if (!inputs.Any()) return; - - double baseTime = gameplayClockContainer.CurrentTime; + double baseTime = manualGameplayClock.CurrentTime; foreach (double timestamp in inputs) { - seekAllClocks(timestamp); - calculator.AddTimestamp(); + seekClockImmediately(timestamp); + calculator.AddInputTimestamp(); } - seekAllClocks(baseTime); + seekClockImmediately(baseTime); } - private IEnumerable generateGraduallyIncreasingKps() + private class TestGameplayClock : IGameplayClock { - IEnumerable final = null!; + public double CurrentTime { get; set; } - for (int i = 1; i <= 10; i++) - { - var currentKps = generateConsistentKps(i, (i - 1) * 10000); + public double Rate => 1; - if (i == 1) - { - final = currentKps; - continue; - } + public bool IsRunning => true; - final = final.Concat(currentKps); - } - - return final; - } - - private IEnumerable generateConsistentKps(double kps, double start = 0, double duration = 10) - { - double end = start + 1000 * duration; - - for (; start < end; start += 1000 / kps) - { - yield return start; - } - } - - #endregion - - #region Test classes - - private class TestFrameStableClock : IFrameStableClock - { - public TestFrameStableClock(IClock source, double startTime = 0) - { - this.source = source; - - if (source is ManualClock manualClock) - { - manualClock.CurrentTime = startTime; - } - } - - public double CurrentTime => source.CurrentTime; - public double Rate => source.Rate; - public bool IsRunning => source.IsRunning; - - private readonly IClock source; + public double TrueGameplayRate { get; set; } = 1; public void ProcessFrame() { - if (source is ManualClock manualClock) - { - manualClock.CurrentTime += 1000 * Rate; - } - - TimeInfo = new FrameTimeInfo - { - Elapsed = 1000 * Rate, - Current = CurrentTime - }; } - public double ElapsedFrameTime => TimeInfo.Elapsed; - public double FramesPerSecond => 1 / ElapsedFrameTime * 1000; - public FrameTimeInfo TimeInfo { get; private set; } - - public IEnumerable NonGameplayAdjustments => Enumerable.Empty(); - public IBindable IsCatchingUp => new Bindable(); - public IBindable WaitingOnFrames => new Bindable(); + public double ElapsedFrameTime => throw new NotImplementedException(); + public double FramesPerSecond => throw new NotImplementedException(); + public FrameTimeInfo TimeInfo => throw new NotImplementedException(); + public double StartTime => throw new NotImplementedException(); + public IEnumerable NonGameplayAdjustments => throw new NotImplementedException(); + public IBindable IsPaused => throw new NotImplementedException(); } - - [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] - private class TestDrawableRuleset : DrawableRuleset - { - public override IEnumerable Objects => Enumerable.Empty(); - - public override event Action NewResult - { - add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); - remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context"); - } - - public override event Action RevertResult - { - add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); - remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context"); - } - - public override Playfield Playfield => null!; - public override Container Overlays => null!; - public override Container FrameStableComponents => null!; - public override IFrameStableClock FrameStableClock { get; } - - internal override bool FrameStablePlayback { get; set; } - public override IReadOnlyList Mods => Array.Empty(); - - public override double GameplayStartTime => 0; - public override GameplayCursorContainer Cursor => null!; - - public TestDrawableRuleset(IFrameStableClock frameStableClock) - : base(new OsuRuleset()) - { - FrameStableClock = frameStableClock; - } - - public override void SetReplayScore(Score replayScore) => throw new NotImplementedException(); - - public override void SetRecordTarget(Score score) => throw new NotImplementedException(); - - public override void RequestResume(Action continueResume) => throw new NotImplementedException(); - - public override void CancelResume() => throw new NotImplementedException(); - } - - #endregion } } From a98c6b2c1f43e910045cc75a879452a732268be5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:14:23 +0900 Subject: [PATCH 137/174] Add comment metioning why we need to use `DrawableRuleset` lookup --- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index b8cf20ee8f..82bf9cfa1e 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond public int Value { get; private set; } + // Even though `FrameStabilityContainer` caches as a `GameplayClock`, we need to check it directly via `drawableRuleset` + // as this calculator is not contained within the `FrameStabilityContainer` and won't see the dependency. private IGameplayClock clock => drawableRuleset?.FrameStableClock ?? gameplayClock; public ClicksPerSecondCalculator() From 15a4eb46c4d3ea9cf387116837733fc741bd53bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:20:26 +0900 Subject: [PATCH 138/174] Rename test scene to match class name --- ...ClicksPerSecond.cs => TestSceneClicksPerSecondCalculator.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneClicksPerSecond.cs => TestSceneClicksPerSecondCalculator.cs} (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs similarity index 98% rename from osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs index ed5102e3b3..b740c06e03 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecond.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneClicksPerSecond : OsuTestScene + public class TestSceneClicksPerSecondCalculator : OsuTestScene { private ClicksPerSecondCalculator calculator = null!; From 8de896a393d14af7c78b17b8f11eaf6d808383d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:21:15 +0900 Subject: [PATCH 139/174] 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 cd4dfec3d7..2c186a52dd 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 6d0827a107..fabef87c28 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 3316bf5a49..89166f924c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From c585f08a3bd3895bea3c5a94a3daf5043ed7baf1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 19:42:09 +0900 Subject: [PATCH 140/174] Fix still inverted condition --- .../Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs index 54b0bc2080..3fc456c411 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("create sprites", () => SetContents(_ => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero))); AddAssert("sprites present", () => sprites.All(s => s.IsPresent)); AddStep("scale sprite", () => sprites.ForEach(s => s.VectorScale = new Vector2(0, 1))); - AddAssert("sprites not present", () => !sprites.All(s => s.IsPresent)); + AddAssert("sprites not present", () => sprites.All(s => !s.IsPresent)); } [Test] From 4f22616860df51d79b4f7c7e110d0621047da1c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:41:04 +0900 Subject: [PATCH 141/174] Rename class to match osu! version --- osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs | 8 ++++---- .../UI/{TouchInputField.cs => CatchTouchInputMapper.cs} | 2 +- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game.Rulesets.Catch/UI/{TouchInputField.cs => CatchTouchInputMapper.cs} (99%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index 24afda4cfa..b510a69f14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -12,19 +12,19 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchTouchInput : OsuTestScene { - private TouchInputField touchInputField = null!; + private CatchTouchInputMapper catchTouchInputMapper = null!; [SetUpSteps] public void SetUpSteps() { - AddStep("create inputfield", () => + AddStep("create input overlay", () => { Child = new CatchInputManager(new CatchRuleset().RulesetInfo) { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - touchInputField = new TouchInputField + catchTouchInputMapper = new CatchTouchInputMapper { Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestInputField() { - AddStep("show inputfield", () => touchInputField.Show()); + AddStep("show overlay", () => catchTouchInputMapper.Show()); } } } diff --git a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs similarity index 99% rename from osu.Game.Rulesets.Catch/UI/TouchInputField.cs rename to osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 80453d6aa3..7607bf60cd 100644 --- a/osu.Game.Rulesets.Catch/UI/TouchInputField.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -16,7 +16,7 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Catch.UI { - public class TouchInputField : VisibilityContainer + public class CatchTouchInputMapper : VisibilityContainer { public enum TouchCatchAction { diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index b84d0c60d5..ef2936ac94 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - KeyBindingInputManager.Add(new TouchInputField()); + KeyBindingInputManager.Add(new CatchTouchInputMapper()); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); From b9afe6f4cf86416eba1497cdeaf56ce7ed82cae6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 19:44:29 +0900 Subject: [PATCH 142/174] Tidy up code quality --- .../UI/CatchTouchInputMapper.cs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 7607bf60cd..bdbd958f6a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -18,28 +18,17 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - public enum TouchCatchAction - { - MoveLeft = 0, - MoveRight = 1, - DashLeft = 2, - DashRight = 3, - None = 4 - } - private Dictionary trackedActions = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; private Container mainContent = null!; - // Fill values with null because UI is not declared in constructor private ArrowHitbox leftBox = null!; private ArrowHitbox rightBox = null!; private ArrowHitbox leftDashBox = null!; private ArrowHitbox rightDashBox = null!; - // Force input to be prossed even when hidden. public override bool PropagatePositionalInputSubTree => true; public override bool PropagateNonPositionalInputSubTree => true; @@ -151,11 +140,6 @@ namespace osu.Game.Rulesets.Catch.UI return true; } - protected override void OnDragEnd(DragEndEvent e) - { - base.OnDragEnd(e); - } - protected override void OnDrag(DragEvent e) { // I'm not sure if this is posible but let's be safe @@ -167,6 +151,7 @@ namespace osu.Game.Rulesets.Catch.UI base.OnDrag(e); } + protected override void OnTouchMove(TouchMoveEvent e) { // I'm not sure if this is posible but let's be safe @@ -240,6 +225,7 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.MoveLeft; if (rightBox.Contains(inputPosition)) return TouchCatchAction.MoveRight; + return TouchCatchAction.None; } @@ -316,5 +302,14 @@ namespace osu.Game.Rulesets.Catch.UI } } } + + public enum TouchCatchAction + { + MoveLeft = 0, + MoveRight = 1, + DashLeft = 2, + DashRight = 3, + None = 4 + } } } From a90ca94a18b3be9db81cbc8c21648dcb4e4fb412 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 19:51:28 +0900 Subject: [PATCH 143/174] Remove outdated tests --- .../TestSceneMultiplayerMatchSongSelect.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 0ecfc059e4..b87321c047 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -19,7 +19,6 @@ using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; -using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -68,37 +67,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); } - [Test] - public void TestBeatmapRevertedOnExitIfNoSelection() - { - BeatmapInfo selectedBeatmap = null; - - AddStep("select beatmap", - () => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.Where(beatmap => beatmap.Ruleset.OnlineID == new OsuRuleset().LegacyID).ElementAt(1))); - AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap)); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("beatmap reverted", () => Beatmap.IsDefault); - } - - [Test] - public void TestModsRevertedOnExitIfNoSelection() - { - AddStep("change mods", () => SelectedMods.Value = new[] { new OsuModDoubleTime() }); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("mods reverted", () => SelectedMods.Value.Count == 0); - } - - [Test] - public void TestRulesetRevertedOnExitIfNoSelection() - { - AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo); - - AddStep("exit song select", () => songSelect.Exit()); - AddAssert("ruleset reverted", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo)); - } - [Test] public void TestBeatmapConfirmed() { From 09fa24c563a8cf476bcf11d88228aadee486c15b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 19:12:54 +0800 Subject: [PATCH 144/174] new display format --- .../Configuration/ManiaRulesetConfigManager.cs | 2 +- osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs index 8e09a01469..64e1f75e2c 100644 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration scrollTime => new SettingDescription( rawValue: scrollTime, name: "Scroll Speed", - value: $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)} ({scrollTime}ms)" + value: $"{scrollTime}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)})" ) ) }; diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 622450ac4a..e239068d1d 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania private class ManiaScrollSlider : OsuSliderBar { - public override LocalisableString TooltipText => $"{(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)} ({Current.Value}ms)"; + public override LocalisableString TooltipText => $"{Current.Value}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)})"; } } } From 888d8b281747b7a34072cc270152aafbe21163d9 Mon Sep 17 00:00:00 2001 From: Josh <43808099+josh-codes@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:39:53 +0800 Subject: [PATCH 145/174] Removed redudent code & converted use of `OnDrag to `OnMouseMove` --- .../UI/CatchTouchInputMapper.cs | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index bdbd958f6a..7886df69b6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osuTK.Graphics; using osuTK; using System.Collections.Generic; +using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI { @@ -119,45 +120,29 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) - return false; - - handleDown(e.Button, e.ScreenSpaceMousePosition); - return true; + return handleDown(e.Button, e.ScreenSpaceMousePosition); } protected override void OnMouseUp(MouseUpEvent e) { - if (getTouchCatchActionFromInput(e.ScreenSpaceMousePosition) == TouchCatchAction.None) - return; - handleUp(e.Button); base.OnMouseUp(e); } - protected override bool OnDragStart(DragStartEvent e) + protected override bool OnMouseMove(MouseMoveEvent e) { - return true; - } + TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); - protected override void OnDrag(DragEvent e) - { - // I'm not sure if this is posible but let's be safe - if (!trackedActions.ContainsKey(e.Button)) - trackedActions.Add(e.Button, TouchCatchAction.None); + // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. + foreach (MouseButton i in e.PressedButtons) + trackedActions[i] = touchCatchAction; - trackedActions[e.Button] = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); calculateActiveKeys(); - - base.OnDrag(e); + return true; } protected override void OnTouchMove(TouchMoveEvent e) { - // I'm not sure if this is posible but let's be safe - if (!trackedActions.ContainsKey(e.Touch.Source)) - trackedActions.Add(e.Touch.Source, TouchCatchAction.None); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); @@ -194,18 +179,20 @@ namespace osu.Game.Rulesets.Catch.UI keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private void handleDown(object source, Vector2 position) + private bool handleDown(object source, Vector2 position) { - Show(); - TouchCatchAction catchAction = getTouchCatchActionFromInput(position); - // Not too sure how this can happen, but let's avoid throwing. - if (trackedActions.ContainsKey(source)) - return; + if (catchAction == TouchCatchAction.None) + return false; + + Show(); + + trackedActions[source] = catchAction; - trackedActions.Add(source, catchAction); calculateActiveKeys(); + + return true; } private void handleUp(object source) From 45239fc737423f05f63c93b05e72ef20a33ef267 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Sep 2022 23:03:15 +0900 Subject: [PATCH 146/174] Update `TrueGameplayRate` accessing --- .../Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs index 82bf9cfa1e..04774b974f 100644 --- a/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs +++ b/osu.Game/Screens/Play/HUD/ClicksPerSecond/ClicksPerSecondCalculator.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond base.Update(); double latestValidTime = clock.CurrentTime; - double earliestTimeValid = latestValidTime - 1000 * gameplayClock.TrueGameplayRate; + double earliestTimeValid = latestValidTime - 1000 * gameplayClock.GetTrueGameplayRate(); int count = 0; From 27aa3552dc03846cf7d23eb7b731b574be20e147 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 00:00:08 +0900 Subject: [PATCH 147/174] Update in line with `TrueGameplayRate` changes --- .../Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs index b740c06e03..2dad5e2c32 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneClicksPerSecondCalculator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; @@ -107,7 +108,9 @@ namespace osu.Game.Tests.Visual.Gameplay public bool IsRunning => true; - public double TrueGameplayRate { get; set; } = 1; + public double TrueGameplayRate { set => adjustableAudioComponent.Tempo.Value = value; } + + private readonly AudioAdjustments adjustableAudioComponent = new AudioAdjustments(); public void ProcessFrame() { @@ -117,6 +120,9 @@ namespace osu.Game.Tests.Visual.Gameplay public double FramesPerSecond => throw new NotImplementedException(); public FrameTimeInfo TimeInfo => throw new NotImplementedException(); public double StartTime => throw new NotImplementedException(); + + public IAdjustableAudioComponent AdjustmentsFromMods => adjustableAudioComponent; + public IEnumerable NonGameplayAdjustments => throw new NotImplementedException(); public IBindable IsPaused => throw new NotImplementedException(); } From d1e27e8a69d061492e7cf5c8223840de19d6de96 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 23:14:34 +0800 Subject: [PATCH 148/174] add arrow short cut for skin editor basically from `ComposeBlueprintContainer` because they have the same logic --- .../Skinning/Editor/SkinBlueprintContainer.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 46f5c1e67f..5a1ef34151 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -11,9 +11,12 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Screens; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Input; namespace osu.Game.Skinning.Editor { @@ -90,6 +93,47 @@ namespace osu.Game.Skinning.Editor base.AddBlueprintFor(item); } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Left: + moveSelection(new Vector2(-1, 0)); + return true; + + case Key.Right: + moveSelection(new Vector2(1, 0)); + return true; + + case Key.Up: + moveSelection(new Vector2(0, -1)); + return true; + + case Key.Down: + moveSelection(new Vector2(0, 1)); + return true; + } + + return false; + } + + /// + /// Move the current selection spatially by the specified delta, in screen coordinates (ie. the same coordinates as the blueprints). + /// + /// + private void moveSelection(Vector2 delta) + { + var firstBlueprint = SelectionHandler.SelectedBlueprints.FirstOrDefault(); + + if (firstBlueprint == null) + return; + + // convert to game space coordinates + delta = firstBlueprint.ToScreenSpace(delta) - firstBlueprint.ToScreenSpace(Vector2.Zero); + + SelectionHandler.HandleMovement(new MoveSelectionEvent(firstBlueprint, delta)); + } + protected override SelectionHandler CreateSelectionHandler() => new SkinSelectionHandler(); protected override SelectionBlueprint CreateBlueprintFor(ISkinnableDrawable component) From 2e775e688643176142460c5cc71aee748d7d1418 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Thu, 8 Sep 2022 23:47:55 +0800 Subject: [PATCH 149/174] Add test for object move --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index bd274dfef5..19d20dd552 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -22,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditor : PlayerTestScene { - private SkinEditor skinEditor; + private SkinEditor skinEditor = null!; protected override bool Autoplay => true; @@ -54,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestEditComponent() { - BarHitErrorMeter hitErrorMeter = null; + BarHitErrorMeter hitErrorMeter = null!; AddStep("select bar hit error blueprint", () => { @@ -65,6 +63,10 @@ namespace osu.Game.Tests.Visual.Gameplay skinEditor.SelectedComponents.Add(blueprint.Item); }); + AddStep("move by keyboard", () => InputManager.Key(Key.Right)); + + AddAssert("hitErrorMeter moved", () => hitErrorMeter.X != 0); + AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault); AddStep("hover first slider", () => From 28477f3b9763fe876030b24c03ea884c3bf35930 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 08:55:35 +0900 Subject: [PATCH 150/174] Fix inspection --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 19d20dd552..578718b7c9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinEditor : PlayerTestScene { - private SkinEditor skinEditor = null!; + private SkinEditor? skinEditor; protected override bool Autoplay => true; @@ -40,13 +40,13 @@ namespace osu.Game.Tests.Visual.Gameplay Player.ScaleTo(0.4f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); - AddUntilStep("wait for loaded", () => skinEditor.IsLoaded); + AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded); } [Test] public void TestToggleEditor() { - AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility()); + AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility()); } [Test] @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay var blueprint = skinEditor.ChildrenOfType().First(b => b.Item is BarHitErrorMeter); hitErrorMeter = (BarHitErrorMeter)blueprint.Item; - skinEditor.SelectedComponents.Clear(); + skinEditor!.SelectedComponents.Clear(); skinEditor.SelectedComponents.Add(blueprint.Item); }); From 731d3f3b6358710943b1ed5230bedc3b7c9cfbcf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Sep 2022 22:06:44 +0900 Subject: [PATCH 151/174] Add MaximumStatistics upgrade for databased scores --- osu.Game/BackgroundBeatmapProcessor.cs | 55 ++++++++++++++++++- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 73 +++++++++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 11 +++- 4 files changed, 136 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 14fdb2e1ef..405386660c 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -14,6 +15,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game @@ -23,6 +25,9 @@ namespace osu.Game [Resolved] private RulesetStore rulesetStore { get; set; } = null!; + [Resolved] + private ScoreManager scoreManager { get; set; } = null!; + [Resolved] private RealmAccess realmAccess { get; set; } = null!; @@ -41,11 +46,12 @@ namespace osu.Game { base.LoadComplete(); - Task.Run(() => + Task.Run(async () => { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + await processScoresWithMissingStatistics(); }).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -140,5 +146,52 @@ namespace osu.Game }); } } + + private async Task processScoresWithMissingStatistics() + { + HashSet scoreIds = new HashSet(); + + Logger.Log("Querying for scores to reprocess..."); + + realmAccess.Run(r => + { + foreach (var score in r.All()) + { + if (score.Statistics.Sum(kvp => kvp.Value) > 0 && score.MaximumStatistics.Sum(kvp => kvp.Value) == 0) + scoreIds.Add(score.ID); + } + }); + + Logger.Log($"Found {scoreIds.Count} scores which require reprocessing."); + + foreach (var id in scoreIds) + { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); + } + + try + { + var score = scoreManager.Query(s => s.ID == id); + + await scoreManager.PopulateMaximumStatistics(score); + + // Can't use async overload because we're not on the update thread. + // ReSharper disable once MethodHasAsyncOverload + realmAccess.Write(r => + { + r.Find(id).MaximumStatisticsJson = JsonConvert.SerializeObject(score.MaximumStatistics); + }); + + Logger.Log($"Populated maximum statistics for score {id}"); + } + catch (Exception e) + { + Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); + } + } + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95a281f09..c95dde8759 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -279,7 +279,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig, difficultyCache)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 45f827354e..488f5815f8 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Newtonsoft.Json; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -16,6 +18,8 @@ using osu.Game.Scoring.Legacy; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using Realms; namespace osu.Game.Scoring @@ -28,15 +32,17 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; + private readonly BeatmapDifficultyCache? difficultyCache; private readonly IAPIProvider api; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, BeatmapDifficultyCache? difficultyCache = null) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.api = api; + this.difficultyCache = difficultyCache; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -71,6 +77,8 @@ namespace osu.Game.Scoring if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + PopulateMaximumStatistics(model).WaitSafely(); + if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); @@ -78,6 +86,69 @@ namespace osu.Game.Scoring model.MaximumStatisticsJson = JsonConvert.SerializeObject(model.MaximumStatistics); } + /// + /// Populates the for a given . + /// + /// The score to populate the statistics of. + public async Task PopulateMaximumStatistics(ScoreInfo score) + { + if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) + return; + + var beatmap = score.BeatmapInfo.Detach(); + var ruleset = score.Ruleset.Detach(); + + // Populate the maximum statistics. + HitResult maxBasicResult = ruleset.CreateInstance().GetHitResults() + .Select(h => h.result) + .Where(h => h.IsBasic()) + .OrderByDescending(Judgement.ToNumericResult).First(); + + foreach ((HitResult result, int count) in score.Statistics) + { + switch (result) + { + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + score.MaximumStatistics[HitResult.LargeTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.LargeTickHit) + count; + break; + + case HitResult.SmallTickHit: + case HitResult.SmallTickMiss: + score.MaximumStatistics[HitResult.SmallTickHit] = score.MaximumStatistics.GetValueOrDefault(HitResult.SmallTickHit) + count; + break; + + case HitResult.IgnoreHit: + case HitResult.IgnoreMiss: + case HitResult.SmallBonus: + case HitResult.LargeBonus: + break; + + default: + score.MaximumStatistics[maxBasicResult] = score.MaximumStatistics.GetValueOrDefault(maxBasicResult) + count; + break; + } + } + + if (!score.IsLegacyScore) + return; + +#pragma warning disable CS0618 + if (difficultyCache == null) + throw new InvalidOperationException($"Cannot populate legacy score statistics without a {nameof(BeatmapDifficultyCache)}."); + + // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. + // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. + StarDifficulty? difficulty = await difficultyCache.GetDifficultyAsync(beatmap, ruleset, score.Mods); + if (difficulty == null) + throw new InvalidOperationException("Failed to populate maximum statistics due to missing difficulty attributes."); + + int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); + if (difficulty.Value.MaxCombo > maxComboFromStatistics) + score.MaximumStatistics[HitResult.LegacyComboIncrease] = difficulty.Value.MaxCombo - maxComboFromStatistics; +#pragma warning restore CS0618 + } + protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport) { base.PostImport(model, realm, batchImport); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 782590114f..87bb834a75 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -28,12 +28,13 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, OsuConfigManager configManager = null) + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, + OsuConfigManager configManager = null, BeatmapDifficultyCache difficultyCache = null) : base(storage, realm) { this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api, difficultyCache) { PostNotification = obj => PostNotification?.Invoke(obj) }; @@ -178,6 +179,12 @@ namespace osu.Game.Scoring public Live Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => scoreImporter.ImportModel(item, archive, batchImport, cancellationToken); + /// + /// Populates the for a given . + /// + /// The score to populate the statistics of. + public Task PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); + #region Implementation of IPresentImports public Action>> PresentImport From 3b932b46ca8870c25e04a4fdcd1db3e792c18212 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 09:59:39 +0900 Subject: [PATCH 152/174] Fix entire TPL thread potentially being consumed during gameplay --- osu.Game/BackgroundBeatmapProcessor.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 405386660c..7a38abd071 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; @@ -50,7 +49,7 @@ namespace osu.Game { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); - processBeatmapSetsWithMissingMetrics(); + await processBeatmapSetsWithMissingMetrics(); await processScoresWithMissingStatistics(); }).ContinueWith(t => { @@ -100,7 +99,7 @@ namespace osu.Game } } - private void processBeatmapSetsWithMissingMetrics() + private async Task processBeatmapSetsWithMissingMetrics() { HashSet beatmapSetIds = new HashSet(); @@ -124,7 +123,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - Thread.Sleep(TimeToSleepDuringGameplay); + await Task.Delay(TimeToSleepDuringGameplay); } realmAccess.Run(r => @@ -169,7 +168,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - Thread.Sleep(TimeToSleepDuringGameplay); + await Task.Delay(TimeToSleepDuringGameplay); } try From ba2ef424d4cac407692ad78c7c1c75b44110d419 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 11:30:36 +0900 Subject: [PATCH 153/174] Turn score ids into `ulong`s --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index cfa9f77634..a0f76c4e14 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online }); } - private int onlineID = 1; + private ulong onlineID = 1; private APIScoresCollection createScores() { diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d7b97cdddf..2c9f250028 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -81,12 +81,12 @@ namespace osu.Game.Online.API.Requests.Responses public int? LegacyTotalScore { get; set; } [JsonProperty("legacy_score_id")] - public uint? LegacyScoreId { get; set; } + public ulong? LegacyScoreId { get; set; } #region osu-web API additions (not stored to database). [JsonProperty("id")] - public long? ID { get; set; } + public ulong? ID { get; set; } [JsonProperty("user")] public APIUser? User { get; set; } @@ -190,6 +190,6 @@ namespace osu.Game.Online.API.Requests.Responses MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), }; - public long OnlineID => ID ?? -1; + public long OnlineID => (long?)ID ?? -1; } } From 08d0c08750ae7341334a99aefa8920c9922f36f3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 13:57:01 +0900 Subject: [PATCH 154/174] Fix async exception by using difficulty calculator directly --- osu.Game/BackgroundBeatmapProcessor.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 34 ++++++++++++-------------- osu.Game/Scoring/ScoreManager.cs | 6 ++--- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 7a38abd071..071862146f 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -175,7 +175,7 @@ namespace osu.Game { var score = scoreManager.Query(s => s.ID == id); - await scoreManager.PopulateMaximumStatistics(score); + scoreManager.PopulateMaximumStatistics(score); // Can't use async overload because we're not on the update thread. // ReSharper disable once MethodHasAsyncOverload diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95dde8759..c95a281f09 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -279,7 +279,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig, difficultyCache)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, API, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 488f5815f8..5c8e21014c 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -3,11 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; -using System.Threading.Tasks; using Newtonsoft.Json; -using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -32,17 +31,15 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - private readonly BeatmapDifficultyCache? difficultyCache; private readonly IAPIProvider api; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, BeatmapDifficultyCache? difficultyCache = null) + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; this.api = api; - this.difficultyCache = difficultyCache; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -77,7 +74,7 @@ namespace osu.Game.Scoring if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); - PopulateMaximumStatistics(model).WaitSafely(); + PopulateMaximumStatistics(model); if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); @@ -90,19 +87,22 @@ namespace osu.Game.Scoring /// Populates the for a given . /// /// The score to populate the statistics of. - public async Task PopulateMaximumStatistics(ScoreInfo score) + public void PopulateMaximumStatistics(ScoreInfo score) { if (score.MaximumStatistics.Select(kvp => kvp.Value).Sum() > 0) return; var beatmap = score.BeatmapInfo.Detach(); var ruleset = score.Ruleset.Detach(); + var rulesetInstance = ruleset.CreateInstance(); + + Debug.Assert(rulesetInstance != null); // Populate the maximum statistics. - HitResult maxBasicResult = ruleset.CreateInstance().GetHitResults() - .Select(h => h.result) - .Where(h => h.IsBasic()) - .OrderByDescending(Judgement.ToNumericResult).First(); + HitResult maxBasicResult = rulesetInstance.GetHitResults() + .Select(h => h.result) + .Where(h => h.IsBasic()) + .OrderByDescending(Judgement.ToNumericResult).First(); foreach ((HitResult result, int count) in score.Statistics) { @@ -134,18 +134,14 @@ namespace osu.Game.Scoring return; #pragma warning disable CS0618 - if (difficultyCache == null) - throw new InvalidOperationException($"Cannot populate legacy score statistics without a {nameof(BeatmapDifficultyCache)}."); - // In osu! and osu!mania, some judgements affect combo but aren't stored to scores. // A special hit result is used to pad out the combo value to match, based on the max combo from the difficulty attributes. - StarDifficulty? difficulty = await difficultyCache.GetDifficultyAsync(beatmap, ruleset, score.Mods); - if (difficulty == null) - throw new InvalidOperationException("Failed to populate maximum statistics due to missing difficulty attributes."); + var calculator = rulesetInstance.CreateDifficultyCalculator(beatmaps().GetWorkingBeatmap(beatmap)); + var attributes = calculator.Calculate(score.Mods); int maxComboFromStatistics = score.MaximumStatistics.Where(kvp => kvp.Key.AffectsCombo()).Select(kvp => kvp.Value).DefaultIfEmpty(0).Sum(); - if (difficulty.Value.MaxCombo > maxComboFromStatistics) - score.MaximumStatistics[HitResult.LegacyComboIncrease] = difficulty.Value.MaxCombo - maxComboFromStatistics; + if (attributes.MaxCombo > maxComboFromStatistics) + score.MaximumStatistics[HitResult.LegacyComboIncrease] = attributes.MaxCombo - maxComboFromStatistics; #pragma warning restore CS0618 } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 87bb834a75..6bb31eb4db 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -29,12 +29,12 @@ namespace osu.Game.Scoring private readonly ScoreImporter scoreImporter; public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api, - OsuConfigManager configManager = null, BeatmapDifficultyCache difficultyCache = null) + OsuConfigManager configManager = null) : base(storage, realm) { this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api, difficultyCache) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) { PostNotification = obj => PostNotification?.Invoke(obj) }; @@ -183,7 +183,7 @@ namespace osu.Game.Scoring /// Populates the for a given . /// /// The score to populate the statistics of. - public Task PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); + public void PopulateMaximumStatistics(ScoreInfo score) => scoreImporter.PopulateMaximumStatistics(score); #region Implementation of IPresentImports From 20ffbc4676a60e6c192131ce33678e2fdada6136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:01:52 +0900 Subject: [PATCH 155/174] Fix beat sync stopping after returning to menu from a failed play Closes #20193. Explanation is inline comment. --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 11 +++++++++++ osu.Game/OsuGameBase.cs | 5 ----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index a4787a34e8..3c528ba838 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -121,6 +121,17 @@ namespace osu.Game.Beatmaps protected override void Update() { base.Update(); + + if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < finalClockSource.CurrentTime) + { + // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. + // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 + // This is not always the case here when doing large seeks. + // (Of note, this is not an issue if the source is adjustable, as the source is seeked to be in time by DecoupleableInterpolatingFramedClock). + // Rather than trying to get around this by fixing the framework clock stack, let's work around it for now. + Seek(Source.CurrentTime); + } + finalClockSource.ProcessFrame(); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c95a281f09..97142d5472 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -390,11 +390,6 @@ namespace osu.Game var framedClock = new FramedClock(beatmap.Track); beatmapClock.ChangeSource(framedClock); - - // Normally the internal decoupled clock will seek the *track* to the decoupled time, but we blocked this. - // It won't behave nicely unless we also set it to the track's time. - // Probably another thing which should be fixed in the decoupled mess (or just replaced). - beatmapClock.Seek(beatmap.Track.CurrentTime); } protected virtual void InitialiseFonts() From 64cf6b9014aa0db328722e33e17f4b6ff2e64c98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:35:35 +0900 Subject: [PATCH 156/174] Compare with decoupled clock directly to avoid including offsets --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 3c528ba838..594663e7f2 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -122,7 +122,7 @@ namespace osu.Game.Beatmaps { base.Update(); - if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < finalClockSource.CurrentTime) + if (Source != null && Source is not IAdjustableClock && Source.CurrentTime < decoupledClock.CurrentTime) { // InterpolatingFramedClock won't interpolate backwards unless its source has an ElapsedFrameTime. // See https://github.com/ppy/osu-framework/blob/ba1385330cc501f34937e08257e586c84e35d772/osu.Framework/Timing/InterpolatingFramedClock.cs#L91-L93 From d6748d6921117a65846da635a45e0c6d04a5eef8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:35:47 +0900 Subject: [PATCH 157/174] Avoid double call to `ProcessFrame` --- osu.Game/Beatmaps/FramedBeatmapClock.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/FramedBeatmapClock.cs b/osu.Game/Beatmaps/FramedBeatmapClock.cs index 594663e7f2..c7050cc50f 100644 --- a/osu.Game/Beatmaps/FramedBeatmapClock.cs +++ b/osu.Game/Beatmaps/FramedBeatmapClock.cs @@ -131,8 +131,8 @@ namespace osu.Game.Beatmaps // Rather than trying to get around this by fixing the framework clock stack, let's work around it for now. Seek(Source.CurrentTime); } - - finalClockSource.ProcessFrame(); + else + finalClockSource.ProcessFrame(); } public double TotalAppliedOffset From 856dbbba692a9d1ad31561a78a174c959a05a586 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 14:52:46 +0900 Subject: [PATCH 158/174] Fix attempting to use "home" key binding while exiting game causing errors --- osu.Game/OsuGame.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 108153fd9d..9e2384322a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -839,7 +839,9 @@ namespace osu.Game OnHome = delegate { CloseAllOverlays(false); - menuScreen?.MakeCurrent(); + + if (menuScreen?.GetChildScreen() != null) + menuScreen.MakeCurrent(); }, }, topMostOverlayContent.Add); From 2709a4d3984fffd805655ddc722bc72918c63875 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:04:25 +0900 Subject: [PATCH 159/174] Ensure overlay is always shown when movement is detected on mouse or keyboard --- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 7886df69b6..1abca2ae98 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -131,6 +131,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseMove(MouseMoveEvent e) { + Show(); + TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. @@ -143,6 +145,8 @@ namespace osu.Game.Rulesets.Catch.UI protected override void OnTouchMove(TouchMoveEvent e) { + Show(); + trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); @@ -186,8 +190,6 @@ namespace osu.Game.Rulesets.Catch.UI if (catchAction == TouchCatchAction.None) return false; - Show(); - trackedActions[source] = catchAction; calculateActiveKeys(); From 715e9018da3dd4d57084cbdc76c84100f712a3fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:11:26 +0900 Subject: [PATCH 160/174] Tidy up code and naming --- .../UI/CatchTouchInputMapper.cs | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 1abca2ae98..c17946bdb1 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -19,16 +19,16 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - private Dictionary trackedActions = new Dictionary(); + private Dictionary trackedActionSources = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; private Container mainContent = null!; - private ArrowHitbox leftBox = null!; - private ArrowHitbox rightBox = null!; - private ArrowHitbox leftDashBox = null!; - private ArrowHitbox rightDashBox = null!; + private InputArea leftBox = null!; + private InputArea rightBox = null!; + private InputArea leftDashBox = null!; + private InputArea rightDashBox = null!; public override bool PropagatePositionalInputSubTree => true; public override bool PropagateNonPositionalInputSubTree => true; @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new ArrowHitbox(TouchCatchAction.MoveLeft, ref trackedActions, colours.Gray3) + leftBox = new InputArea(TouchCatchAction.MoveLeft, ref trackedActionSources, colours.Gray3) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativePositionAxes = Axes.Both, Width = 0.5f, }, - leftDashBox = new ArrowHitbox(TouchCatchAction.DashLeft, ref trackedActions, colours.Gray2) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, ref trackedActionSources, colours.Gray2) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.CentreRight, Children = new Drawable[] { - rightBox = new ArrowHitbox(TouchCatchAction.MoveRight, ref trackedActions, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, ref trackedActionSources, colours.Gray3) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.UI RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new ArrowHitbox(TouchCatchAction.DashRight, ref trackedActions, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, ref trackedActionSources, colours.Gray2) { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Catch.UI // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. foreach (MouseButton i in e.PressedButtons) - trackedActions[i] = touchCatchAction; + trackedActionSources[i] = touchCatchAction; calculateActiveKeys(); return true; @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Catch.UI { Show(); - trackedActions[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); + trackedActionSources[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); calculateActiveKeys(); base.OnTouchMove(e); @@ -165,24 +165,6 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private void calculateActiveKeys() - { - if (trackedActions.ContainsValue(TouchCatchAction.DashLeft) || trackedActions.ContainsValue(TouchCatchAction.MoveLeft)) - keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); - else - keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); - - if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.MoveRight)) - keyBindingContainer.TriggerPressed(CatchAction.MoveRight); - else - keyBindingContainer.TriggerReleased(CatchAction.MoveRight); - - if (trackedActions.ContainsValue(TouchCatchAction.DashRight) || trackedActions.ContainsValue(TouchCatchAction.DashLeft)) - keyBindingContainer.TriggerPressed(CatchAction.Dash); - else - keyBindingContainer.TriggerReleased(CatchAction.Dash); - } - private bool handleDown(object source, Vector2 position) { TouchCatchAction catchAction = getTouchCatchActionFromInput(position); @@ -190,7 +172,7 @@ namespace osu.Game.Rulesets.Catch.UI if (catchAction == TouchCatchAction.None) return false; - trackedActions[source] = catchAction; + trackedActionSources[source] = catchAction; calculateActiveKeys(); @@ -199,11 +181,29 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { - trackedActions.Remove(source); + trackedActionSources.Remove(source); calculateActiveKeys(); } + private void calculateActiveKeys() + { + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) + keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveLeft); + + if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.MoveRight)) + keyBindingContainer.TriggerPressed(CatchAction.MoveRight); + else + keyBindingContainer.TriggerReleased(CatchAction.MoveRight); + + if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.DashLeft)) + keyBindingContainer.TriggerPressed(CatchAction.Dash); + else + keyBindingContainer.TriggerReleased(CatchAction.Dash); + } + private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) { if (leftDashBox.Contains(inputPosition)) @@ -228,7 +228,7 @@ namespace osu.Game.Rulesets.Catch.UI mainContent.FadeOut(300); } - private class ArrowHitbox : CompositeDrawable, IKeyBindingHandler + private class InputArea : CompositeDrawable, IKeyBindingHandler { private readonly TouchCatchAction handledAction; @@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Catch.UI private bool isHiglighted; - public ArrowHitbox(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) { this.handledAction = handledAction; this.trackedActions = trackedActions; From a42c1af09ea2571d20afeb5435e4388af4225c68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:19:30 +0900 Subject: [PATCH 161/174] Tidy up highlighting code and ensure read-only access to dictionary by highlight areas --- .../UI/CatchTouchInputMapper.cs | 90 ++++++++----------- 1 file changed, 35 insertions(+), 55 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index c17946bdb1..58cda7fef4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -1,8 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; -using System.Diagnostics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -10,16 +11,18 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osuTK.Graphics; using osuTK; -using System.Collections.Generic; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI { public class CatchTouchInputMapper : VisibilityContainer { - private Dictionary trackedActionSources = new Dictionary(); + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + + private readonly Dictionary trackedActionSources = new Dictionary(); private KeyBindingContainer keyBindingContainer = null!; @@ -30,17 +33,11 @@ namespace osu.Game.Rulesets.Catch.UI private InputArea leftDashBox = null!; private InputArea rightDashBox = null!; - public override bool PropagatePositionalInputSubTree => true; - public override bool PropagateNonPositionalInputSubTree => true; - [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { - Debug.Assert(catchInputManager.KeyBindingContainer != null); - keyBindingContainer = catchInputManager.KeyBindingContainer; - // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; Children = new Drawable[] @@ -48,7 +45,6 @@ namespace osu.Game.Rulesets.Catch.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Alpha = 0, Children = new Drawable[] { @@ -56,25 +52,18 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.15f, - Height = 1, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, Children = new Drawable[] { - leftBox = new InputArea(TouchCatchAction.MoveLeft, ref trackedActionSources, colours.Gray3) + leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, - leftDashBox = new InputArea(TouchCatchAction.DashLeft, ref trackedActionSources, colours.Gray2) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources, colours.Gray2) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, } } @@ -83,26 +72,21 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.15f, - Height = 1, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Children = new Drawable[] { - rightBox = new InputArea(TouchCatchAction.MoveRight, ref trackedActionSources, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources, colours.Gray3) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, }, - rightDashBox = new InputArea(TouchCatchAction.DashRight, ref trackedActionSources, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources, colours.Gray2) { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, Width = 0.5f, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, }, } }, @@ -218,15 +202,9 @@ namespace osu.Game.Rulesets.Catch.UI return TouchCatchAction.None; } - protected override void PopIn() - { - mainContent.FadeIn(500, Easing.OutQuint); - } + protected override void PopIn() => mainContent.FadeIn(500, Easing.OutQuint); - protected override void PopOut() - { - mainContent.FadeOut(300); - } + protected override void PopOut() => mainContent.FadeOut(300); private class InputArea : CompositeDrawable, IKeyBindingHandler { @@ -234,11 +212,11 @@ namespace osu.Game.Rulesets.Catch.UI private readonly Box overlay; - private readonly Dictionary trackedActions; + private readonly IEnumerable> trackedActions; - private bool isHiglighted; + private bool isHighlighted; - public InputArea(TouchCatchAction handledAction, ref Dictionary trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions, Color4 colour) { this.handledAction = handledAction; this.trackedActions = trackedActions; @@ -273,22 +251,24 @@ namespace osu.Game.Rulesets.Catch.UI public bool OnPressed(KeyBindingPressEvent _) { - if (trackedActions.ContainsValue(handledAction)) - { - isHiglighted = true; - overlay.FadeTo(0.5f, 80, Easing.OutQuint); - } - + updateHighlight(); return false; } public void OnReleased(KeyBindingReleaseEvent _) { - if (isHiglighted && !trackedActions.ContainsValue(handledAction)) - { - isHiglighted = false; - overlay.FadeOut(1000, Easing.Out); - } + updateHighlight(); + } + + private void updateHighlight() + { + bool isHandling = trackedActions.Any(a => a.Value == handledAction); + + if (isHandling == isHighlighted) + return; + + isHighlighted = isHandling; + overlay.FadeTo(isHighlighted ? 0.5f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); } } From e6ba95ee16e7595f748f2ee39a94289918a47327 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:22:12 +0900 Subject: [PATCH 162/174] Don't bother calculating active keys if input source was not handled --- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 58cda7fef4..847814c0ea 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -149,15 +150,14 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private bool handleDown(object source, Vector2 position) + private bool handleDown(object inputSource, Vector2 position) { TouchCatchAction catchAction = getTouchCatchActionFromInput(position); if (catchAction == TouchCatchAction.None) return false; - trackedActionSources[source] = catchAction; - + trackedActionSources[inputSource] = catchAction; calculateActiveKeys(); return true; @@ -165,9 +165,8 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { - trackedActionSources.Remove(source); - - calculateActiveKeys(); + if (trackedActionSources.Remove(source)) + calculateActiveKeys(); } private void calculateActiveKeys() From ba951b76f73b16c6ea7b1305a78697bea90a6fc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:28:40 +0900 Subject: [PATCH 163/174] Unify and simplify input handling code --- .../UI/CatchTouchInputMapper.cs | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 847814c0ea..3d634c4e00 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -108,40 +107,30 @@ namespace osu.Game.Rulesets.Catch.UI return handleDown(e.Button, e.ScreenSpaceMousePosition); } - protected override void OnMouseUp(MouseUpEvent e) + protected override bool OnTouchDown(TouchDownEvent e) { - handleUp(e.Button); - base.OnMouseUp(e); + handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); + return true; } protected override bool OnMouseMove(MouseMoveEvent e) { - Show(); - - TouchCatchAction touchCatchAction = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); - - // Loop through the buttons to avoid keeping a button pressed if both mouse buttons are pressed. - foreach (MouseButton i in e.PressedButtons) - trackedActionSources[i] = touchCatchAction; - - calculateActiveKeys(); + // multiple mouse buttons may be pressed and handling the same action. + foreach (MouseButton button in e.PressedButtons) + handleMove(button, e.ScreenSpaceMousePosition); return true; } protected override void OnTouchMove(TouchMoveEvent e) { - Show(); - - trackedActionSources[e.Touch.Source] = getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position); - calculateActiveKeys(); - + handleMove(e.Touch.Source, e.ScreenSpaceTouch.Position); base.OnTouchMove(e); } - protected override bool OnTouchDown(TouchDownEvent e) + protected override void OnMouseUp(MouseUpEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); - return true; + handleUp(e.Button); + base.OnMouseUp(e); } protected override void OnTouchUp(TouchUpEvent e) @@ -150,15 +139,23 @@ namespace osu.Game.Rulesets.Catch.UI base.OnTouchUp(e); } - private bool handleDown(object inputSource, Vector2 position) + private void handleMove(object inputSource, Vector2 screenSpaceInputPosition) { - TouchCatchAction catchAction = getTouchCatchActionFromInput(position); + Show(); - if (catchAction == TouchCatchAction.None) + trackedActionSources[inputSource] = getTouchCatchActionFromInput(screenSpaceInputPosition); + updatePressedActions(); + } + + private bool handleDown(object inputSource, Vector2 screenSpaceInputPosition) + { + TouchCatchAction action = getTouchCatchActionFromInput(screenSpaceInputPosition); + + if (action == TouchCatchAction.None) return false; - trackedActionSources[inputSource] = catchAction; - calculateActiveKeys(); + trackedActionSources[inputSource] = action; + updatePressedActions(); return true; } @@ -166,10 +163,10 @@ namespace osu.Game.Rulesets.Catch.UI private void handleUp(object source) { if (trackedActionSources.Remove(source)) - calculateActiveKeys(); + updatePressedActions(); } - private void calculateActiveKeys() + private void updatePressedActions() { if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); @@ -187,15 +184,15 @@ namespace osu.Game.Rulesets.Catch.UI keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private TouchCatchAction getTouchCatchActionFromInput(Vector2 inputPosition) + private TouchCatchAction getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) { - if (leftDashBox.Contains(inputPosition)) + if (leftDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashLeft; - if (rightDashBox.Contains(inputPosition)) + if (rightDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashRight; - if (leftBox.Contains(inputPosition)) + if (leftBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveLeft; - if (rightBox.Contains(inputPosition)) + if (rightBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveRight; return TouchCatchAction.None; From 64eaf461ac9ce39a9f13ae082b4f56eaacf83a49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 15:58:43 +0900 Subject: [PATCH 164/174] Simplify input handling even further --- .../UI/CatchTouchInputMapper.cs | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 3d634c4e00..13333ecc61 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -36,6 +35,8 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load(CatchInputManager catchInputManager, OsuColour colours) { + const float width = 0.15f; + keyBindingContainer = catchInputManager.KeyBindingContainer; RelativeSizeAxes = Axes.Both; @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, - Width = 0.15f, + Width = width, Children = new Drawable[] { leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) @@ -71,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, - Width = 0.15f, + Width = width, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Children = new Drawable[] @@ -104,70 +105,69 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool OnMouseDown(MouseDownEvent e) { - return handleDown(e.Button, e.ScreenSpaceMousePosition); + return updateAction(e.Button, getTouchCatchActionFromInput(e.ScreenSpaceMousePosition)); } protected override bool OnTouchDown(TouchDownEvent e) { - handleDown(e.Touch.Source, e.ScreenSpaceTouch.Position); - return true; + return updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); } protected override bool OnMouseMove(MouseMoveEvent e) { + Show(); + + TouchCatchAction? action = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); + // multiple mouse buttons may be pressed and handling the same action. foreach (MouseButton button in e.PressedButtons) - handleMove(button, e.ScreenSpaceMousePosition); - return true; + updateAction(button, action); + + return false; } protected override void OnTouchMove(TouchMoveEvent e) { - handleMove(e.Touch.Source, e.ScreenSpaceTouch.Position); + updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); base.OnTouchMove(e); } protected override void OnMouseUp(MouseUpEvent e) { - handleUp(e.Button); + updateAction(e.Button, null); base.OnMouseUp(e); } protected override void OnTouchUp(TouchUpEvent e) { - handleUp(e.Touch.Source); + updateAction(e.Touch.Source, null); base.OnTouchUp(e); } - private void handleMove(object inputSource, Vector2 screenSpaceInputPosition) + private bool updateAction(object source, TouchCatchAction? newAction) { - Show(); + TouchCatchAction? actionBefore = null; - trackedActionSources[inputSource] = getTouchCatchActionFromInput(screenSpaceInputPosition); - updatePressedActions(); - } + if (trackedActionSources.TryGetValue(source, out TouchCatchAction found)) + actionBefore = found; - private bool handleDown(object inputSource, Vector2 screenSpaceInputPosition) - { - TouchCatchAction action = getTouchCatchActionFromInput(screenSpaceInputPosition); + if (actionBefore != newAction) + { + if (newAction != null) + trackedActionSources[source] = newAction.Value; + else + trackedActionSources.Remove(source); - if (action == TouchCatchAction.None) - return false; - - trackedActionSources[inputSource] = action; - updatePressedActions(); - - return true; - } - - private void handleUp(object source) - { - if (trackedActionSources.Remove(source)) updatePressedActions(); + } + + return newAction != null; } private void updatePressedActions() { + Show(); + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.MoveLeft)) keyBindingContainer.TriggerPressed(CatchAction.MoveLeft); else @@ -178,13 +178,13 @@ namespace osu.Game.Rulesets.Catch.UI else keyBindingContainer.TriggerReleased(CatchAction.MoveRight); - if (trackedActionSources.ContainsValue(TouchCatchAction.DashRight) || trackedActionSources.ContainsValue(TouchCatchAction.DashLeft)) + if (trackedActionSources.ContainsValue(TouchCatchAction.DashLeft) || trackedActionSources.ContainsValue(TouchCatchAction.DashRight)) keyBindingContainer.TriggerPressed(CatchAction.Dash); else keyBindingContainer.TriggerReleased(CatchAction.Dash); } - private TouchCatchAction getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) + private TouchCatchAction? getTouchCatchActionFromInput(Vector2 screenSpaceInputPosition) { if (leftDashBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.DashLeft; @@ -195,18 +195,18 @@ namespace osu.Game.Rulesets.Catch.UI if (rightBox.Contains(screenSpaceInputPosition)) return TouchCatchAction.MoveRight; - return TouchCatchAction.None; + return null; } - protected override void PopIn() => mainContent.FadeIn(500, Easing.OutQuint); + protected override void PopIn() => mainContent.FadeIn(300, Easing.OutQuint); - protected override void PopOut() => mainContent.FadeOut(300); + protected override void PopOut() => mainContent.FadeOut(300, Easing.OutQuint); private class InputArea : CompositeDrawable, IKeyBindingHandler { private readonly TouchCatchAction handledAction; - private readonly Box overlay; + private readonly Box highlightOverlay; private readonly IEnumerable> trackedActions; @@ -221,24 +221,20 @@ namespace osu.Game.Rulesets.Catch.UI { new Container { - Width = 1, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Box { + RelativeSizeAxes = Axes.Both, Alpha = 0.8f, Colour = colour, - Width = 1, - RelativeSizeAxes = Axes.Both, }, - overlay = new Box + highlightOverlay = new Box { - Alpha = 0, - Colour = colour.Multiply(1.4f), - Blending = BlendingParameters.Additive, - Width = 1, RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, } } } @@ -264,17 +260,16 @@ namespace osu.Game.Rulesets.Catch.UI return; isHighlighted = isHandling; - overlay.FadeTo(isHighlighted ? 0.5f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); + highlightOverlay.FadeTo(isHighlighted ? 0.1f : 0, isHighlighted ? 80 : 400, Easing.OutQuint); } } public enum TouchCatchAction { - MoveLeft = 0, - MoveRight = 1, - DashLeft = 2, - DashRight = 3, - None = 4 + MoveLeft, + MoveRight, + DashLeft, + DashRight, } } } From f3fc8af6ee9fde39b0b4c644c2bc702ae0242107 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:03:36 +0900 Subject: [PATCH 165/174] Adjust visuals --- .../UI/CatchTouchInputMapper.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 13333ecc61..cdb587ad64 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI @@ -55,18 +54,19 @@ namespace osu.Game.Rulesets.Catch.UI Width = width, Children = new Drawable[] { - leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources, colours.Gray3) + leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, + }, + leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Colour = colours.GrayC, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, - leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources, colours.Gray2) - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - } } }, new Container @@ -77,12 +77,13 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopRight, Children = new Drawable[] { - rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources, colours.Gray3) + rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, + Colour = colours.GrayC, }, - rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources, colours.Gray2) + rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, Width = 0.5f, @@ -212,7 +213,7 @@ namespace osu.Game.Rulesets.Catch.UI private bool isHighlighted; - public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions, Color4 colour) + public InputArea(TouchCatchAction handledAction, IEnumerable> trackedActions) { this.handledAction = handledAction; this.trackedActions = trackedActions; @@ -227,8 +228,8 @@ namespace osu.Game.Rulesets.Catch.UI new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.8f, - Colour = colour, + Alpha = 0.2f, + Colour = OsuColour.Gray(0.8f), }, highlightOverlay = new Box { From 280b1dd48406d2af07be856e8abc1cdae097d18f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Sep 2022 16:12:18 +0900 Subject: [PATCH 166/174] Revert async Task change --- osu.Game/BackgroundBeatmapProcessor.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 071862146f..ea5904a8d3 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; @@ -45,12 +46,12 @@ namespace osu.Game { base.LoadComplete(); - Task.Run(async () => + Task.Run(() => { Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); - await processBeatmapSetsWithMissingMetrics(); - await processScoresWithMissingStatistics(); + processBeatmapSetsWithMissingMetrics(); + processScoresWithMissingStatistics(); }).ContinueWith(t => { if (t.Exception?.InnerException is ObjectDisposedException) @@ -99,7 +100,7 @@ namespace osu.Game } } - private async Task processBeatmapSetsWithMissingMetrics() + private void processBeatmapSetsWithMissingMetrics() { HashSet beatmapSetIds = new HashSet(); @@ -123,7 +124,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - await Task.Delay(TimeToSleepDuringGameplay); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => @@ -146,7 +147,7 @@ namespace osu.Game } } - private async Task processScoresWithMissingStatistics() + private void processScoresWithMissingStatistics() { HashSet scoreIds = new HashSet(); @@ -168,7 +169,7 @@ namespace osu.Game while (localUserPlayInfo?.IsPlaying.Value == true) { Logger.Log("Background processing sleeping due to active gameplay..."); - await Task.Delay(TimeToSleepDuringGameplay); + Thread.Sleep(TimeToSleepDuringGameplay); } try From bffc9555bfcf5861ea9bc566f0d76876eef0ef5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:12:54 +0900 Subject: [PATCH 167/174] Adjust visuals slightly further (and remove double-gray application) --- .../TestSceneCatchTouchInput.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs index b510a69f14..cbf6e8f202 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchTouchInput.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests } [Test] - public void TestInputField() + public void TestBasic() { AddStep("show overlay", () => catchTouchInputMapper.Show()); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index cdb587ad64..e6736d6c93 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Colour = colours.GrayC, + Colour = colours.Gray9, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, Width = 0.5f, - Colour = colours.GrayC, + Colour = colours.Gray9, }, rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources) { @@ -223,13 +223,14 @@ namespace osu.Game.Rulesets.Catch.UI new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - Colour = OsuColour.Gray(0.8f), + Alpha = 0.15f, }, highlightOverlay = new Box { From ec21ab8171c7b2a346e1f11e4550b5c6896506e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 16:39:22 +0900 Subject: [PATCH 168/174] Reduce ramp mod multipliers in line with other difficulty change mods for now Closes https://github.com/ppy/osu/issues/20204. Will require reprocessing of everything server-side. --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 ++ osu.Game/Rulesets/Mods/ModWindDown.cs | 1 - osu.Game/Rulesets/Mods/ModWindUp.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 7031489d0e..72a7f4b9a3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Mods /// public const double FINAL_RATE_PROGRESS = 0.75f; + public override double ScoreMultiplier => 0.5; + [SettingSource("Initial rate", "The starting speed of the track")] public abstract BindableNumber InitialRate { get; } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index e84bdab69c..22ed7c2efd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "WD"; public override LocalisableString Description => "Sloooow doooown..."; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleDown; - public override double ScoreMultiplier => 1.0; [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 39cee50f96..13ece6d9a3 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "WU"; public override LocalisableString Description => "Can you keep up?"; public override IconUsage? Icon => FontAwesome.Solid.ChevronCircleUp; - public override double ScoreMultiplier => 1.0; [SettingSource("Initial rate", "The starting speed of the track")] public override BindableNumber InitialRate { get; } = new BindableDouble From 648c6245bbe72f1625dcecdb35875580d5f76a93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:39:54 +0900 Subject: [PATCH 169/174] Add xmldoc --- osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs index 11b81b26fd..baffe106ed 100644 --- a/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/SizePreservingSpriteText.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics; @@ -14,6 +12,9 @@ using osuTK.Graphics; namespace osu.Game.Graphics.Sprites { + /// + /// A wrapped version of which will expand in size based on text content, but never shrink back down. + /// public class SizePreservingSpriteText : CompositeDrawable { private readonly OsuSpriteText text = new OsuSpriteText(); From 68d1b7d7cf61603c1ee05e4958be1aec371cc8d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:48:51 +0900 Subject: [PATCH 170/174] Reduce test count --- .../TestSceneUprightAspectMaintainingContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index f81f8e8d85..00ecc166d5 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly List> childContainers = new List>(rows); // Preferably should be set to (4 * 2^n) - private const int rotation_step_count = 8; + private const int rotation_step_count = 3; private readonly List flipStates = new List(); private readonly List rotationSteps = new List(); @@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.UserInterface flipStates.AddRange(new[] { 1, -1 }); rotationSteps.AddRange(Enumerable.Range(0, rotation_step_count).Select(x => 360f * ((float)x / rotation_step_count))); - scaleSteps.AddRange(new[] { 1, 0.5f, 0.3f, 1.5f, 2.0f }); + scaleSteps.AddRange(new[] { 1, 0.3f, 1.5f }); } [Test] From 9ec399a25ac560d409930d0387499ed247e0c549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:49:31 +0900 Subject: [PATCH 171/174] Remove NRT overrides in new tests --- .../Visual/UserInterface/TestSceneSizePreservingSpriteText.cs | 2 -- .../UserInterface/TestSceneUprightAspectMaintainingContainer.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs index 69f5015af6..c4568d9aeb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSizePreservingSpriteText.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Globalization; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs index 00ecc166d5..67c26829df 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUprightAspectMaintainingContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; From a2f96ea120b9461edae3194e0bf7a6229fedceab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 17:55:29 +0900 Subject: [PATCH 172/174] Make `random` implicitly null to avoid potential incorrect behaviour in `randomBool` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 24 +++++++++------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 17df1bf489..056a325dce 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast; - private Random? rng; + private Random random = null!; public void ApplyToBeatmap(IBeatmap beatmap) { @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Mods Seed.Value ??= RNG.Next(); - rng = new Random((int)Seed.Value); + random = new Random((int)Seed.Value); var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); @@ -50,14 +50,14 @@ namespace osu.Game.Rulesets.Osu.Mods { if (shouldStartNewSection(osuBeatmap, positionInfos, i)) { - sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.0008f); + sectionOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.0008f); flowDirection = !flowDirection; } if (i == 0) { - positionInfos[i].DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); - positionInfos[i].RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); + positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); + positionInfos[i].RelativeAngle = (float)(random.NextDouble() * 2 * Math.PI - Math.PI); } else { @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods float flowChangeOffset = 0; // Offsets only the angle of the current hit object. - float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); + float oneTimeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); if (shouldApplyFlowChange(positionInfos, i)) { - flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(rng, 0, 0.002f); + flowChangeOffset = OsuHitObjectGenerationUtils.RandomGaussian(random, 0, 0.002f); flowDirection = !flowDirection; } @@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Osu.Mods bool previousObjectWasOnDownbeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject, true); bool previousObjectWasOnBeat = OsuHitObjectGenerationUtils.IsHitObjectOnBeat(beatmap, positionInfos[i - 1].HitObject); - return (previousObjectStartedCombo && randomBool(0.6f)) || + return (previousObjectStartedCombo && random.NextDouble() < 0.6f) || previousObjectWasOnDownbeat || - (previousObjectWasOnBeat && randomBool(0.4f)); + (previousObjectWasOnBeat && random.NextDouble() < 0.4f); } /// Whether a flow change should be applied at the current . @@ -120,11 +120,7 @@ namespace osu.Game.Rulesets.Osu.Mods bool previousObjectStartedCombo = positionInfos[Math.Max(0, i - 2)].HitObject.IndexInCurrentCombo > 1 && positionInfos[i - 1].HitObject.NewCombo; - return previousObjectStartedCombo && randomBool(0.6f); + return previousObjectStartedCombo && random.NextDouble() < 0.6f; } - - /// true with a probability of , false otherwise. - private bool randomBool(float probability) => - rng?.NextDouble() < probability; } } From 31cd7cdca0902e10605552cfdb004531bfb4a3df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 18:00:51 +0900 Subject: [PATCH 173/174] Refactor `IsHitObjectOnBeat` to be understandable --- .../Utils/OsuHitObjectGenerationUtils.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index a890dbde43..3a8b3f67d0 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -195,19 +195,17 @@ namespace osu.Game.Rulesets.Osu.Utils /// true if hitObject is on a (down-)beat, false otherwise. public static bool IsHitObjectOnBeat(OsuBeatmap beatmap, OsuHitObject hitObject, bool downbeatsOnly = false) { - var timingPoints = beatmap.ControlPointInfo.TimingPoints; - var currentTimingPoint = timingPoints.LastOrDefault(p => p.Time <= hitObject.StartTime); + var timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); - if (currentTimingPoint == null) - return false; + double timeSinceTimingPoint = hitObject.StartTime - timingPoint.Time; - double timeSinceTimingPoint = hitObject.StartTime - currentTimingPoint.Time; + double beatLength = timingPoint.BeatLength; - double length = downbeatsOnly - ? currentTimingPoint.BeatLength * currentTimingPoint.TimeSignature.Numerator - : currentTimingPoint.BeatLength; + if (downbeatsOnly) + beatLength *= timingPoint.TimeSignature.Numerator; - return (timeSinceTimingPoint + 1) % length < 2; + // Ensure within 1ms of expected location. + return Math.Abs(timeSinceTimingPoint + 1) % beatLength < 2; } /// From 7d100c5eeca6aab52fc624b31f99b4d66515d01d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 9 Sep 2022 18:10:10 +0900 Subject: [PATCH 174/174] Fix test in line with new expectations --- .../Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs index 0af876bfe8..5afda0ad0c 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCardThumbnail.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Beatmaps }; }); AddStep("enable dim", () => thumbnail.Dimmed.Value = true); - AddUntilStep("button visible", () => playButton.IsPresent); + AddUntilStep("button visible", () => playButton.Alpha == 1); AddStep("click button", () => { @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddStep("disable dim", () => thumbnail.Dimmed.Value = false); AddWaitStep("wait some", 3); - AddAssert("button still visible", () => playButton.IsPresent); + AddAssert("button still visible", () => playButton.Alpha == 1); // The track plays in real-time, so we need to check for progress in increments to avoid timeout. AddUntilStep("progress > 0.25", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.25); @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.Beatmaps AddUntilStep("progress > 0.75", () => thumbnail.ChildrenOfType().Single().Progress.Value > 0.75); AddUntilStep("wait for track to end", () => !playButton.Playing.Value); - AddUntilStep("button hidden", () => !playButton.IsPresent); + AddUntilStep("button hidden", () => playButton.Alpha == 0); } private void iconIs(IconUsage usage) => AddUntilStep("icon is correct", () => playButton.ChildrenOfType().Any(icon => icon.Icon.Equals(usage)));