From 4f4c481a678b8fb5e608ca9c7af5ce4372ac80f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 12 Jul 2023 19:21:24 +0900 Subject: [PATCH 1/7] Fix timing distribution graph sometimes not displaying correctly Weird "basal" height logic just didn't make any sense (was getting stuck at 1 when `DrawHeight` was 0) --- .../HitEventTimingDistributionGraph.cs | 92 ++++++------------- 1 file changed, 27 insertions(+), 65 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 6b1850002d..16da8c64a0 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -211,7 +211,8 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly bool isCentre; private readonly float totalValue; - private float basalHeight; + private const float minimum_height = 0.02f; + private float offsetAdjustment; private Circle[] boxOriginals = null!; @@ -256,15 +257,17 @@ namespace osu.Game.Screens.Ranking.Statistics else { // A bin with no value draws a grey dot instead. - Circle dot = new Circle + InternalChildren = boxOriginals = new[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Colour = isCentre ? Color4.White : Color4.Gray, - Height = 0, + new Circle + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Colour = isCentre ? Color4.White : Color4.Gray, + Height = 0, + } }; - InternalChildren = boxOriginals = new[] { dot }; } } @@ -272,31 +275,24 @@ namespace osu.Game.Screens.Ranking.Statistics { base.LoadComplete(); - if (!values.Any()) - return; - - updateBasalHeight(); - - foreach (var boxOriginal in boxOriginals) - { - boxOriginal.Y = 0; - boxOriginal.Height = basalHeight; - } - float offsetValue = 0; - for (int i = 0; i < values.Count; i++) + for (int i = 0; i < boxOriginals.Length; 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; - } - } + int value = i < values.Count ? values[i].Value : 0; - protected override void Update() - { - base.Update(); - updateBasalHeight(); + var box = boxOriginals[i]; + + box.Y = 0; + box.Height = 0; + + box.MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + box.ResizeHeightTo(heightForValue(value), duration, Easing.OutQuint); + offsetValue -= value; + } + + if (boxAdjustment != null) + drawAdjustmentBar(); } public void UpdateOffset(float adjustment) @@ -324,43 +320,9 @@ namespace osu.Game.Screens.Ranking.Statistics drawAdjustmentBar(); } - private void updateBasalHeight() - { - float newBasalHeight = DrawHeight > DrawWidth ? DrawWidth / DrawHeight : 1; + private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue; - if (newBasalHeight == basalHeight) - return; - - basalHeight = newBasalHeight; - foreach (var dot in boxOriginals) - dot.Height = basalHeight; - - draw(); - } - - private float offsetForValue(float value) => (1 - basalHeight) * value / maxValue; - - private float heightForValue(float value) => MathF.Max(basalHeight + offsetForValue(value), 0); - - private void draw() - { - resizeBars(); - - if (boxAdjustment != null) - drawAdjustmentBar(); - } - - private void resizeBars() - { - float offsetValue = 0; - - for (int i = 0; i < values.Count; i++) - { - boxOriginals[i].Y = offsetForValue(offsetValue) * DrawHeight; - boxOriginals[i].Height = heightForValue(values[i].Value); - offsetValue -= values[i].Value; - } - } + private float heightForValue(float value) => minimum_height + offsetForValue(value); private void drawAdjustmentBar() { From 8e294c325842430f56189ef8142a4b288279c07f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 13:22:45 +0900 Subject: [PATCH 2/7] Add test coverage of hitting objects immediately after a swell --- .../Judgements/TestSceneSwellJudgements.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs index ccc829f09e..4abad98eab 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Judgements/TestSceneSwellJudgements.cs @@ -114,5 +114,75 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements AddAssert("all tick offsets are 0", () => JudgementResults.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0)); } + + /// + /// Ensure input is correctly sent to subsequent hits if a swell is fully completed. + /// + [Test] + public void TestHitSwellThenHitHit() + { + const double swell_time = 1000; + const double hit_time = 1150; + + Swell swell = new Swell + { + StartTime = swell_time, + Duration = 100, + RequiredHits = 1 + }; + + Hit hit = new Hit + { + StartTime = hit_time + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(swell_time, TaikoAction.LeftRim), + new TaikoReplayFrame(hit_time, TaikoAction.RightCentre), + }; + + PerformTest(frames, CreateBeatmap(swell, hit)); + + AssertJudgementCount(3); + + AssertResult(0, HitResult.IgnoreHit); + AssertResult(0, HitResult.LargeBonus); + AssertResult(0, HitResult.Great); + } + + [Test] + public void TestMissSwellThenHitHit() + { + const double swell_time = 1000; + const double hit_time = 1150; + + Swell swell = new Swell + { + StartTime = swell_time, + Duration = 100, + RequiredHits = 1 + }; + + Hit hit = new Hit + { + StartTime = hit_time + }; + + List frames = new List + { + new TaikoReplayFrame(0), + new TaikoReplayFrame(hit_time, TaikoAction.RightCentre), + }; + + PerformTest(frames, CreateBeatmap(swell, hit)); + + AssertJudgementCount(3); + + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.IgnoreMiss); + AssertResult(0, HitResult.Great); + } } } From 259ac6d427b3142a99c0366024adb068b97cb47e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 13:16:58 +0900 Subject: [PATCH 3/7] Fix osu!taiko swells eating input after already being judged --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 8441e3a749..3fa6f4b756 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -276,6 +276,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (Time.Current < HitObject.StartTime) return false; + if (AllJudged) + return false; + bool isCentre = e.Action == TaikoAction.LeftCentre || e.Action == TaikoAction.RightCentre; // Ensure alternating centre and rim hits From e2b5abd4e8e90a0cd6ef169f2fefaad7909a9376 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 14:43:14 +0900 Subject: [PATCH 4/7] Split bar drawable creation into own method --- .../HitEventTimingDistributionGraph.cs | 137 +++++++++--------- 1 file changed, 69 insertions(+), 68 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 16da8c64a0..7e7c0ccb54 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -113,94 +113,95 @@ namespace osu.Game.Screens.Ranking.Statistics } } - if (barDrawables != null) - { - for (int i = 0; i < barDrawables.Length; i++) - { - barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value)); - } - } + if (barDrawables == null) + createBarDrawables(); else { - int maxCount = bins.Max(b => b.Values.Sum()); - barDrawables = bins.Select((bin, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray(); + for (int i = 0; i < barDrawables.Length; i++) + barDrawables[i].UpdateOffset(bins[i].Sum(b => b.Value)); + } + } - Container axisFlow; + private void createBarDrawables() + { + int maxCount = bins.Max(b => b.Values.Sum()); + barDrawables = bins.Select((_, i) => new Bar(bins[i], maxCount, i == timing_distribution_centre_bin_index)).ToArray(); - const float axis_font_size = 12; + Container axisFlow; - InternalChild = new GridContainer + const float axis_font_size = 12; + + InternalChild = new GridContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Content = new[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.8f, - Content = new[] + new Drawable[] { - new Drawable[] + new GridContainer { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { barDrawables } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - Height = axis_font_size, - } - }, + RelativeSizeAxes = Axes.Both, + Content = new[] { barDrawables } + } }, - RowDimensions = new[] + new Drawable[] { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - }; + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + Height = axis_font_size, + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; - // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - double maxValue = timing_distribution_bins * binSize; - double axisValueStep = maxValue / axis_points; + // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + double maxValue = timing_distribution_bins * binSize; + double axisValueStep = maxValue / axis_points; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "0", + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); + + for (int i = 1; i <= axis_points; i++) + { + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; axisFlow.Add(new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "0", + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) }); - for (int i = 1; i <= axis_points; i++) + axisFlow.Add(new OsuSpriteText { - double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); - float alpha = 1f - position * 0.8f; - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = position / 2, - Alpha = alpha, - Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) - }); - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: axis_font_size, weight: FontWeight.SemiBold) + }); } } From 21f26f98da76ee5d89dc7c5c8e32309bd2caf034 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 14:57:01 +0900 Subject: [PATCH 5/7] Fix graph breaking when resized vertically --- .../HitEventTimingDistributionGraph.cs | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 7e7c0ccb54..85c2777645 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -220,6 +222,8 @@ namespace osu.Game.Screens.Ranking.Statistics private Circle? boxAdjustment; + private float? lastDrawHeight; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -276,24 +280,18 @@ namespace osu.Game.Screens.Ranking.Statistics { base.LoadComplete(); - float offsetValue = 0; + Scheduler.AddOnce(updateMetrics, true); + } - for (int i = 0; i < boxOriginals.Length; i++) + protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source) + { + if (invalidation.HasFlagFast(Invalidation.DrawSize)) { - int value = i < values.Count ? values[i].Value : 0; - - var box = boxOriginals[i]; - - box.Y = 0; - box.Height = 0; - - box.MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); - box.ResizeHeightTo(heightForValue(value), duration, Easing.OutQuint); - offsetValue -= value; + if (lastDrawHeight != null && lastDrawHeight != DrawHeight) + Scheduler.AddOnce(updateMetrics, false); } - if (boxAdjustment != null) - drawAdjustmentBar(); + return base.OnInvalidate(invalidation, source); } public void UpdateOffset(float adjustment) @@ -318,12 +316,36 @@ namespace osu.Game.Screens.Ranking.Statistics } offsetAdjustment = adjustment; - drawAdjustmentBar(); + + Scheduler.AddOnce(updateMetrics, true); } - private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue; + private void updateMetrics(bool animate = true) + { + float offsetValue = 0; - private float heightForValue(float value) => minimum_height + offsetForValue(value); + for (int i = 0; i < boxOriginals.Length; i++) + { + int value = i < values.Count ? values[i].Value : 0; + + var box = boxOriginals[i]; + + box.Y = 0; + box.Height = 0; + + box.MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); + box.ResizeHeightTo(heightForValue(value), duration, Easing.OutQuint); + offsetValue -= value; + } + + if (boxAdjustment != null) + drawAdjustmentBar(); + + if (!animate) + FinishTransforms(true); + + lastDrawHeight = DrawHeight; + } private void drawAdjustmentBar() { @@ -332,6 +354,10 @@ namespace osu.Game.Screens.Ranking.Statistics boxAdjustment.ResizeHeightTo(heightForValue(offsetAdjustment), duration, Easing.OutQuint); boxAdjustment.FadeTo(!hasAdjustment ? 0 : 1, duration, Easing.OutQuint); } + + private float offsetForValue(float value) => (1 - minimum_height) * value / maxValue; + + private float heightForValue(float value) => minimum_height + offsetForValue(value); } } } From 1d62a041ccdd8ae10066823080b76861d42ff15d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Jul 2023 15:07:26 +0900 Subject: [PATCH 6/7] Fix animation restarting unexpectedly --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 85c2777645..e8d2eefd81 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -330,9 +330,6 @@ namespace osu.Game.Screens.Ranking.Statistics var box = boxOriginals[i]; - box.Y = 0; - box.Height = 0; - box.MoveToY(offsetForValue(offsetValue) * BoundingBox.Height, duration, Easing.OutQuint); box.ResizeHeightTo(heightForValue(value), duration, Easing.OutQuint); offsetValue -= value; From cde8d8e7f1fccc221527209216c24a73612e7d71 Mon Sep 17 00:00:00 2001 From: OliBomby Date: Thu, 13 Jul 2023 14:33:21 +0200 Subject: [PATCH 7/7] Optimize realm transactions for editor Save action --- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 54c243e842..265149e378 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -431,8 +431,9 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = BeatmapOnlineStatus.LocallyModified; beatmapInfo.ResetOnlineInfo(); - using (var stream = new MemoryStream()) + Realm.Write(r => { + using var stream = new MemoryStream(); using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw); @@ -458,23 +459,20 @@ namespace osu.Game.Beatmaps updateHashAndMarkDirty(setInfo); - Realm.Write(r => - { - var liveBeatmapSet = r.Find(setInfo.ID)!; + var liveBeatmapSet = r.Find(setInfo.ID)!; - setInfo.CopyChangesToRealm(liveBeatmapSet); + setInfo.CopyChangesToRealm(liveBeatmapSet); - if (transferCollections) - beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); + if (transferCollections) + beatmapInfo.TransferCollectionReferences(r, oldMd5Hash); - liveBeatmapSet.Beatmaps.Single(b => b.ID == beatmapInfo.ID) - .UpdateLocalScores(r); + liveBeatmapSet.Beatmaps.Single(b => b.ID == beatmapInfo.ID) + .UpdateLocalScores(r); - // do not look up metadata. - // this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst. - ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None); - }); - } + // do not look up metadata. + // this is a locally-modified set now, so looking up metadata is busy work at best and harmful at worst. + ProcessBeatmap?.Invoke(liveBeatmapSet, MetadataLookupScope.None); + }); Debug.Assert(beatmapInfo.BeatmapSet != null);