From a7f8c5935dd611843ff92c9d4193a94281b16a98 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:36:56 +0900 Subject: [PATCH 001/170] Expose LowestSuccessfulHitResult() --- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 018b50bd3d..77acbd4137 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Scoring /// Retrieves the with the largest hit window that produces a successful hit. /// /// The lowest allowed successful . - protected HitResult LowestSuccessfulHitResult() + public HitResult LowestSuccessfulHitResult() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) { From e98f51923a7c242cae1ec275726d2a18a82dd48b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:38:24 +0900 Subject: [PATCH 002/170] Add timing distribution to OsuScoreProcessor --- .../Scoring/OsuScoreProcessor.cs | 76 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 +++ 2 files changed, 88 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 79a6ea7e92..83339bd061 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,17 +1,93 @@ // 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.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + /// + /// The number of bins on each side of the timing distribution. + /// + private const int timing_distribution_bins = 25; + + /// + /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. + /// + private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; + + /// + /// The centre bin, with a timing distribution very close to/at 0. + /// + private const int timing_distribution_centre_bin_index = timing_distribution_bins; + + private TimingDistribution timingDistribution; + + public override void ApplyBeatmap(IBeatmap beatmap) + { + var hitWindows = CreateHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + + timingDistribution = new TimingDistribution(total_timing_distribution_bins, hitWindows.WindowFor(hitWindows.LowestSuccessfulHitResult()) / timing_distribution_bins); + + base.ApplyBeatmap(beatmap); + } + + protected override void OnResultApplied(JudgementResult result) + { + base.OnResultApplied(result); + + if (result.IsHit) + { + int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); + timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; + } + } + + protected override void OnResultReverted(JudgementResult result) + { + base.OnResultReverted(result); + + if (result.IsHit) + { + int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); + timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; + } + } + + public override void PopulateScore(ScoreInfo score) + { + base.PopulateScore(score); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + timingDistribution.Bins.AsSpan().Clear(); + } + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); public override HitWindows CreateHitWindows() => new OsuHitWindows(); } + + public class TimingDistribution + { + public readonly int[] Bins; + public readonly double BinSize; + + public TimingDistribution(int binCount, double binSize) + { + Bins = new int[binCount]; + BinSize = binSize; + } + } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1f40f44dce..619547aef4 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -129,6 +129,12 @@ namespace osu.Game.Rulesets.Scoring } updateScore(); + + OnResultApplied(result); + } + + protected virtual void OnResultApplied(JudgementResult result) + { } protected sealed override void RevertResultInternal(JudgementResult result) @@ -154,6 +160,12 @@ namespace osu.Game.Rulesets.Scoring } updateScore(); + + OnResultReverted(result); + } + + protected virtual void OnResultReverted(JudgementResult result) + { } private void updateScore() From c7c94eb3fdd6c25363c9379bc9c881a407952171 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:38:50 +0900 Subject: [PATCH 003/170] Initial implementation of timing distribution graph --- .../TestSceneTimingDistributionGraph.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs new file mode 100644 index 0000000000..456ac19383 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.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; +using osu.Game.Rulesets.Osu.Scoring; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneTimingDistributionGraph : OsuTestScene + { + public TestSceneTimingDistributionGraph() + { + Add(new TimingDistributionGraph(createNormalDistribution()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 100) + }); + } + + private TimingDistribution createNormalDistribution() + { + var distribution = new TimingDistribution(51, 5); + + // We create an approximately-normal distribution of 51 elements by using the 13th binomial row (14 initial elements) and subdividing the inner values twice. + var row = new List { 1 }; + for (int i = 0; i < 13; i++) + row.Add(row[i] * (13 - i) / (i + 1)); + + // Each subdivision yields 2n-1 total elements, so first subdivision will contain 27 elements, and the second will contain 53 elements. + for (int div = 0; div < 2; div++) + { + var newRow = new List { 1 }; + + for (int i = 0; i < row.Count - 1; i++) + { + newRow.Add((row[i] + row[i + 1]) / 2); + newRow.Add(row[i + 1]); + } + + row = newRow; + } + + // After the subdivisions take place, we're left with 53 values which we use the inner 51 of. + for (int i = 1; i < row.Count - 1; i++) + distribution.Bins[i - 1] = row[i]; + + return distribution; + } + } + + public class TimingDistributionGraph : CompositeDrawable + { + private readonly TimingDistribution distribution; + + public TimingDistributionGraph(TimingDistribution distribution) + { + this.distribution = distribution; + } + + [BackgroundDependencyLoader] + private void load() + { + int maxCount = distribution.Bins.Max(); + + var bars = new Drawable[distribution.Bins.Length]; + for (int i = 0; i < bars.Length; i++) + bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + }; + } + + private class Bar : CompositeDrawable + { + public Bar() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + RelativeSizeAxes = Axes.Both; + + Padding = new MarginPadding { Horizontal = 1 }; + + InternalChild = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#66FFCC") + }; + } + } + } +} From 6217fb26daf0a88c9c8bd867f1efb533c0c5c1bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 18:50:25 +0900 Subject: [PATCH 004/170] Finish up design implementation of timing distribution graph --- .../TestSceneTimingDistributionGraph.cs | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 456ac19383..4129975166 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -8,6 +8,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Scoring; using osuTK; @@ -21,7 +23,7 @@ namespace osu.Game.Tests.Visual.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(300, 100) + Size = new Vector2(400, 130) }); } @@ -58,6 +60,17 @@ namespace osu.Game.Tests.Visual.Ranking public class TimingDistributionGraph : CompositeDrawable { + /// + /// The number of data points shown on the axis below the graph. + /// + private const float axis_points = 5; + + /// + /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. + /// Without an inset, the final data point will be placed halfway outside the graph. + /// + private const float axis_value_inset = 0.2f; + private readonly TimingDistribution distribution; public TimingDistributionGraph(TimingDistribution distribution) @@ -74,11 +87,79 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < bars.Length; i++) bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + Container axisFlow; + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, - Content = new[] { bars } + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } }; + + // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. + // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + int sideBins = (distribution.Bins.Length - 1) / 2; + double maxValue = sideBins * distribution.BinSize; + double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "0", + Font = OsuFont.GetFont(size: 12, 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, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: 12, 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: 12, weight: FontWeight.SemiBold) + }); + } } private class Bar : CompositeDrawable From c9469dc0ddaf98b6d119e01581e09b7c209720fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 22:48:43 +0900 Subject: [PATCH 005/170] Add background --- .../TestSceneTimingDistributionGraph.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 4129975166..73225ff599 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -19,12 +19,20 @@ namespace osu.Game.Tests.Visual.Ranking { public TestSceneTimingDistributionGraph() { - Add(new TimingDistributionGraph(createNormalDistribution()) + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(400, 130) - }); + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + new TimingDistributionGraph(createNormalDistribution()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(400, 130) + } + }; } private TimingDistribution createNormalDistribution() From ce56c457218879b78b14bd1226c300e27731e194 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 22:48:52 +0900 Subject: [PATCH 006/170] Implement the accuracy heatmap --- .../Ranking/TestSceneAccuracyHeatmap.cs | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs new file mode 100644 index 0000000000..8386ee5992 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -0,0 +1,273 @@ +// 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.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene + { + private readonly Box background; + private readonly Drawable object1; + private readonly Drawable object2; + private readonly Heatmap heatmap; + + public TestSceneAccuracyHeatmap() + { + Children = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333"), + }, + object1 = new BorderCircle + { + Position = new Vector2(256, 192), + Colour = Color4.Yellow, + }, + object2 = new BorderCircle + { + Position = new Vector2(500, 300), + }, + heatmap = new Heatmap + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scheduler.AddDelayed(() => + { + var randomPos = new Vector2( + RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), + RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); + + // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). + heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); + }, 1, true); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + heatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); + return true; + } + + private class Heatmap : CompositeDrawable + { + /// + /// Full size of the heatmap. + /// + private const float size = 100; + + /// + /// Size of the inner circle containing the "hit" points, relative to . + /// All other points outside of the inner circle are "miss" points. + /// + private const float inner_portion = 0.8f; + + private const float rotation = 45; + private const float point_size = 4; + + private Container allPoints; + + public Heatmap() + { + Size = new Vector2(size); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } + } + }, + allPoints = new Container { RelativeSizeAxes = Axes.Both } + }; + + Vector2 centre = new Vector2(size / 2); + int rows = (int)Math.Ceiling(size / point_size); + int cols = (int)Math.Ceiling(size / point_size); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Vector2 pos = new Vector2(c * point_size, r * point_size); + HitType type = HitType.Hit; + + if (Vector2.Distance(pos, centre) > size * inner_portion / 2) + type = HitType.Miss; + + allPoints.Add(new HitPoint(pos, type) + { + Size = new Vector2(point_size), + Colour = type == HitType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + }); + } + } + } + + public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + { + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. + double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. + double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. + + float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + + // Find the most relevant hit point. + double minDist = double.PositiveInfinity; + HitPoint point = null; + + foreach (var p in allPoints) + { + Vector2 localCentre = new Vector2(size / 2); + float localRadius = localCentre.X * inner_portion * normalisedDistance; + double localAngle = finalAngle + 3 * Math.PI / 4; + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + + float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); + + if (dist < minDist) + { + minDist = dist; + point = p; + } + } + + Debug.Assert(point != null); + point.Increment(); + } + } + + private class HitPoint : Circle + { + private readonly HitType type; + + public HitPoint(Vector2 position, HitType type) + { + this.type = type; + + Position = position; + Alpha = 0; + } + + public void Increment() + { + if (Alpha < 1) + Alpha += 0.1f; + else if (type == HitType.Hit) + Colour = ((Color4)Colour).Lighten(0.1f); + } + } + + private enum HitType + { + Hit, + Miss + } + + private class BorderCircle : CircularContainer + { + public BorderCircle() + { + Origin = Anchor.Centre; + Size = new Vector2(100); + + Masking = true; + BorderThickness = 2; + BorderColour = Color4.White; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(4), + } + }; + } + } + } +} From 6000e0f86a48e89d6e64f14cb4f9b5046f04c486 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 23:01:22 +0900 Subject: [PATCH 007/170] Increase size to match timing distribution graph --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 8386ee5992..b605ddcc35 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking /// /// Full size of the heatmap. /// - private const float size = 100; + private const float size = 130; /// /// Size of the inner circle containing the "hit" points, relative to . From b663c940aea1da0d66b8cea0c86637edaacf81d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 23:46:46 +0900 Subject: [PATCH 008/170] Rename enum --- .../Visual/Ranking/TestSceneAccuracyHeatmap.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index b605ddcc35..9f82287640 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -167,15 +167,15 @@ namespace osu.Game.Tests.Visual.Ranking for (int c = 0; c < cols; c++) { Vector2 pos = new Vector2(c * point_size, r * point_size); - HitType type = HitType.Hit; + HitPointType pointType = HitPointType.Hit; if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - type = HitType.Miss; + pointType = HitPointType.Miss; - allPoints.Add(new HitPoint(pos, type) + allPoints.Add(new HitPoint(pos, pointType) { Size = new Vector2(point_size), - Colour = type == HitType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }); } } @@ -216,11 +216,11 @@ namespace osu.Game.Tests.Visual.Ranking private class HitPoint : Circle { - private readonly HitType type; + private readonly HitPointType pointType; - public HitPoint(Vector2 position, HitType type) + public HitPoint(Vector2 position, HitPointType pointType) { - this.type = type; + this.pointType = pointType; Position = position; Alpha = 0; @@ -230,12 +230,12 @@ namespace osu.Game.Tests.Visual.Ranking { if (Alpha < 1) Alpha += 0.1f; - else if (type == HitType.Hit) + else if (pointType == HitPointType.Hit) Colour = ((Color4)Colour).Lighten(0.1f); } } - private enum HitType + private enum HitPointType { Hit, Miss From f9db37a1de9c425661df474f2d56fbf2d9ddfa27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 21:48:59 +0900 Subject: [PATCH 009/170] Split out types --- .../Scoring/OsuScoreProcessor.cs | 18 -- .../Scoring/TimingDistribution.cs | 17 ++ osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 186 ++++++++++++++++++ .../Statistics/TimingDistributionGraph.cs | 139 +++++++++++++ .../Ranking/TestSceneAccuracyHeatmap.cs | 175 +--------------- .../TestSceneTimingDistributionGraph.cs | 130 +----------- 6 files changed, 344 insertions(+), 321 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs create mode 100644 osu.Game.Rulesets.Osu/Statistics/Heatmap.cs create mode 100644 osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 83339bd061..a9d48df52d 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -62,11 +61,6 @@ namespace osu.Game.Rulesets.Osu.Scoring } } - public override void PopulateScore(ScoreInfo score) - { - base.PopulateScore(score); - } - protected override void Reset(bool storeResults) { base.Reset(storeResults); @@ -78,16 +72,4 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } - - public class TimingDistribution - { - public readonly int[] Bins; - public readonly double BinSize; - - public TimingDistribution(int binCount, double binSize) - { - Bins = new int[binCount]; - BinSize = binSize; - } - } } diff --git a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs new file mode 100644 index 0000000000..46f259f3d8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs @@ -0,0 +1,17 @@ +// 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.Rulesets.Osu.Scoring +{ + public class TimingDistribution + { + public readonly int[] Bins; + public readonly double BinSize; + + public TimingDistribution(int binCount, double binSize) + { + Bins = new int[binCount]; + BinSize = binSize; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs new file mode 100644 index 0000000000..8105d12991 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -0,0 +1,186 @@ +// 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.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Statistics +{ + public class Heatmap : CompositeDrawable + { + /// + /// Full size of the heatmap. + /// + private const float size = 130; + + /// + /// Size of the inner circle containing the "hit" points, relative to . + /// All other points outside of the inner circle are "miss" points. + /// + private const float inner_portion = 0.8f; + + private const float rotation = 45; + private const float point_size = 4; + + private Container allPoints; + + public Heatmap() + { + Size = new Vector2(size); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } + } + }, + allPoints = new Container { RelativeSizeAxes = Axes.Both } + }; + + Vector2 centre = new Vector2(size / 2); + int rows = (int)Math.Ceiling(size / point_size); + int cols = (int)Math.Ceiling(size / point_size); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Vector2 pos = new Vector2(c * point_size, r * point_size); + HitPointType pointType = HitPointType.Hit; + + if (Vector2.Distance(pos, centre) > size * inner_portion / 2) + pointType = HitPointType.Miss; + + allPoints.Add(new HitPoint(pos, pointType) + { + Size = new Vector2(point_size), + Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + }); + } + } + } + + public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + { + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. + double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. + double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. + + float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + + // Find the most relevant hit point. + double minDist = double.PositiveInfinity; + HitPoint point = null; + + foreach (var p in allPoints) + { + Vector2 localCentre = new Vector2(size / 2); + float localRadius = localCentre.X * inner_portion * normalisedDistance; + double localAngle = finalAngle + 3 * Math.PI / 4; + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + + float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); + + if (dist < minDist) + { + minDist = dist; + point = p; + } + } + + Debug.Assert(point != null); + point.Increment(); + } + + private class HitPoint : Circle + { + private readonly HitPointType pointType; + + public HitPoint(Vector2 position, HitPointType pointType) + { + this.pointType = pointType; + + Position = position; + Alpha = 0; + } + + public void Increment() + { + if (Alpha < 1) + Alpha += 0.1f; + else if (pointType == HitPointType.Hit) + Colour = ((Color4)Colour).Lighten(0.1f); + } + } + + private enum HitPointType + { + Hit, + Miss + } + } +} diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs new file mode 100644 index 0000000000..a47d726988 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -0,0 +1,139 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Osu.Scoring; + +namespace osu.Game.Rulesets.Osu.Statistics +{ + public class TimingDistributionGraph : CompositeDrawable + { + /// + /// The number of data points shown on the axis below the graph. + /// + private const float axis_points = 5; + + /// + /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. + /// Without an inset, the final data point will be placed halfway outside the graph. + /// + private const float axis_value_inset = 0.2f; + + private readonly TimingDistribution distribution; + + public TimingDistributionGraph(TimingDistribution distribution) + { + this.distribution = distribution; + } + + [BackgroundDependencyLoader] + private void load() + { + int maxCount = distribution.Bins.Max(); + + var bars = new Drawable[distribution.Bins.Length]; + for (int i = 0; i < bars.Length; i++) + bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + + Container axisFlow; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; + + // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. + // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + int sideBins = (distribution.Bins.Length - 1) / 2; + double maxValue = sideBins * distribution.BinSize; + double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "0", + Font = OsuFont.GetFont(size: 12, 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, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: 12, 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: 12, weight: FontWeight.SemiBold) + }); + } + } + + private class Bar : CompositeDrawable + { + public Bar() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + RelativeSizeAxes = Axes.Both; + + Padding = new MarginPadding { Horizontal = 1 }; + + InternalChild = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#66FFCC") + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 9f82287640..9e5fda0ae6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,15 +1,13 @@ // 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.Diagnostics; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Rulesets.Osu.Statistics; using osuTK; using osuTK.Graphics; @@ -70,177 +68,6 @@ namespace osu.Game.Tests.Visual.Ranking return true; } - private class Heatmap : CompositeDrawable - { - /// - /// Full size of the heatmap. - /// - private const float size = 130; - - /// - /// Size of the inner circle containing the "hit" points, relative to . - /// All other points outside of the inner circle are "miss" points. - /// - private const float inner_portion = 0.8f; - - private const float rotation = 45; - private const float point_size = 4; - - private Container allPoints; - - public Heatmap() - { - Size = new Vector2(size); - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(inner_portion), - Masking = true, - BorderThickness = 2f, - BorderColour = Color4.White, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#202624") - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 10, - Height = 2f, - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = -1, - Width = 2f, - Height = 10, - } - } - }, - allPoints = new Container { RelativeSizeAxes = Axes.Both } - }; - - Vector2 centre = new Vector2(size / 2); - int rows = (int)Math.Ceiling(size / point_size); - int cols = (int)Math.Ceiling(size / point_size); - - for (int r = 0; r < rows; r++) - { - for (int c = 0; c < cols; c++) - { - Vector2 pos = new Vector2(c * point_size, r * point_size); - HitPointType pointType = HitPointType.Hit; - - if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - pointType = HitPointType.Miss; - - allPoints.Add(new HitPoint(pos, pointType) - { - Size = new Vector2(point_size), - Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) - }); - } - } - } - - public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) - { - double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. - double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. - double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - - // Find the most relevant hit point. - double minDist = double.PositiveInfinity; - HitPoint point = null; - - foreach (var p in allPoints) - { - Vector2 localCentre = new Vector2(size / 2); - float localRadius = localCentre.X * inner_portion * normalisedDistance; - double localAngle = finalAngle + 3 * Math.PI / 4; - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); - - float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); - - if (dist < minDist) - { - minDist = dist; - point = p; - } - } - - Debug.Assert(point != null); - point.Increment(); - } - } - - private class HitPoint : Circle - { - private readonly HitPointType pointType; - - public HitPoint(Vector2 position, HitPointType pointType) - { - this.pointType = pointType; - - Position = position; - Alpha = 0; - } - - public void Increment() - { - if (Alpha < 1) - Alpha += 0.1f; - else if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(0.1f); - } - } - - private enum HitPointType - { - Hit, - Miss - } - private class BorderCircle : CircularContainer { public BorderCircle() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 73225ff599..7530fc42b8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -2,15 +2,11 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Statistics; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -65,128 +61,4 @@ namespace osu.Game.Tests.Visual.Ranking return distribution; } } - - public class TimingDistributionGraph : CompositeDrawable - { - /// - /// The number of data points shown on the axis below the graph. - /// - private const float axis_points = 5; - - /// - /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. - /// Without an inset, the final data point will be placed halfway outside the graph. - /// - private const float axis_value_inset = 0.2f; - - private readonly TimingDistribution distribution; - - public TimingDistributionGraph(TimingDistribution distribution) - { - this.distribution = distribution; - } - - [BackgroundDependencyLoader] - private void load() - { - int maxCount = distribution.Bins.Max(); - - var bars = new Drawable[distribution.Bins.Length]; - for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; - - Container axisFlow; - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { bars } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - } - }, - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - }; - - // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. - // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - int sideBins = (distribution.Bins.Length - 1) / 2; - double maxValue = sideBins * distribution.BinSize; - double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "0", - Font = OsuFont.GetFont(size: 12, 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, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: 12, 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: 12, weight: FontWeight.SemiBold) - }); - } - } - - private class Bar : CompositeDrawable - { - public Bar() - { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - - RelativeSizeAxes = Axes.Both; - - Padding = new MarginPadding { Horizontal = 1 }; - - InternalChild = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#66FFCC") - }; - } - } - } } From 900da8849866bc13ddc2e71c4065d24c4ed4a74c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 22:44:55 +0900 Subject: [PATCH 010/170] Populate hit offsets from score processor --- .../Judgements/OsuHitCircleJudgementResult.cs | 23 +++++++ .../Objects/Drawables/DrawableHitCircle.cs | 28 ++++++++- osu.Game.Rulesets.Osu/Scoring/HitOffset.cs | 23 +++++++ .../Scoring/OsuScoreProcessor.cs | 61 ++++++++++++++++++- osu.Game/Scoring/ScoreInfo.cs | 2 + 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs create mode 100644 osu.Game.Rulesets.Osu/Scoring/HitOffset.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs new file mode 100644 index 0000000000..103d02958d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuHitCircleJudgementResult : OsuJudgementResult + { + public HitCircle HitCircle => (HitCircle)HitObject; + + public Vector2? HitPosition; + public float? Radius; + + public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement) + : base(hitObject, judgement) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d73ad888f4..2f86400b25 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -7,8 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osuTK; @@ -32,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; + private InputManager inputManager; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -86,6 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + public override double LifetimeStart { get => base.LifetimeStart; @@ -126,7 +138,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - ApplyResult(r => r.Type = result); + ApplyResult(r => + { + var circleResult = (OsuHitCircleJudgementResult)r; + + if (result != HitResult.Miss) + { + var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + circleResult.HitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); + circleResult.Radius = (float)HitObject.Radius; + } + + circleResult.Type = result; + }); } protected override void UpdateInitialTransforms() @@ -172,6 +196,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => ApproachCircle; + protected override JudgementResult CreateResult(Judgement judgement) => new OsuHitCircleJudgementResult(HitObject, judgement); + public class HitReceptor : CompositeDrawable, IKeyBindingHandler { // IsHovered is used diff --git a/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs b/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs new file mode 100644 index 0000000000..e6a5a01b48 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public class HitOffset + { + public readonly Vector2 Position1; + public readonly Vector2 Position2; + public readonly Vector2 HitPosition; + public readonly float Radius; + + public HitOffset(Vector2 position1, Vector2 position2, Vector2 hitPosition, float radius) + { + Position1 = position1; + Position2 = position2; + HitPosition = hitPosition; + Radius = radius; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a9d48df52d..97be372e37 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -2,11 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -28,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Scoring private const int timing_distribution_centre_bin_index = timing_distribution_bins; private TimingDistribution timingDistribution; + private readonly List hitOffsets = new List(); public override void ApplyBeatmap(IBeatmap beatmap) { @@ -39,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Scoring base.ApplyBeatmap(beatmap); } + private OsuHitCircleJudgementResult lastCircleResult; + protected override void OnResultApplied(JudgementResult result) { base.OnResultApplied(result); @@ -47,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; + + addHitOffset(result); } } @@ -58,17 +67,67 @@ namespace osu.Game.Rulesets.Osu.Scoring { int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; + + removeHitOffset(result); } } + private void addHitOffset(JudgementResult result) + { + if (!(result is OsuHitCircleJudgementResult circleResult)) + return; + + if (lastCircleResult == null) + { + lastCircleResult = circleResult; + return; + } + + if (circleResult.HitPosition != null) + { + Debug.Assert(circleResult.Radius != null); + hitOffsets.Add(new HitOffset(lastCircleResult.HitCircle.StackedEndPosition, circleResult.HitCircle.StackedEndPosition, circleResult.HitPosition.Value, circleResult.Radius.Value)); + } + + lastCircleResult = circleResult; + } + + private void removeHitOffset(JudgementResult result) + { + if (!(result is OsuHitCircleJudgementResult circleResult)) + return; + + if (hitOffsets.Count > 0 && circleResult.HitPosition != null) + hitOffsets.RemoveAt(hitOffsets.Count - 1); + } + protected override void Reset(bool storeResults) { base.Reset(storeResults); timingDistribution.Bins.AsSpan().Clear(); + hitOffsets.Clear(); } - protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); + public override void PopulateScore(ScoreInfo score) + { + base.PopulateScore(score); + + score.ExtraStatistics["timing_distribution"] = timingDistribution; + score.ExtraStatistics["hit_offsets"] = hitOffsets; + } + + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) + { + switch (hitObject) + { + case HitCircle _: + return new OsuHitCircleJudgementResult(hitObject, judgement); + + default: + return new OsuJudgementResult(hitObject, judgement); + } + } public override HitWindows CreateHitWindows() => new OsuHitWindows(); } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7b37c267bc..38b37afc55 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -166,6 +166,8 @@ namespace osu.Game.Scoring } } + public Dictionary ExtraStatistics = new Dictionary(); + [JsonIgnore] public List Files { get; set; } From 89b54be67395cfdaa6ef6b37862e899545e54c72 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 22:45:18 +0900 Subject: [PATCH 011/170] Add initial implementation of the statistics panel --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 14 ++++ osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 10 ++- .../Ranking/TestSceneAccuracyHeatmap.cs | 4 +- .../Ranking/TestScreenStatisticsPanel.cs | 24 +++++++ osu.Game/Rulesets/Ruleset.cs | 3 + .../Ranking/Statistics/StatisticContainer.cs | 70 +++++++++++++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 56 +++++++++++++++ 7 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 689a7b35ea..a76413480d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Osu { @@ -186,5 +188,17 @@ namespace osu.Game.Rulesets.Osu public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + + public override IEnumerable CreateStatistics(ScoreInfo score) => new[] + { + new StatisticContainer("Timing Distribution") + { + Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + }, + new StatisticContainer("Accuracy Heatmap") + { + Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + }, + }; } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 8105d12991..89d861a6d1 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -29,10 +31,13 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; private const float point_size = 4; + private readonly IReadOnlyList offsets; private Container allPoints; - public Heatmap() + public Heatmap(IReadOnlyList offsets) { + this.offsets = offsets; + Size = new Vector2(size); } @@ -122,6 +127,9 @@ namespace osu.Game.Rulesets.Osu.Statistics }); } } + + foreach (var o in offsets) + AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); } public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 9e5fda0ae6..a1b2dccea3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,12 +1,14 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; using osuTK; using osuTK.Graphics; @@ -38,7 +40,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new Heatmap + heatmap = new Heatmap(new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs new file mode 100644 index 0000000000..e61cf9568d --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestScreenStatisticsPanel : OsuTestScene + { + [Test] + public void TestScore() + { + loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + } + + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => + { + Child = new StatisticsPanel(score); + }); + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 4f28607733..5c349ca557 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,6 +23,7 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets { @@ -208,5 +209,7 @@ namespace osu.Game.Rulesets /// /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + + public virtual IEnumerable CreateStatistics(ScoreInfo score) => Enumerable.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs new file mode 100644 index 0000000000..8d10529496 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -0,0 +1,70 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticContainer : Container + { + protected override Container Content => content; + + private readonly Container content; + + public StatisticContainer(string name) + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 9, + Width = 4, + Colour = Color4Extensions.FromHex("#00FFAA") + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = name, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + } + } + } + }, + new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs new file mode 100644 index 0000000000..8ab85527db --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -0,0 +1,56 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticsPanel : CompositeDrawable + { + public StatisticsPanel(ScoreInfo score) + { + // Todo: Not correct. + RelativeSizeAxes = Axes.Both; + + Container statistics; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + new ScorePanel(score) // Todo: Temporary + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + State = PanelState.Expanded, + X = 30 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, + Right = 50 + }, + Child = statistics = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(30, 15), + } + } + }; + + foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) + statistics.Add(s); + } + } +} From a65c1a9abdb358f69065e72b1ebe00ab2e1ee95a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 16:08:41 +0900 Subject: [PATCH 012/170] Fix test name --- ...TestScreenStatisticsPanel.cs => TestSceneStatisticsPanel.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Ranking/{TestScreenStatisticsPanel.cs => TestSceneStatisticsPanel.cs} (91%) diff --git a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs similarity index 91% rename from osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index e61cf9568d..22ee6077cb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Tests.Visual.Ranking { - public class TestScreenStatisticsPanel : OsuTestScene + public class TestSceneStatisticsPanel : OsuTestScene { [Test] public void TestScore() From 9ea7c3dc90474e56bdf07c30988f1cd5007ef919 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 16:31:02 +0900 Subject: [PATCH 013/170] Make heatmap support dynamic sizing --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 155 +++++++++++------- .../Ranking/TestSceneAccuracyHeatmap.cs | 16 +- 2 files changed, 108 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 89d861a6d1..7e140e6fd2 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -18,12 +19,7 @@ namespace osu.Game.Rulesets.Osu.Statistics public class Heatmap : CompositeDrawable { /// - /// Full size of the heatmap. - /// - private const float size = 130; - - /// - /// Size of the inner circle containing the "hit" points, relative to . + /// Size of the inner circle containing the "hit" points, relative to the size of this . /// All other points outside of the inner circle are "miss" points. /// private const float inner_portion = 0.8f; @@ -34,77 +30,106 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly IReadOnlyList offsets; private Container allPoints; + private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); + public Heatmap(IReadOnlyList offsets) { this.offsets = offsets; - Size = new Vector2(size); + AddLayout(sizeLayout); } [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChild = new Container { - new CircularContainer + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(inner_portion), - Masking = true, - BorderThickness = 2f, - BorderColour = Color4.White, - Child = new Box + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#202624") - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 10, - Height = 2f, - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = -1, - Width = 2f, - Height = 10, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } } + }, + allPoints = new Container + { + RelativeSizeAxes = Axes.Both } - }, - allPoints = new Container { RelativeSizeAxes = Axes.Both } + } }; + } + + protected override void Update() + { + base.Update(); + validateHitPoints(); + } + + private void validateHitPoints() + { + if (sizeLayout.IsValid) + return; + + allPoints.Clear(); + + // Since the content is fit, both dimensions should have the same size. + float size = allPoints.DrawSize.X; Vector2 centre = new Vector2(size / 2); int rows = (int)Math.Ceiling(size / point_size); @@ -130,16 +155,24 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var o in offsets) AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + + sizeLayout.Validate(); } - public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { + if (allPoints.Count == 0) + return; + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + // Since the content is fit, both dimensions should have the same size. + float size = allPoints.DrawSize.X; + // Find the most relevant hit point. double minDist = double.PositiveInfinity; HitPoint point = null; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index a1b2dccea3..53c8e56f53 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Ranking private readonly Box background; private readonly Drawable object1; private readonly Drawable object2; - private readonly Heatmap heatmap; + private readonly TestHeatmap heatmap; public TestSceneAccuracyHeatmap() { @@ -40,10 +40,11 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new Heatmap(new List()) + heatmap = new TestHeatmap(new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Size = new Vector2(130) } }; } @@ -70,6 +71,17 @@ namespace osu.Game.Tests.Visual.Ranking return true; } + private class TestHeatmap : Heatmap + { + public TestHeatmap(IReadOnlyList offsets) + : base(offsets) + { + } + + public new void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + => base.AddPoint(start, end, hitPoint, radius); + } + private class BorderCircle : CircularContainer { public BorderCircle() From 3dbe164b2c4da72b25c26d25156bc5a8de3ce28b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:20:38 +0900 Subject: [PATCH 014/170] Add some very basic safety checks around non-existent data --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 7 +++++-- .../Statistics/TimingDistributionGraph.cs | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 7e140e6fd2..51508a5e8b 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -153,8 +153,11 @@ namespace osu.Game.Rulesets.Osu.Statistics } } - foreach (var o in offsets) - AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + if (offsets?.Count > 0) + { + foreach (var o in offsets) + AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + } sizeLayout.Validate(); } diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index a47d726988..0ba94b7101 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Statistics [BackgroundDependencyLoader] private void load() { + if (distribution?.Bins == null || distribution.Bins.Length == 0) + return; + int maxCount = distribution.Bins.Max(); var bars = new Drawable[distribution.Bins.Length]; From 076eac2362480a80749a0b42d1caaf7295f25ae3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:46:34 +0900 Subject: [PATCH 015/170] Inset entire graph rather than just the axis --- .../Statistics/TimingDistributionGraph.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 0ba94b7101..1f9f38bf3b 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Osu.Statistics /// private const float axis_points = 5; - /// - /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. - /// Without an inset, the final data point will be placed halfway outside the graph. - /// - private const float axis_value_inset = 0.2f; - private readonly TimingDistribution distribution; public TimingDistributionGraph(TimingDistribution distribution) @@ -50,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Statistics InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, + Width = 0.8f, Content = new[] { new Drawable[] @@ -80,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. int sideBins = (distribution.Bins.Length - 1) / 2; double maxValue = sideBins * distribution.BinSize; - double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText { From 9442fc00ac408c314ca078bbfa5d3c426adf6aa2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:48:59 +0900 Subject: [PATCH 016/170] Temporary hack to make replay player populate scores --- osu.Game/Screens/Play/ReplayPlayer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index b443603128..d7580ea271 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -27,11 +27,14 @@ namespace osu.Game.Screens.Play protected override void GotoRanking() { - this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); + this.Push(CreateResults(CreateScore())); } protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); - protected override ScoreInfo CreateScore() => score.ScoreInfo; + // protected override ScoreInfo CreateScore() + // { + // return score.ScoreInfo; + // } } } From a2ddb4edb456ed7b0c78ff4af1f67e4e3a312289 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:28 +0900 Subject: [PATCH 017/170] Change interface for creating statistic rows --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 37 +++++++++++++++---- osu.Game/Rulesets/Ruleset.cs | 2 +- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticRow.cs | 15 ++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 16 ++++++-- 5 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticRow.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a76413480d..67a9bda1a9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -189,16 +190,36 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override IEnumerable CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] { - new StatisticContainer("Timing Distribution") + new StatisticRow { - Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) - }, - new StatisticContainer("Accuracy Heatmap") - { - Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) - }, + Content = new Drawable[] + { + new StatisticContainer("Timing Distribution") + { + RelativeSizeAxes = Axes.X, + Height = 130, + Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + { + RelativeSizeAxes = Axes.Both + } + }, + new StatisticContainer("Accuracy Heatmap") + { + RelativeSizeAxes = Axes.Both, + Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + { + RelativeSizeAxes = Axes.Both + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 130), + } + } }; } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 5c349ca557..f05685b6e9 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,6 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; - public virtual IEnumerable CreateStatistics(ScoreInfo score) => Enumerable.Empty(); + public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index 8d10529496..d7b42c1c2f 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Ranking.Statistics { InternalChild = new GridContainer { - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs new file mode 100644 index 0000000000..5d39ef57b2 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -0,0 +1,15 @@ +// 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; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticRow + { + public Drawable[] Content = Array.Empty(); + public Dimension[] ColumnDimensions = Array.Empty(); + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8ab85527db..6c5fa1837a 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Ranking.Statistics // Todo: Not correct. RelativeSizeAxes = Axes.Both; - Container statistics; + FillFlowContainer statisticRows; InternalChildren = new Drawable[] { @@ -41,16 +41,26 @@ namespace osu.Game.Screens.Ranking.Statistics Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, Right = 50 }, - Child = statistics = new FillFlowContainer + Child = statisticRows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), } } }; foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) - statistics.Add(s); + { + statisticRows.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { s.Content }, + ColumnDimensions = s.ColumnDimensions, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } } } } From 808e216059e00b52cf7c02a62ea1ca94bd5aaccd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:37 +0900 Subject: [PATCH 018/170] Improve test scene --- .../Visual/Ranking/TestSceneStatisticsPanel.cs | 13 ++++++++++++- .../Ranking/TestSceneTimingDistributionGraph.cs | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 22ee6077cb..c02be9ab5d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -1,8 +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.Collections.Generic; using NUnit.Framework; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -13,7 +15,16 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScore() { - loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + ExtraStatistics = + { + ["timing_distribution"] = TestSceneTimingDistributionGraph.CreateNormalDistribution(), + ["hit_offsets"] = new List() + } + }; + + loadPanel(score); } private void loadPanel(ScoreInfo score) => AddStep("load panel", () => diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 7530fc42b8..2249655093 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(createNormalDistribution()) + new TimingDistributionGraph(CreateNormalDistribution()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Ranking }; } - private TimingDistribution createNormalDistribution() + public static TimingDistribution CreateNormalDistribution() { var distribution = new TimingDistribution(51, 5); From e7687a09271d12f5cadf93b00b7ff798733d9340 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:43 +0900 Subject: [PATCH 019/170] Temporary placement inside results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index fbb9b95478..4d589b4527 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Online.API; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking.Statistics; using osuTK; namespace osu.Game.Screens.Ranking @@ -134,6 +135,11 @@ namespace osu.Game.Screens.Ranking }, }); } + + AddInternal(new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.Both + }); } protected override void LoadComplete() From 1cf16038a708f1c586b6405b58bb3d70082cf2c5 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:54:05 +0100 Subject: [PATCH 020/170] Create IApplicableToSample --- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToSample.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs new file mode 100644 index 0000000000..559d127cfc --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs @@ -0,0 +1,15 @@ +// 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.Audio.Sample; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make adjustments to a sample. + /// + public interface IApplicableToSample : IApplicableMod + { + void ApplyToSample(SampleChannel sample); + } +} From 9f4f3ce2cc055867cbfe58f723c334f7b08b1448 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:54:50 +0100 Subject: [PATCH 021/170] Handle IApplicableToSample mods --- .../Storyboards/Drawables/DrawableStoryboardSample.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8292b02068..2b9c66d2e6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -1,11 +1,14 @@ // 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 osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Storyboards.Drawables { @@ -28,12 +31,17 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, IBindable> mods) { channel = beatmap.Value.Skin.GetSample(sampleInfo); if (channel != null) + { channel.Volume.Value = sampleInfo.Volume / 100.0; + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToSample(channel); + } } protected override void Update() From 4138f6119f24ce50991a64c89cd91e4c5fa28a23 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:58:23 +0100 Subject: [PATCH 022/170] Update rate adjust mods to also use IApplicableToSample --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 8 +++++++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index cb2ff149f1..ecd625c3b4 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToTrack + public abstract class ModRateAdjust : Mod, IApplicableToTrack, IApplicableToSample { public abstract BindableNumber SpeedChange { get; } @@ -16,6 +17,11 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } + public virtual void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index df059eef7d..352e3ae915 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -10,10 +10,11 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; +using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack, IApplicableToSample { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -58,6 +59,11 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } + public void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public virtual void ApplyToBeatmap(IBeatmap beatmap) { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); From 5e74985edafa034306c4559d32e99aa495697d80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jun 2020 19:28:40 +0900 Subject: [PATCH 023/170] Add scrolling capability to results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 61 +++++++++++++++-------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4d589b4527..11ed9fb5b7 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Ranking public abstract class ResultsScreen : OsuScreen { protected const float BACKGROUND_BLUR = 20; + private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -68,10 +69,24 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList + Child = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - SelectedScore = { BindTarget = SelectedScore } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + panels = new ScorePanelList + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + SelectedScore = { BindTarget = SelectedScore } + }, + new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + } + } } } }, @@ -135,11 +150,6 @@ namespace osu.Game.Screens.Ranking }, }); } - - AddInternal(new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.Both - }); } protected override void LoadComplete() @@ -180,27 +190,38 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } + [Cached] private class ResultsScrollContainer : OsuScrollContainer { - private readonly Container content; - - protected override Container Content => content; - public ResultsScrollContainer() { - base.Content.Add(content = new Container - { - RelativeSizeAxes = Axes.X - }); - RelativeSizeAxes = Axes.Both; ScrollbarVisible = false; } - protected override void Update() + protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { - base.Update(); - content.Height = Math.Max(768 - TwoLayerButton.SIZE_EXTENDED.Y, DrawHeight); + if (!animated) + { + // If the user is scrolling via mouse drag, follow the mouse 1:1. + base.OnUserScroll(value, false, distanceDecay); + } + else + { + float direction = Math.Sign(value - Target); + float target = Target + direction * screen_height; + + if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) + { + // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. + base.OnUserScroll(value, true, distanceDecay); + } + else + { + // Otherwise, scroll one screen in the target direction. + base.OnUserScroll(target, true, distanceDecay); + } + } } } } From c3e268616fdd1b44cc146f0a07b7e05cd788866f Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 17 Jun 2020 11:43:32 +0100 Subject: [PATCH 024/170] Implement grouping interface IApplicableToAudio --- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 10 ++++++++++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToAudio.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs new file mode 100644 index 0000000000..40e13764c6 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace osu.Game.Rulesets.Mods +{ + public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample + { + } +} diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ecd625c3b4..874384686f 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -8,7 +8,7 @@ using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToTrack, IApplicableToSample + public abstract class ModRateAdjust : Mod, IApplicableToAudio { public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 352e3ae915..cbd07efa97 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -14,7 +14,7 @@ using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack, IApplicableToSample + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToAudio { /// /// The point in the beatmap at which the final ramping rate should be reached. From 725b2e540bdfd86d836c6345f6e6ea1daead0865 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jun 2020 22:29:00 +0900 Subject: [PATCH 025/170] wip --- .../Visual/Ranking/TestSceneScorePanel.cs | 38 ++++++++ osu.Game/Screens/Ranking/ResultsScreen.cs | 89 +++++++++++++------ osu.Game/Screens/Ranking/ScorePanel.cs | 60 +++++++++++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 59 +++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 7 -- 5 files changed, 205 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..1c5087ee94 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -101,6 +102,39 @@ namespace osu.Game.Tests.Visual.Ranking AddWaitStep("wait for transition", 10); } + [Test] + public void TestSceneTrackingScorePanel() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + + addPanelStep(score, PanelState.Contracted); + + AddStep("enable tracking", () => + { + panel.Anchor = Anchor.CentreLeft; + panel.Origin = Anchor.CentreLeft; + panel.Tracking = true; + + Add(panel.CreateTrackingComponent().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); + }); + + assertTracking(true); + + AddStep("expand panel", () => panel.State = PanelState.Expanded); + AddWaitStep("wait for transition", 2); + assertTracking(true); + + AddStep("stop tracking", () => panel.Tracking = false); + assertTracking(false); + + AddStep("start tracking", () => panel.Tracking = true); + assertTracking(true); + } + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = panel = new ScorePanel(score) @@ -110,5 +144,9 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); + + private void assertTracking(bool tracking) => AddAssert($"{(tracking ? "is" : "is not")} tracking", () => + Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.TopLeft, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.TopLeft) == tracking + && Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.BottomRight, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.BottomRight) == tracking); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 11ed9fb5b7..4ef012f6f2 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -10,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -44,6 +46,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } + private Container scorePanelContainer; + private ResultsScrollContainer scrollContainer; + private Container expandedPanelProxyContainer; private Drawable bottomPanel; private ScorePanelList panels; @@ -58,6 +63,13 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { + scorePanelContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }; + FillFlowContainer buttons; InternalChild = new GridContainer @@ -67,26 +79,35 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - new ResultsScrollContainer + new Container { - Child = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + scorePanelContainer, + scrollContainer = new ResultsScrollContainer { - panels = new ScorePanelList + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, - Height = screen_height, - SelectedScore = { BindTarget = SelectedScore } - }, - new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.X, - Height = screen_height, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + panels = new ScorePanelList(scorePanelContainer) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + SelectedScore = { BindTarget = SelectedScore } + }, + new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + } + } } - } + }, + expandedPanelProxyContainer = new Container { RelativeSizeAxes = Axes.Both } } } }, @@ -173,6 +194,21 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + ScorePanel expandedPanel = scorePanelContainer.Single(p => p.State == PanelState.Expanded); + expandedPanel.Tracking = false; + expandedPanel.Anchor = Anchor.Centre; + expandedPanel.Origin = Anchor.Centre; + + scorePanelContainer.X = (float)Interpolation.Lerp(0, -DrawWidth / 2 + ScorePanel.EXPANDED_WIDTH / 2f, Math.Clamp(scrollContainer.Current / (screen_height * 0.8f), 0, 1)); + + if (expandedPanelProxyContainer.Count == 0) + expandedPanelProxyContainer.Add(expandedPanel.CreateProxy()); + } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -205,22 +241,21 @@ namespace osu.Game.Screens.Ranking { // If the user is scrolling via mouse drag, follow the mouse 1:1. base.OnUserScroll(value, false, distanceDecay); + return; + } + + float direction = Math.Sign(value - Target); + float target = Target + direction * screen_height; + + if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) + { + // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. + base.OnUserScroll(value, true, distanceDecay); } else { - float direction = Math.Sign(value - Target); - float target = Target + direction * screen_height; - - if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) - { - // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. - base.OnUserScroll(value, true, distanceDecay); - } - else - { - // Otherwise, scroll one screen in the target direction. - base.OnUserScroll(target, true, distanceDecay); - } + // Otherwise, scroll one screen in the target direction. + base.OnUserScroll(target, true, distanceDecay); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 65fb901c89..7ca96a9a58 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -182,6 +182,40 @@ namespace osu.Game.Screens.Ranking } } + private bool tracking; + private Vector2 lastNonTrackingPosition; + + /// + /// Whether this should track the position of the tracking component created via . + /// + public bool Tracking + { + get => tracking; + set + { + if (tracking == value) + return; + + tracking = value; + + if (tracking) + lastNonTrackingPosition = Position; + else + Position = lastNonTrackingPosition; + } + } + + protected override void Update() + { + base.Update(); + + if (Tracking && trackingComponent != null) + { + Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); + Position = topLeftPos - AnchorPosition + OriginPosition; + } + } + private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -248,5 +282,31 @@ namespace osu.Game.Screens.Ranking => base.ReceivePositionalInputAt(screenSpacePos) || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); + + private TrackingComponent trackingComponent; + + public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this); + + public class TrackingComponent : Drawable + { + public readonly ScorePanel Panel; + + public TrackingComponent(ScorePanel panel) + { + Panel = panel; + } + + protected override void Update() + { + base.Update(); + Size = Panel.DrawSize; + } + + // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. + // So by forwarding input events, we remove the need to consider the order in which input is handled. + protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 1142297274..d49085bc96 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -27,11 +28,20 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); + private readonly Container panels; private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; - public ScorePanelList() + /// + /// Creates a new . + /// + /// The target container in which s should reside. + /// s are set to track by default, but this allows + /// This should be placed _before_ the in the hierarchy. + /// + /// + public ScorePanelList(Container panelTarget = null) { RelativeSizeAxes = Axes.Both; @@ -47,6 +57,18 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + + if (panelTarget == null) + { + // To prevent 1-frame sizing issues, the panel container is added _before_ the scroll + flow containers + AddInternal(panels = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = 1 + }); + } + else + panels = panelTarget; } protected override void LoadComplete() @@ -62,10 +84,9 @@ namespace osu.Game.Screens.Ranking /// The to add. public void AddScore(ScoreInfo score) { - flow.Add(new ScorePanel(score) + var panel = new ScorePanel(score) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Tracking = true }.With(p => { p.StateChanged += s => @@ -73,6 +94,13 @@ namespace osu.Game.Screens.Ranking if (s == PanelState.Expanded) SelectedScore.Value = p.Score; }; + }); + + panels.Add(panel); + flow.Add(panel.CreateTrackingComponent().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; })); if (SelectedScore.Value == score) @@ -99,14 +127,15 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score == score.OldValue)) + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) { - p.State = PanelState.Contracted; - p.Margin = new MarginPadding(); + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); } // Find the panel corresponding to the new score. - expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue); + expandedPanel = expandedTrackingComponent?.Panel; // handle horizontal scroll only when not hovering the expanded panel. scroll.HandleScroll = () => expandedPanel?.IsHovered != true; @@ -114,9 +143,11 @@ namespace osu.Game.Screens.Ranking if (expandedPanel == null) return; + Debug.Assert(expandedTrackingComponent != null); + // Expand the new panel. + expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; // Scroll to the new panel. This is done manually since we need: // 1) To scroll after the scroll container's visible range is updated. @@ -145,15 +176,15 @@ namespace osu.Game.Screens.Ranking flow.Padding = new MarginPadding { Horizontal = offset }; } - private class Flow : FillFlowContainer + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count(); + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Score.TotalScore) - .ThenBy(s => s.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Panel.Score.TotalScore) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 6c5fa1837a..bae6d0ffbb 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -26,13 +26,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new ScorePanel(score) // Todo: Temporary - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - State = PanelState.Expanded, - X = 30 - }, new Container { RelativeSizeAxes = Axes.Both, From bed5e857df7d2af44ae5f4bbfa304f04be741da1 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 17 Jun 2020 14:49:55 +0100 Subject: [PATCH 026/170] Add missing license header and remove unused usings --- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs index 40e13764c6..901da7af55 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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.Rulesets.Mods { From 69d85ca3aeab18758ad644e1592d99a83fe35506 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 13:20:16 +0900 Subject: [PATCH 027/170] Add more cards to results screen test --- .../Visual/Ranking/TestSceneResultsScreen.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 125aa0a1e7..ea33aa62e3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -1,6 +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 System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -8,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens; @@ -113,6 +116,22 @@ namespace osu.Game.Tests.Visual.Ranking RetryOverlay = InternalChildren.OfType().SingleOrDefault(); } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + var scores = new List(); + + for (int i = 0; i < 20; i++) + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + score.TotalScore += 10 - i; + scores.Add(score); + } + + scoresCallback?.Invoke(scores); + + return null; + } } private class UnrankedSoloResultsScreen : SoloResultsScreen From c31a05977d7b62a81e146de50ab374c8e2cca0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 16:50:45 +0900 Subject: [PATCH 028/170] Re-implement statistics as a click-in panel --- osu.Game/Screens/Ranking/ResultsScreen.cs | 109 +++++++----------- osu.Game/Screens/Ranking/ScorePanel.cs | 74 ++++++------ osu.Game/Screens/Ranking/ScorePanelList.cs | 67 ++++++----- .../Ranking/Statistics/StatisticsPanel.cs | 34 +++--- 4 files changed, 135 insertions(+), 149 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4ef012f6f2..927628a811 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -11,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -46,11 +44,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } - private Container scorePanelContainer; - private ResultsScrollContainer scrollContainer; - private Container expandedPanelProxyContainer; + private StatisticsPanel statisticsPanel; private Drawable bottomPanel; - private ScorePanelList panels; + private ScorePanelList scorePanelList; protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -63,13 +59,6 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - scorePanelContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }; - FillFlowContainer buttons; InternalChild = new GridContainer @@ -84,30 +73,26 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - scorePanelContainer, - scrollContainer = new ResultsScrollContainer + new OsuScrollContainer { - Child = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new Container { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + Height = screen_height, Children = new Drawable[] { - panels = new ScorePanelList(scorePanelContainer) + scorePanelList = new ScorePanelList { - RelativeSizeAxes = Axes.X, - Height = screen_height, - SelectedScore = { BindTarget = SelectedScore } + RelativeSizeAxes = Axes.Both, + SelectedScore = { BindTarget = SelectedScore }, + PostExpandAction = onExpandedPanelClicked }, - new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.X, - Height = screen_height, - } + statisticsPanel = new StatisticsPanel(Score) { RelativeSizeAxes = Axes.Both } } } }, - expandedPanelProxyContainer = new Container { RelativeSizeAxes = Axes.Both } } } }, @@ -155,7 +140,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - panels.AddScore(Score); + scorePanelList.AddScore(Score); if (player != null && allowRetry) { @@ -180,7 +165,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - panels.AddScore(s); + scorePanelList.AddScore(s); })); if (req != null) @@ -194,21 +179,6 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - ScorePanel expandedPanel = scorePanelContainer.Single(p => p.State == PanelState.Expanded); - expandedPanel.Tracking = false; - expandedPanel.Anchor = Anchor.Centre; - expandedPanel.Origin = Anchor.Centre; - - scorePanelContainer.X = (float)Interpolation.Lerp(0, -DrawWidth / 2 + ScorePanel.EXPANDED_WIDTH / 2f, Math.Clamp(scrollContainer.Current / (screen_height * 0.8f), 0, 1)); - - if (expandedPanelProxyContainer.Count == 0) - expandedPanelProxyContainer.Add(expandedPanel.CreateProxy()); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -226,36 +196,39 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } - [Cached] - private class ResultsScrollContainer : OsuScrollContainer + private void onExpandedPanelClicked() { - public ResultsScrollContainer() + statisticsPanel.ToggleVisibility(); + + if (statisticsPanel.State.Value == Visibility.Hidden) { - RelativeSizeAxes = Axes.Both; - ScrollbarVisible = false; + foreach (var panel in scorePanelList.Panels) + { + if (panel.State == PanelState.Contracted) + panel.FadeIn(150); + else + { + panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p => + { + scorePanelList.HandleScroll = true; + p.Tracking = true; + }); + } + } } - - protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) + else { - if (!animated) + foreach (var panel in scorePanelList.Panels) { - // If the user is scrolling via mouse drag, follow the mouse 1:1. - base.OnUserScroll(value, false, distanceDecay); - return; - } + if (panel.State == PanelState.Contracted) + panel.FadeOut(150, Easing.OutQuint); + else + { + scorePanelList.HandleScroll = false; - float direction = Math.Sign(value - Target); - float target = Target + direction * screen_height; - - if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) - { - // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. - base.OnUserScroll(value, true, distanceDecay); - } - else - { - // Otherwise, scroll one screen in the target direction. - base.OnUserScroll(target, true, distanceDecay); + panel.Tracking = false; + panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); + } } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 7ca96a9a58..31b2796c13 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -76,6 +76,18 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; + public Action PostExpandAction; + + /// + /// Whether this should track the position of the tracking component created via . + /// + public bool Tracking; + + /// + /// Whether this can enter into an state. + /// + public bool CanExpand = true; + public readonly ScoreInfo Score; private Container content; @@ -182,38 +194,18 @@ namespace osu.Game.Screens.Ranking } } - private bool tracking; - private Vector2 lastNonTrackingPosition; - - /// - /// Whether this should track the position of the tracking component created via . - /// - public bool Tracking - { - get => tracking; - set - { - if (tracking == value) - return; - - tracking = value; - - if (tracking) - lastNonTrackingPosition = Position; - else - Position = lastNonTrackingPosition; - } - } - protected override void Update() { base.Update(); if (Tracking && trackingComponent != null) - { - Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); - Position = topLeftPos - AnchorPosition + OriginPosition; - } + Position = GetTrackingPosition(); + } + + public Vector2 GetTrackingPosition() + { + Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); + return topLeftPos - AnchorPosition + OriginPosition; } private void updateState() @@ -270,10 +262,28 @@ namespace osu.Game.Screens.Ranking } } + public override Vector2 Size + { + get => base.Size; + set + { + base.Size = value; + + if (trackingComponent != null) + trackingComponent.Size = value; + } + } + protected override bool OnClick(ClickEvent e) { if (State == PanelState.Contracted) - State = PanelState.Expanded; + { + if (CanExpand) + State = PanelState.Expanded; + return true; + } + + PostExpandAction?.Invoke(); return true; } @@ -296,17 +306,13 @@ namespace osu.Game.Screens.Ranking Panel = panel; } - protected override void Update() - { - base.Update(); - Size = Panel.DrawSize; - } - // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. // So by forwarding input events, we remove the need to consider the order in which input is handled. protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); + + public override bool IsPresent => Panel.IsPresent; } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d49085bc96..e332f462bb 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,9 +26,15 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public Action PostExpandAction; + public readonly Bindable SelectedScore = new Bindable(); + public float CurrentScrollPosition => scroll.Current; + + public IReadOnlyList Panels => panels; private readonly Container panels; + private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -36,39 +42,27 @@ namespace osu.Game.Screens.Ranking /// /// Creates a new . /// - /// The target container in which s should reside. - /// s are set to track by default, but this allows - /// This should be placed _before_ the in the hierarchy. - /// - /// - public ScorePanelList(Container panelTarget = null) + public ScorePanelList() { RelativeSizeAxes = Axes.Both; InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, - Child = flow = new Flow + HandleScroll = () => HandleScroll && expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(panel_spacing, 0), - AutoSizeAxes = Axes.Both, + panels = new Container { RelativeSizeAxes = Axes.Both }, + flow = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, + }, } }; - - if (panelTarget == null) - { - // To prevent 1-frame sizing issues, the panel container is added _before_ the scroll + flow containers - AddInternal(panels = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = 1 - }); - } - else - panels = panelTarget; } protected override void LoadComplete() @@ -78,6 +72,25 @@ namespace osu.Game.Screens.Ranking SelectedScore.BindValueChanged(selectedScoreChanged, true); } + private bool handleScroll = true; + + public bool HandleScroll + { + get => handleScroll; + set + { + handleScroll = value; + + foreach (var p in panels) + p.CanExpand = value; + + scroll.ScrollbarVisible = value; + + if (!value) + scroll.ScrollTo(CurrentScrollPosition, false); + } + } + /// /// Adds a to this list. /// @@ -86,7 +99,8 @@ namespace osu.Game.Screens.Ranking { var panel = new ScorePanel(score) { - Tracking = true + Tracking = true, + PostExpandAction = () => PostExpandAction?.Invoke() }.With(p => { p.StateChanged += s => @@ -137,9 +151,6 @@ namespace osu.Game.Screens.Ranking var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue); expandedPanel = expandedTrackingComponent?.Panel; - // handle horizontal scroll only when not hovering the expanded panel. - scroll.HandleScroll = () => expandedPanel?.IsHovered != true; - if (expandedPanel == null) return; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index bae6d0ffbb..cc9007f527 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,17 +1,17 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - public class StatisticsPanel : CompositeDrawable + public class StatisticsPanel : VisibilityContainer { + protected override bool StartHidden => true; + public StatisticsPanel(ScoreInfo score) { // Todo: Not correct. @@ -19,27 +19,19 @@ namespace osu.Game.Screens.Ranking.Statistics FillFlowContainer statisticRows; - InternalChildren = new Drawable[] + InternalChild = new Container { - new Box + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#333") + Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, + Right = 50 }, - new Container + Child = statisticRows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, - Right = 50 - }, - Child = statisticRows = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - } + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 15), } }; @@ -55,5 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics }); } } + + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() => this.FadeOut(); } } From 6c8a24260bd4edd511b9284db59fe70e12f97347 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 17:06:05 +0900 Subject: [PATCH 029/170] Add padding --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 927628a811..4a7cb6679a 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -227,7 +227,7 @@ namespace osu.Game.Screens.Ranking scorePanelList.HandleScroll = false; panel.Tracking = false; - panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); + panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cc9007f527..733c855426 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -10,6 +10,8 @@ namespace osu.Game.Screens.Ranking.Statistics { public class StatisticsPanel : VisibilityContainer { + public const float SIDE_PADDING = 30; + protected override bool StartHidden => true; public StatisticsPanel(ScoreInfo score) @@ -24,8 +26,10 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, - Right = 50 + Left = ScorePanel.EXPANDED_WIDTH + SIDE_PADDING * 3, + Right = SIDE_PADDING, + Top = SIDE_PADDING, + Bottom = 50 // Approximate padding to the bottom of the score panel. }, Child = statisticRows = new FillFlowContainer { From 20db5b33abc952390f58a9110266f55f5377fc51 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:11:03 +0900 Subject: [PATCH 030/170] Rework score processor to provide more generic events --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +- .../Scoring/OsuScoreProcessor.cs | 128 +++++++----------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 24 +++- .../Statistics/TimingDistributionGraph.cs | 45 ++++-- .../Ranking/TestSceneAccuracyHeatmap.cs | 9 +- .../Ranking/TestSceneStatisticsPanel.cs | 9 +- .../TestSceneTimingDistributionGraph.cs | 34 ++--- osu.Game/Scoring/ScoreInfo.cs | 4 +- 8 files changed, 126 insertions(+), 132 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 67a9bda1a9..c7003deed2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -200,7 +201,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + Child = new TimingDistributionGraph(score.HitEvents.Cast().ToList()) { RelativeSizeAxes = Axes.Both } @@ -208,7 +209,7 @@ namespace osu.Game.Rulesets.Osu new StatisticContainer("Accuracy Heatmap") { RelativeSizeAxes = Axes.Both, - Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + Child = new Heatmap(score.Beatmap, score.HitEvents.Cast().ToList()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 97be372e37..9694367210 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,120 +1,52 @@ // 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 osu.Game.Beatmaps; +using System.Linq; +using JetBrains.Annotations; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - /// - /// The number of bins on each side of the timing distribution. - /// - private const int timing_distribution_bins = 25; - - /// - /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. - /// - private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; - - /// - /// The centre bin, with a timing distribution very close to/at 0. - /// - private const int timing_distribution_centre_bin_index = timing_distribution_bins; - - private TimingDistribution timingDistribution; - private readonly List hitOffsets = new List(); - - public override void ApplyBeatmap(IBeatmap beatmap) - { - var hitWindows = CreateHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); - - timingDistribution = new TimingDistribution(total_timing_distribution_bins, hitWindows.WindowFor(hitWindows.LowestSuccessfulHitResult()) / timing_distribution_bins); - - base.ApplyBeatmap(beatmap); - } - - private OsuHitCircleJudgementResult lastCircleResult; + private readonly List hitEvents = new List(); + private HitObject lastHitObject; protected override void OnResultApplied(JudgementResult result) { base.OnResultApplied(result); - if (result.IsHit) - { - int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); - timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; - - addHitOffset(result); - } + hitEvents.Add(new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, (result as OsuHitCircleJudgementResult)?.HitPosition)); + lastHitObject = result.HitObject; } protected override void OnResultReverted(JudgementResult result) { base.OnResultReverted(result); - if (result.IsHit) - { - int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); - timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; - - removeHitOffset(result); - } - } - - private void addHitOffset(JudgementResult result) - { - if (!(result is OsuHitCircleJudgementResult circleResult)) - return; - - if (lastCircleResult == null) - { - lastCircleResult = circleResult; - return; - } - - if (circleResult.HitPosition != null) - { - Debug.Assert(circleResult.Radius != null); - hitOffsets.Add(new HitOffset(lastCircleResult.HitCircle.StackedEndPosition, circleResult.HitCircle.StackedEndPosition, circleResult.HitPosition.Value, circleResult.Radius.Value)); - } - - lastCircleResult = circleResult; - } - - private void removeHitOffset(JudgementResult result) - { - if (!(result is OsuHitCircleJudgementResult circleResult)) - return; - - if (hitOffsets.Count > 0 && circleResult.HitPosition != null) - hitOffsets.RemoveAt(hitOffsets.Count - 1); + hitEvents.RemoveAt(hitEvents.Count - 1); } protected override void Reset(bool storeResults) { base.Reset(storeResults); - timingDistribution.Bins.AsSpan().Clear(); - hitOffsets.Clear(); + hitEvents.Clear(); + lastHitObject = null; } public override void PopulateScore(ScoreInfo score) { base.PopulateScore(score); - score.ExtraStatistics["timing_distribution"] = timingDistribution; - score.ExtraStatistics["hit_offsets"] = hitOffsets; + score.HitEvents.AddRange(hitEvents.Select(e => e).Cast()); } protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) @@ -131,4 +63,42 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } + + public readonly struct HitEvent + { + /// + /// The time offset from the end of at which the event occurred. + /// + public readonly double TimeOffset; + + /// + /// The hit result. + /// + public readonly HitResult Result; + + /// + /// The on which the result occurred. + /// + public readonly HitObject HitObject; + + /// + /// The occurring prior to . + /// + [CanBeNull] + public readonly HitObject LastHitObject; + + /// + /// The player's cursor position, if available, at the time of the event. + /// + public readonly Vector2? CursorPosition; + + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? cursorPosition) + { + TimeOffset = timeOffset; + Result = result; + HitObject = hitObject; + LastHitObject = lastHitObject; + CursorPosition = cursorPosition; + } + } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 51508a5e8b..95cfc5b768 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -27,14 +29,16 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; private const float point_size = 4; - private readonly IReadOnlyList offsets; private Container allPoints; + private readonly BeatmapInfo beatmap; + private readonly IReadOnlyList hitEvents; private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); - public Heatmap(IReadOnlyList offsets) + public Heatmap(BeatmapInfo beatmap, IReadOnlyList hitEvents) { - this.offsets = offsets; + this.beatmap = beatmap; + this.hitEvents = hitEvents; AddLayout(sizeLayout); } @@ -153,10 +157,18 @@ namespace osu.Game.Rulesets.Osu.Statistics } } - if (offsets?.Count > 0) + if (hitEvents.Count > 0) { - foreach (var o in offsets) - AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + // Todo: This should probably not be done like this. + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + + foreach (var e in hitEvents) + { + if (e.LastHitObject == null || e.CursorPosition == null) + continue; + + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); + } } sizeLayout.Validate(); diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 1f9f38bf3b..b319cc5aa9 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -1,6 +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 System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -15,29 +17,52 @@ namespace osu.Game.Rulesets.Osu.Statistics { public class TimingDistributionGraph : CompositeDrawable { + /// + /// The number of bins on each side of the timing distribution. + /// + private const int timing_distribution_bins = 25; + + /// + /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. + /// + private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; + + /// + /// The centre bin, with a timing distribution very close to/at 0. + /// + private const int timing_distribution_centre_bin_index = timing_distribution_bins; + /// /// The number of data points shown on the axis below the graph. /// private const float axis_points = 5; - private readonly TimingDistribution distribution; + private readonly List hitEvents; - public TimingDistributionGraph(TimingDistribution distribution) + public TimingDistributionGraph(List hitEvents) { - this.distribution = distribution; + this.hitEvents = hitEvents; } [BackgroundDependencyLoader] private void load() { - if (distribution?.Bins == null || distribution.Bins.Length == 0) + if (hitEvents.Count == 0) return; - int maxCount = distribution.Bins.Max(); + int[] bins = new int[total_timing_distribution_bins]; + double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - var bars = new Drawable[distribution.Bins.Length]; + foreach (var e in hitEvents) + { + int binOffset = (int)(e.TimeOffset / binSize); + bins[timing_distribution_centre_bin_index + binOffset]++; + } + + int maxCount = bins.Max(); + var bars = new Drawable[total_timing_distribution_bins]; for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + bars[i] = new Bar { Height = (float)bins[i] / maxCount }; Container axisFlow; @@ -71,10 +96,8 @@ namespace osu.Game.Rulesets.Osu.Statistics } }; - // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. - // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - int sideBins = (distribution.Bins.Length - 1) / 2; - double maxValue = sideBins * distribution.BinSize; + // 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 diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 53c8e56f53..ba6a0e42c2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -8,8 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new TestHeatmap(new List()) + heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -73,8 +76,8 @@ namespace osu.Game.Tests.Visual.Ranking private class TestHeatmap : Heatmap { - public TestHeatmap(IReadOnlyList offsets) - : base(offsets) + public TestHeatmap(BeatmapInfo beatmap, List events) + : base(beatmap, events) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index c02be9ab5d..faabdf2cb6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.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. -using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -17,11 +16,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - ExtraStatistics = - { - ["timing_distribution"] = TestSceneTimingDistributionGraph.CreateNormalDistribution(), - ["hit_offsets"] = new List() - } + HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents().Cast().ToList(), }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 2249655093..178d6d95b5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -1,12 +1,15 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -22,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(CreateNormalDistribution()) + new TimingDistributionGraph(CreateDistributedHitEvents()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,34 +34,19 @@ namespace osu.Game.Tests.Visual.Ranking }; } - public static TimingDistribution CreateNormalDistribution() + public static List CreateDistributedHitEvents() { - var distribution = new TimingDistribution(51, 5); + var hitEvents = new List(); - // We create an approximately-normal distribution of 51 elements by using the 13th binomial row (14 initial elements) and subdividing the inner values twice. - var row = new List { 1 }; - for (int i = 0; i < 13; i++) - row.Add(row[i] * (13 - i) / (i + 1)); - - // Each subdivision yields 2n-1 total elements, so first subdivision will contain 27 elements, and the second will contain 53 elements. - for (int div = 0; div < 2; div++) + for (int i = 0; i < 50; i++) { - var newRow = new List { 1 }; + int count = (int)(Math.Pow(25 - Math.Abs(i - 25), 2)); - for (int i = 0; i < row.Count - 1; i++) - { - newRow.Add((row[i] + row[i + 1]) / 2); - newRow.Add(row[i + 1]); - } - - row = newRow; + for (int j = 0; j < count; j++) + hitEvents.Add(new HitEvent(i - 25, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); } - // After the subdivisions take place, we're left with 53 values which we use the inner 51 of. - for (int i = 1; i < row.Count - 1; i++) - distribution.Bins[i - 1] = row[i]; - - return distribution; + return hitEvents; } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 38b37afc55..6fc5892b3c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -166,7 +166,9 @@ namespace osu.Game.Scoring } } - public Dictionary ExtraStatistics = new Dictionary(); + [NotMapped] + [JsonIgnore] + public List HitEvents = new List(); [JsonIgnore] public List Files { get; set; } From ecdfcb1955f4929bc11fe1e3d1e8e1ddadfbd119 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:21:30 +0900 Subject: [PATCH 031/170] Display placeholder if no statistics available --- .../Ranking/TestSceneStatisticsPanel.cs | 17 +++++- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +- .../Ranking/Statistics/StatisticsPanel.cs | 61 +++++++++++++------ 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index faabdf2cb6..cc3415a530 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -3,6 +3,8 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -12,7 +14,7 @@ namespace osu.Game.Tests.Visual.Ranking public class TestSceneStatisticsPanel : OsuTestScene { [Test] - public void TestScore() + public void TestScoreWithStatistics() { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { @@ -22,9 +24,20 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(score); } + [Test] + public void TestScoreWithoutStatistics() + { + loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new StatisticsPanel(score); + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score } + }; }); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4a7cb6679a..c02a120a73 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -89,7 +89,11 @@ namespace osu.Game.Screens.Ranking SelectedScore = { BindTarget = SelectedScore }, PostExpandAction = onExpandedPanelClicked }, - statisticsPanel = new StatisticsPanel(Score) { RelativeSizeAxes = Axes.Both } + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + } } } }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 733c855426..28a8bc460e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,8 +1,11 @@ // 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 osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osuTK; @@ -12,15 +15,14 @@ namespace osu.Game.Screens.Ranking.Statistics { public const float SIDE_PADDING = 30; + public readonly Bindable Score = new Bindable(); + protected override bool StartHidden => true; - public StatisticsPanel(ScoreInfo score) + private readonly Container content; + + public StatisticsPanel() { - // Todo: Not correct. - RelativeSizeAxes = Axes.Both; - - FillFlowContainer statisticRows; - InternalChild = new Container { RelativeSizeAxes = Axes.Both, @@ -31,24 +33,47 @@ namespace osu.Game.Screens.Ranking.Statistics Top = SIDE_PADDING, Bottom = 50 // Approximate padding to the bottom of the score panel. }, - Child = statisticRows = new FillFlowContainer + Child = content = new Container { RelativeSizeAxes = Axes.Both }, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + Score.BindValueChanged(populateStatistics, true); + } + + private void populateStatistics(ValueChangedEvent score) + { + foreach (var child in content) + child.FadeOut(150).Expire(); + + var newScore = score.NewValue; + + if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) + content.Add(new MessagePlaceholder("Score has no statistics :(")); + else + { + var rows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), - } - }; + }; - foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) - { - statisticRows.Add(new GridContainer + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatistics(newScore)) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { s.Content }, - ColumnDimensions = s.ColumnDimensions, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); + rows.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { row.Content }, + ColumnDimensions = row.ColumnDimensions, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } + + content.Add(rows); } } From 53f507f51af7adc2e48200281fc8ff1598489142 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:27:10 +0900 Subject: [PATCH 032/170] Fade background --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c02a120a73..5073adcc50 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -219,6 +219,8 @@ namespace osu.Game.Screens.Ranking }); } } + + Background.FadeTo(0.5f, 150); } else { @@ -234,6 +236,8 @@ namespace osu.Game.Screens.Ranking panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } + + Background.FadeTo(0.1f, 150); } } } From 85a0f78600e97866c1726eefeed64113fef5d76c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:27:27 +0900 Subject: [PATCH 033/170] Hide statistics panel on first exit --- osu.Game/Screens/Ranking/ResultsScreen.cs | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 5073adcc50..de1939352f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, SelectedScore = { BindTarget = SelectedScore }, - PostExpandAction = onExpandedPanelClicked + PostExpandAction = () => statisticsPanel.ToggleVisibility() }, statisticsPanel = new StatisticsPanel { @@ -174,6 +174,8 @@ namespace osu.Game.Screens.Ranking if (req != null) api.Queue(req); + + statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } /// @@ -195,17 +197,23 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { + if (statisticsPanel.State.Value == Visibility.Visible) + { + statisticsPanel.Hide(); + return true; + } + Background.FadeTo(1, 250); return base.OnExiting(next); } - private void onExpandedPanelClicked() + private void onStatisticsStateChanged(ValueChangedEvent state) { - statisticsPanel.ToggleVisibility(); - - if (statisticsPanel.State.Value == Visibility.Hidden) + if (state.NewValue == Visibility.Hidden) { + Background.FadeTo(0.5f, 150); + foreach (var panel in scorePanelList.Panels) { if (panel.State == PanelState.Contracted) @@ -219,11 +227,11 @@ namespace osu.Game.Screens.Ranking }); } } - - Background.FadeTo(0.5f, 150); } else { + Background.FadeTo(0.1f, 150); + foreach (var panel in scorePanelList.Panels) { if (panel.State == PanelState.Contracted) @@ -236,8 +244,6 @@ namespace osu.Game.Screens.Ranking panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } - - Background.FadeTo(0.1f, 150); } } } From f04f2d21755103041272a66484730a5ae8687cfc Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Thu, 18 Jun 2020 21:46:32 +0100 Subject: [PATCH 034/170] Add test scene --- .../Gameplay/TestSceneStoryboardSamples.cs | 57 +++++++++++++++++++ .../Drawables/DrawableStoryboardSample.cs | 17 +++--- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 552d163b2f..60911d6792 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -10,9 +10,12 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -70,6 +73,37 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); } + [Test] + public void TestSamplePlaybackWithRateMods() + { + GameplayClockContainer gameplayContainer = null; + TestDrawableStoryboardSample sample = null; + + OsuModDoubleTime doubleTimeMod = null; + + AddStep("create container", () => + { + var beatmap = Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + + Add(gameplayContainer = new GameplayClockContainer(beatmap, new[] { doubleTimeMod = new OsuModDoubleTime() }, 0)); + + SelectedMods.Value = new[] { doubleTimeMod }; + Beatmap.Value = new TestCustomSkinWorkingBeatmap(beatmap.Beatmap, gameplayContainer.GameplayClock, Audio); + }); + + AddStep("create storyboard sample", () => + { + gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start", () => gameplayContainer.Start()); + + AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == doubleTimeMod.SpeedChange.Value); + } + private class TestSkin : LegacySkin { public TestSkin(string resourceName, AudioManager audioManager) @@ -99,5 +133,28 @@ namespace osu.Game.Tests.Gameplay { } } + + private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly AudioManager audio; + + public TestCustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio) + : base(beatmap, null, referenceClock, audio) + { + this.audio = audio; + } + + protected override ISkin GetSkin() => new TestSkin("test-sample", audio); + } + + private class TestDrawableStoryboardSample : DrawableStoryboardSample + { + public TestDrawableStoryboardSample(StoryboardSampleInfo sampleInfo) + : base(sampleInfo) + { + } + + public SampleChannel TestChannel => Channel; + } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 2b9c66d2e6..04df46410e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -20,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables private const double allowable_late_start = 100; private readonly StoryboardSampleInfo sampleInfo; - private SampleChannel channel; + + protected SampleChannel Channel; public override bool RemoveWhenNotAlive => false; @@ -33,14 +34,14 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, IBindable> mods) { - channel = beatmap.Value.Skin.GetSample(sampleInfo); + Channel = beatmap.Value.Skin.GetSample(sampleInfo); - if (channel != null) + if (Channel != null) { - channel.Volume.Value = sampleInfo.Volume / 100.0; + Channel.Volume.Value = sampleInfo.Volume / 100.0; foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(channel); + mod.ApplyToSample(Channel); } } @@ -52,7 +53,7 @@ namespace osu.Game.Storyboards.Drawables if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample - channel?.Stop(); + Channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) @@ -64,7 +65,7 @@ namespace osu.Game.Storyboards.Drawables // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future if (Time.Current - sampleInfo.StartTime < allowable_late_start) - channel?.Play(); + Channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) @@ -75,7 +76,7 @@ namespace osu.Game.Storyboards.Drawables protected override void Dispose(bool isDisposing) { - channel?.Stop(); + Channel?.Stop(); base.Dispose(isDisposing); } } From 5530e2a1dbaa413a4383348ca7b2cf042d3c1c60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 15:35:39 +0900 Subject: [PATCH 035/170] Add test for delayed score fetch --- .../Visual/Ranking/TestSceneResultsScreen.cs | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ea33aa62e3..9d3c22d87c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; @@ -16,6 +18,7 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { @@ -44,7 +47,7 @@ namespace osu.Game.Tests.Visual.Ranking private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] - public void ResultsWithoutPlayer() + public void TestResultsWithoutPlayer() { TestResultsScreen screen = null; OsuScreenStack stack; @@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Ranking } [Test] - public void ResultsWithPlayer() + public void TestResultsWithPlayer() { TestResultsScreen screen = null; @@ -73,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking } [Test] - public void ResultsForUnranked() + public void TestResultsForUnranked() { UnrankedSoloResultsScreen screen = null; @@ -82,6 +85,24 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestFetchScoresAfterShowingStatistics() + { + DelayedFetchResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for fetch", () => screen.FetchCompleted); + AddAssert("expanded panel still on screen", () => this.ChildrenOfType().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0); + } + private class TestResultsContainer : Container { [Cached(typeof(Player))] @@ -134,6 +155,42 @@ namespace osu.Game.Tests.Visual.Ranking } } + private class DelayedFetchResultsScreen : TestResultsScreen + { + public bool FetchCompleted { get; private set; } + + private readonly double delay; + + public DelayedFetchResultsScreen(ScoreInfo score, double delay) + : base(score) + { + this.delay = delay; + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + + var scores = new List(); + + for (int i = 0; i < 20; i++) + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + score.TotalScore += 10 - i; + scores.Add(score); + } + + scoresCallback?.Invoke(scores); + + Schedule(() => FetchCompleted = true); + }); + + return null; + } + } + private class UnrankedSoloResultsScreen : SoloResultsScreen { public HotkeyRetryOverlay RetryOverlay; From ec16b0fc5a3888c65b198da0640b563accba5803 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 17:28:35 +0900 Subject: [PATCH 036/170] Rework score panel tracking to fix visual edge cases --- .../Visual/Ranking/TestSceneScorePanel.cs | 38 -------- osu.Game/Screens/Ranking/ResultsScreen.cs | 97 +++++++++++++------ osu.Game/Screens/Ranking/ScorePanel.cs | 45 ++------- osu.Game/Screens/Ranking/ScorePanelList.cs | 79 +++++++++------ .../Ranking/ScorePanelTrackingContainer.cs | 35 +++++++ 5 files changed, 155 insertions(+), 139 deletions(-) create mode 100644 osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 1c5087ee94..250fdc5ebd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -102,39 +101,6 @@ namespace osu.Game.Tests.Visual.Ranking AddWaitStep("wait for transition", 10); } - [Test] - public void TestSceneTrackingScorePanel() - { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; - - addPanelStep(score, PanelState.Contracted); - - AddStep("enable tracking", () => - { - panel.Anchor = Anchor.CentreLeft; - panel.Origin = Anchor.CentreLeft; - panel.Tracking = true; - - Add(panel.CreateTrackingComponent().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); - }); - - assertTracking(true); - - AddStep("expand panel", () => panel.State = PanelState.Expanded); - AddWaitStep("wait for transition", 2); - assertTracking(true); - - AddStep("stop tracking", () => panel.Tracking = false); - assertTracking(false); - - AddStep("start tracking", () => panel.Tracking = true); - assertTracking(true); - } - private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = panel = new ScorePanel(score) @@ -144,9 +110,5 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); - - private void assertTracking(bool tracking) => AddAssert($"{(tracking ? "is" : "is not")} tracking", () => - Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.TopLeft, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.TopLeft) == tracking - && Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.BottomRight, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.BottomRight) == tracking); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index de1939352f..133efd6e7b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -47,6 +48,7 @@ namespace osu.Game.Screens.Ranking private StatisticsPanel statisticsPanel; private Drawable bottomPanel; private ScorePanelList scorePanelList; + private Container detachedPanelContainer; protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -89,11 +91,15 @@ namespace osu.Game.Screens.Ranking SelectedScore = { BindTarget = SelectedScore }, PostExpandAction = () => statisticsPanel.ToggleVisibility() }, + detachedPanelContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, statisticsPanel = new StatisticsPanel { RelativeSizeAxes = Axes.Both, Score = { BindTarget = SelectedScore } - } + }, } } }, @@ -169,7 +175,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - scorePanelList.AddScore(s); + addScore(s); })); if (req != null) @@ -208,42 +214,71 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } + private void addScore(ScoreInfo score) + { + var panel = scorePanelList.AddScore(score); + + if (detachedPanel != null) + panel.Alpha = 0; + } + + private ScorePanel detachedPanel; + private void onStatisticsStateChanged(ValueChangedEvent state) { - if (state.NewValue == Visibility.Hidden) + if (state.NewValue == Visibility.Visible) { - Background.FadeTo(0.5f, 150); + // Detach the panel in its original location, and move into the desired location in the local container. + var expandedPanel = scorePanelList.GetPanelForScore(SelectedScore.Value); + var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft; - foreach (var panel in scorePanelList.Panels) - { - if (panel.State == PanelState.Contracted) - panel.FadeIn(150); - else - { - panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p => - { - scorePanelList.HandleScroll = true; - p.Tracking = true; - }); - } - } - } - else - { + // Detach and move into the local container. + scorePanelList.Detach(expandedPanel); + detachedPanelContainer.Add(expandedPanel); + + // Move into its original location in the local container. + var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos); + expandedPanel.MoveTo(origLocation); + expandedPanel.MoveToX(origLocation.X); + + // Move into the final location. + expandedPanel.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); + + // Hide contracted panels. + foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + contracted.FadeOut(150, Easing.OutQuint); + scorePanelList.HandleInput = false; + + // Dim background. Background.FadeTo(0.1f, 150); - foreach (var panel in scorePanelList.Panels) - { - if (panel.State == PanelState.Contracted) - panel.FadeOut(150, Easing.OutQuint); - else - { - scorePanelList.HandleScroll = false; + detachedPanel = expandedPanel; + } + else if (detachedPanel != null) + { + var screenSpacePos = detachedPanel.ScreenSpaceDrawQuad.TopLeft; - panel.Tracking = false; - panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); - } - } + // Remove from the local container and re-attach. + detachedPanelContainer.Remove(detachedPanel); + scorePanelList.Attach(detachedPanel); + + // Move into its original location in the attached container. + var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); + detachedPanel.MoveTo(origLocation); + detachedPanel.MoveToX(origLocation.X); + + // Move into the final location. + detachedPanel.MoveToX(0, 150, Easing.OutQuint); + + // Show contracted panels. + foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + contracted.FadeIn(150, Easing.OutQuint); + scorePanelList.HandleInput = true; + + // Un-dim background. + Background.FadeTo(0.5f, 150); + + detachedPanel = null; } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 31b2796c13..257279bdc9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -78,11 +78,6 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; public Action PostExpandAction; - /// - /// Whether this should track the position of the tracking component created via . - /// - public bool Tracking; - /// /// Whether this can enter into an state. /// @@ -194,20 +189,6 @@ namespace osu.Game.Screens.Ranking } } - protected override void Update() - { - base.Update(); - - if (Tracking && trackingComponent != null) - Position = GetTrackingPosition(); - } - - public Vector2 GetTrackingPosition() - { - Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); - return topLeftPos - AnchorPosition + OriginPosition; - } - private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -269,8 +250,8 @@ namespace osu.Game.Screens.Ranking { base.Size = value; - if (trackingComponent != null) - trackingComponent.Size = value; + if (trackingContainer != null) + trackingContainer.Size = value; } } @@ -293,26 +274,14 @@ namespace osu.Game.Screens.Ranking || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); - private TrackingComponent trackingComponent; + private ScorePanelTrackingContainer trackingContainer; - public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this); - - public class TrackingComponent : Drawable + public ScorePanelTrackingContainer CreateTrackingContainer() { - public readonly ScorePanel Panel; + if (trackingContainer != null) + throw new InvalidOperationException("A score panel container has already been created."); - public TrackingComponent(ScorePanel panel) - { - Panel = panel; - } - - // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. - // So by forwarding input events, we remove the need to consider the order in which input is handled. - protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); - - public override bool IsPresent => Panel.IsPresent; + return trackingContainer = new ScorePanelTrackingContainer(this); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e332f462bb..32903860ec 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.Ranking public float CurrentScrollPosition => scroll.Current; - public IReadOnlyList Panels => panels; - private readonly Container panels; - private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -49,10 +46,9 @@ namespace osu.Game.Screens.Ranking InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, - HandleScroll = () => HandleScroll && expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. + HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. Children = new Drawable[] { - panels = new Container { RelativeSizeAxes = Axes.Both }, flow = new Flow { Anchor = Anchor.Centre, @@ -72,34 +68,14 @@ namespace osu.Game.Screens.Ranking SelectedScore.BindValueChanged(selectedScoreChanged, true); } - private bool handleScroll = true; - - public bool HandleScroll - { - get => handleScroll; - set - { - handleScroll = value; - - foreach (var p in panels) - p.CanExpand = value; - - scroll.ScrollbarVisible = value; - - if (!value) - scroll.ScrollTo(CurrentScrollPosition, false); - } - } - /// /// Adds a to this list. /// /// The to add. - public void AddScore(ScoreInfo score) + public ScorePanel AddScore(ScoreInfo score) { var panel = new ScorePanel(score) { - Tracking = true, PostExpandAction = () => PostExpandAction?.Invoke() }.With(p => { @@ -110,8 +86,7 @@ namespace osu.Game.Screens.Ranking }; }); - panels.Add(panel); - flow.Add(panel.CreateTrackingComponent().With(d => + flow.Add(panel.CreateTrackingContainer().With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; @@ -132,6 +107,8 @@ namespace osu.Game.Screens.Ranking scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; } } + + return panel; } /// @@ -187,15 +164,53 @@ namespace osu.Game.Screens.Ranking flow.Padding = new MarginPadding { Horizontal = offset }; } - private class Flow : FillFlowContainer + private bool handleInput = true; + + public bool HandleInput + { + get => handleInput; + set + { + handleInput = value; + scroll.ScrollbarVisible = value; + } + } + + public override bool PropagatePositionalInputSubTree => HandleInput && base.PropagatePositionalInputSubTree; + + public override bool PropagateNonPositionalInputSubTree => HandleInput && base.PropagateNonPositionalInputSubTree; + + public IEnumerable GetScorePanels() => flow.Select(t => t.Panel); + + public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel; + + public void Detach(ScorePanel panel) + { + var container = flow.FirstOrDefault(t => t.Panel == panel); + if (container == null) + throw new InvalidOperationException("Panel is not contained by the score panel list."); + + container.Detach(); + } + + public void Attach(ScorePanel panel) + { + var container = flow.FirstOrDefault(t => t.Panel == panel); + if (container == null) + throw new InvalidOperationException("Panel is not contained by the score panel list."); + + container.Attach(); + } + + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Panel.Score.TotalScore) - .ThenBy(s => s.Panel.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Panel.Score.TotalScore) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs new file mode 100644 index 0000000000..f6f26d0f8a --- /dev/null +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -0,0 +1,35 @@ +// 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.Screens.Ranking +{ + public class ScorePanelTrackingContainer : CompositeDrawable + { + public readonly ScorePanel Panel; + + public ScorePanelTrackingContainer(ScorePanel panel) + { + Panel = panel; + Attach(); + } + + public void Detach() + { + if (InternalChildren.Count == 0) + throw new InvalidOperationException("Score panel container is not attached."); + + RemoveInternal(Panel); + } + + public void Attach() + { + if (InternalChildren.Count > 0) + throw new InvalidOperationException("Score panel container is already attached."); + + AddInternal(Panel); + } + } +} From 55196efe6e4ae03efe09e7dbc2b79d7d90f8e4c5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 18:02:54 +0900 Subject: [PATCH 037/170] Fix panel depth ordering --- osu.Game/Screens/Ranking/ScorePanelList.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 32903860ec..8f9064c2d1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -211,6 +211,22 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(s => s.Panel.Score.TotalScore) .ThenBy(s => s.Panel.Score.OnlineScoreID); + + protected override int Compare(Drawable x, Drawable y) + { + var tX = (ScorePanelTrackingContainer)x; + var tY = (ScorePanelTrackingContainer)y; + + int result = tY.Panel.Score.TotalScore.CompareTo(tX.Panel.Score.TotalScore); + + if (result != 0) + return result; + + if (tX.Panel.Score.OnlineScoreID == null || tY.Panel.Score.OnlineScoreID == null) + return base.Compare(x, y); + + return tX.Panel.Score.OnlineScoreID.Value.CompareTo(tY.Panel.Score.OnlineScoreID.Value); + } } private class Scroll : OsuScrollContainer From c9ad3192b02ae4a9a2cc4d6b19adb9b84d54d4ef Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 18:02:57 +0900 Subject: [PATCH 038/170] Add more tests --- .../Visual/Ranking/TestSceneResultsScreen.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 9d3c22d87c..ac364b5233 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; @@ -18,6 +19,7 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Statistics; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -85,6 +87,73 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestShowHideStatistics() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddUntilStep("expanded panel at the left of the screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150; + }); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddUntilStep("expanded panel in centre of screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1); + }); + } + + [Test] + public void TestShowStatisticsAndClickOtherPanel() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + ScorePanel expandedPanel = null; + ScorePanel contractedPanel = null; + + AddStep("click expanded panel then contracted panel", () => + { + expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + + contractedPanel = this.ChildrenOfType().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X); + InputManager.MoveMouseTo(contractedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddAssert("contracted panel still contracted", () => contractedPanel.State == PanelState.Contracted); + AddAssert("expanded panel still expanded", () => expandedPanel.State == PanelState.Expanded); + } + [Test] public void TestFetchScoresAfterShowingStatistics() { From cae3a5f447e166b57bfed30a52e4a51964e4e2a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:08:36 +0900 Subject: [PATCH 039/170] Rework heatmap for more consistent performance --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 94 +++++++++---------- .../Ranking/TestSceneAccuracyHeatmap.cs | 55 +++++++---- 2 files changed, 78 insertions(+), 71 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 95cfc5b768..8ebc8e9001 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -26,10 +26,15 @@ namespace osu.Game.Rulesets.Osu.Statistics /// private const float inner_portion = 0.8f; - private const float rotation = 45; - private const float point_size = 4; + /// + /// Number of rows/columns of points. + /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// + private const int points_per_dimension = 32; - private Container allPoints; + private const float rotation = 45; + + private GridContainer pointGrid; private readonly BeatmapInfo beatmap; private readonly IReadOnlyList hitEvents; @@ -111,52 +116,39 @@ namespace osu.Game.Rulesets.Osu.Statistics } } }, - allPoints = new Container + pointGrid = new GridContainer { RelativeSizeAxes = Axes.Both } } }; - } - protected override void Update() - { - base.Update(); - validateHitPoints(); - } + Vector2 centre = new Vector2(points_per_dimension) / 2; + float innerRadius = centre.X * inner_portion; - private void validateHitPoints() - { - if (sizeLayout.IsValid) - return; + Drawable[][] points = new Drawable[points_per_dimension][]; - allPoints.Clear(); - - // Since the content is fit, both dimensions should have the same size. - float size = allPoints.DrawSize.X; - - Vector2 centre = new Vector2(size / 2); - int rows = (int)Math.Ceiling(size / point_size); - int cols = (int)Math.Ceiling(size / point_size); - - for (int r = 0; r < rows; r++) + for (int r = 0; r < points_per_dimension; r++) { - for (int c = 0; c < cols; c++) + points[r] = new Drawable[points_per_dimension]; + + for (int c = 0; c < points_per_dimension; c++) { - Vector2 pos = new Vector2(c * point_size, r * point_size); - HitPointType pointType = HitPointType.Hit; + HitPointType pointType = Vector2.Distance(new Vector2(c, r), centre) <= innerRadius + ? HitPointType.Hit + : HitPointType.Miss; - if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - pointType = HitPointType.Miss; - - allPoints.Add(new HitPoint(pos, pointType) + var point = new HitPoint(pointType) { - Size = new Vector2(point_size), Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) - }); + }; + + points[r][c] = point; } } + pointGrid.Content = points; + if (hitEvents.Count > 0) { // Todo: This should probably not be done like this. @@ -170,41 +162,39 @@ namespace osu.Game.Rulesets.Osu.Statistics AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); } } - - sizeLayout.Validate(); } protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { - if (allPoints.Count == 0) + if (pointGrid.Content.Length == 0) return; double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - // Since the content is fit, both dimensions should have the same size. - float size = allPoints.DrawSize.X; + // Convert the above into the local search space. + Vector2 localCentre = new Vector2(points_per_dimension) / 2; + float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. + double localAngle = finalAngle + 3 * Math.PI / 4; // The angle inside the heatmap on which the closest point lies. + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. double minDist = double.PositiveInfinity; HitPoint point = null; - foreach (var p in allPoints) + for (int r = 0; r < points_per_dimension; r++) { - Vector2 localCentre = new Vector2(size / 2); - float localRadius = localCentre.X * inner_portion * normalisedDistance; - double localAngle = finalAngle + 3 * Math.PI / 4; - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); - - float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); - - if (dist < minDist) + for (int c = 0; c < points_per_dimension; c++) { - minDist = dist; - point = p; + float dist = Vector2.Distance(new Vector2(c, r), localPoint); + + if (dist < minDist) + { + minDist = dist; + point = (HitPoint)pointGrid.Content[r][c]; + } } } @@ -216,11 +206,11 @@ namespace osu.Game.Rulesets.Osu.Statistics { private readonly HitPointType pointType; - public HitPoint(Vector2 position, HitPointType pointType) + public HitPoint(HitPointType pointType) { this.pointType = pointType; - Position = position; + RelativeSizeAxes = Axes.Both; Alpha = 0; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index ba6a0e42c2..52cc41fbd8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; @@ -20,13 +22,18 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene { - private readonly Box background; - private readonly Drawable object1; - private readonly Drawable object2; - private readonly TestHeatmap heatmap; + private Box background; + private Drawable object1; + private Drawable object2; + private TestHeatmap heatmap; + private ScheduledDelegate automaticAdditionDelegate; - public TestSceneAccuracyHeatmap() + [SetUp] + public void Setup() => Schedule(() => { + automaticAdditionDelegate?.Cancel(); + automaticAdditionDelegate = null; + Children = new[] { background = new Box @@ -41,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking }, object2 = new BorderCircle { - Position = new Vector2(500, 300), + Position = new Vector2(100, 300), }, heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) { @@ -50,22 +57,32 @@ namespace osu.Game.Tests.Visual.Ranking Size = new Vector2(130) } }; + }); + + [Test] + public void TestManyHitPointsAutomatic() + { + AddStep("add scheduled delegate", () => + { + automaticAdditionDelegate = Scheduler.AddDelayed(() => + { + var randomPos = new Vector2( + RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), + RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); + + // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). + heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); + }, 1, true); + }); + + AddWaitStep("wait for some hit points", 10); } - protected override void LoadComplete() + [Test] + public void TestManualPlacement() { - base.LoadComplete(); - - Scheduler.AddDelayed(() => - { - var randomPos = new Vector2( - RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), - RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); - - // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). - heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); - InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); - }, 1, true); + AddStep("return user input", () => InputManager.UseParentInput = true); } protected override bool OnMouseDown(MouseDownEvent e) From d3e4e6325884a2ac753d3c0c2c2601accb7a4d2f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:12:48 +0900 Subject: [PATCH 040/170] Remove unnecessary class --- .../Scoring/TimingDistribution.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs deleted file mode 100644 index 46f259f3d8..0000000000 --- a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs +++ /dev/null @@ -1,17 +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.Rulesets.Osu.Scoring -{ - public class TimingDistribution - { - public readonly int[] Bins; - public readonly double BinSize; - - public TimingDistribution(int binCount, double binSize) - { - Bins = new int[binCount]; - BinSize = binSize; - } - } -} From a3ff25177ad782e562732315a74be1557ca19ffc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:12:55 +0900 Subject: [PATCH 041/170] Asyncify statistics load --- .../Ranking/Statistics/StatisticsPanel.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 28a8bc460e..acaf91246d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osuTK; @@ -20,6 +22,7 @@ namespace osu.Game.Screens.Ranking.Statistics protected override bool StartHidden => true; private readonly Container content; + private readonly LoadingSpinner spinner; public StatisticsPanel() { @@ -33,7 +36,11 @@ namespace osu.Game.Screens.Ranking.Statistics Top = SIDE_PADDING, Bottom = 50 // Approximate padding to the bottom of the score panel. }, - Child = content = new Container { RelativeSizeAxes = Axes.Both }, + Children = new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.Both }, + spinner = new LoadingSpinner() + } }; } @@ -43,8 +50,12 @@ namespace osu.Game.Screens.Ranking.Statistics Score.BindValueChanged(populateStatistics, true); } + private CancellationTokenSource loadCancellation; + private void populateStatistics(ValueChangedEvent score) { + loadCancellation?.Cancel(); + foreach (var child in content) child.FadeOut(150).Expire(); @@ -54,6 +65,8 @@ namespace osu.Game.Screens.Ranking.Statistics content.Add(new MessagePlaceholder("Score has no statistics :(")); else { + spinner.Show(); + var rows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -73,7 +86,14 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - content.Add(rows); + LoadComponentAsync(rows, d => + { + if (Score.Value != newScore) + return; + + spinner.Hide(); + content.Add(d); + }, (loadCancellation = new CancellationTokenSource()).Token); } } From 8c9506197d30b1635bb51541959978221d7f0d94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:41:36 +0900 Subject: [PATCH 042/170] Increase the number of bins in the timing distribution --- osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index b319cc5aa9..30d25f581f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Statistics /// /// The number of bins on each side of the timing distribution. /// - private const int timing_distribution_bins = 25; + private const int timing_distribution_bins = 50; /// /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. From ef56225d9adfda9bd45038746cd03edfb244a7b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:43:46 +0900 Subject: [PATCH 043/170] Rename CursorPosition -< PositionOffset --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 8 ++++---- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 9694367210..0a9ce83912 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -88,17 +88,17 @@ namespace osu.Game.Rulesets.Osu.Scoring public readonly HitObject LastHitObject; /// - /// The player's cursor position, if available, at the time of the event. + /// The player's position offset, if available, at the time of the event. /// - public readonly Vector2? CursorPosition; + public readonly Vector2? PositionOffset; - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? cursorPosition) + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? positionOffset) { TimeOffset = timeOffset; Result = result; HitObject = hitObject; LastHitObject = lastHitObject; - CursorPosition = cursorPosition; + PositionOffset = positionOffset; } } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 8ebc8e9001..b648dd5e47 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -156,10 +156,10 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var e in hitEvents) { - if (e.LastHitObject == null || e.CursorPosition == null) + if (e.LastHitObject == null || e.PositionOffset == null) continue; - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); } } } From eab00ec9d9644f32e72268c819fad8cf5801e17c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:58:35 +0900 Subject: [PATCH 044/170] Move hit events to the ScoreProcessor --- .../Judgements/OsuHitCircleJudgementResult.cs | 9 ++- .../Objects/Drawables/DrawableHitCircle.cs | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +- .../Scoring/OsuScoreProcessor.cs | 76 ------------------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 36 ++++----- .../Statistics/TimingDistributionGraph.cs | 15 ++-- .../Ranking/TestSceneAccuracyHeatmap.cs | 10 +-- .../Ranking/TestSceneStatisticsPanel.cs | 3 +- .../TestSceneTimingDistributionGraph.cs | 4 +- osu.Game/Rulesets/Scoring/HitEvent.cs | 48 ++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 18 +++++ osu.Game/Scoring/ScoreInfo.cs | 2 +- 12 files changed, 106 insertions(+), 124 deletions(-) create mode 100644 osu.Game/Rulesets/Scoring/HitEvent.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs index 103d02958d..9b33e746b3 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs @@ -10,10 +10,15 @@ namespace osu.Game.Rulesets.Osu.Judgements { public class OsuHitCircleJudgementResult : OsuJudgementResult { + /// + /// The . + /// public HitCircle HitCircle => (HitCircle)HitObject; - public Vector2? HitPosition; - public float? Radius; + /// + /// The position of the player's cursor when was hit. + /// + public Vector2? CursorPositionAtHit; public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement) : base(hitObject, judgement) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 2f86400b25..854fc4c91c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -142,11 +142,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var circleResult = (OsuHitCircleJudgementResult)r; + // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. if (result != HitResult.Miss) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - circleResult.HitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); - circleResult.Radius = (float)HitObject.Radius; + circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); } circleResult.Type = result; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index c7003deed2..45980cb3d5 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; -using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -201,7 +200,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph(score.HitEvents.Cast().ToList()) + Child = new TimingDistributionGraph(score) { RelativeSizeAxes = Axes.Both } @@ -209,7 +208,7 @@ namespace osu.Game.Rulesets.Osu new StatisticContainer("Accuracy Heatmap") { RelativeSizeAxes = Axes.Both, - Child = new Heatmap(score.Beatmap, score.HitEvents.Cast().ToList()) + Child = new Heatmap(score) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 0a9ce83912..231a24cac5 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,54 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osuTK; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - private readonly List hitEvents = new List(); - private HitObject lastHitObject; - - protected override void OnResultApplied(JudgementResult result) - { - base.OnResultApplied(result); - - hitEvents.Add(new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, (result as OsuHitCircleJudgementResult)?.HitPosition)); - lastHitObject = result.HitObject; - } - - protected override void OnResultReverted(JudgementResult result) - { - base.OnResultReverted(result); - - hitEvents.RemoveAt(hitEvents.Count - 1); - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - hitEvents.Clear(); - lastHitObject = null; - } - - public override void PopulateScore(ScoreInfo score) - { - base.PopulateScore(score); - - score.HitEvents.AddRange(hitEvents.Select(e => e).Cast()); - } - protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) { switch (hitObject) @@ -63,42 +25,4 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } - - public readonly struct HitEvent - { - /// - /// The time offset from the end of at which the event occurred. - /// - public readonly double TimeOffset; - - /// - /// The hit result. - /// - public readonly HitResult Result; - - /// - /// The on which the result occurred. - /// - public readonly HitObject HitObject; - - /// - /// The occurring prior to . - /// - [CanBeNull] - public readonly HitObject LastHitObject; - - /// - /// The player's position offset, if available, at the time of the event. - /// - public readonly Vector2? PositionOffset; - - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? positionOffset) - { - TimeOffset = timeOffset; - Result = result; - HitObject = hitObject; - LastHitObject = lastHitObject; - PositionOffset = positionOffset; - } - } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index b648dd5e47..49d7f67b7f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -2,17 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -36,16 +33,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private GridContainer pointGrid; - private readonly BeatmapInfo beatmap; - private readonly IReadOnlyList hitEvents; - private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); + private readonly ScoreInfo score; - public Heatmap(BeatmapInfo beatmap, IReadOnlyList hitEvents) + public Heatmap(ScoreInfo score) { - this.beatmap = beatmap; - this.hitEvents = hitEvents; - - AddLayout(sizeLayout); + this.score = score; } [BackgroundDependencyLoader] @@ -149,18 +141,18 @@ namespace osu.Game.Rulesets.Osu.Statistics pointGrid.Content = points; - if (hitEvents.Count > 0) + if (score.HitEvents == null || score.HitEvents.Count == 0) + return; + + // Todo: This should probably not be done like this. + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + + foreach (var e in score.HitEvents) { - // Todo: This should probably not be done like this. - float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + if (e.LastHitObject == null || e.PositionOffset == null) + continue; - foreach (var e in hitEvents) - { - if (e.LastHitObject == null || e.PositionOffset == null) - continue; - - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); - } + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); } } diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 30d25f581f..f3ccb0630e 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -2,7 +2,6 @@ // 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.Extensions.Color4Extensions; @@ -11,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Statistics { @@ -37,23 +36,23 @@ namespace osu.Game.Rulesets.Osu.Statistics /// private const float axis_points = 5; - private readonly List hitEvents; + private readonly ScoreInfo score; - public TimingDistributionGraph(List hitEvents) + public TimingDistributionGraph(ScoreInfo score) { - this.hitEvents = hitEvents; + this.score = score; } [BackgroundDependencyLoader] private void load() { - if (hitEvents.Count == 0) + if (score.HitEvents == null || score.HitEvents.Count == 0) return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = score.HitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - foreach (var e in hitEvents) + foreach (var e in score.HitEvents) { int binOffset = (int)(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 52cc41fbd8..d8b0594803 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,7 +1,6 @@ // 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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,10 +9,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(100, 300), }, - heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) + heatmap = new TestHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,8 +91,8 @@ namespace osu.Game.Tests.Visual.Ranking private class TestHeatmap : Heatmap { - public TestHeatmap(BeatmapInfo beatmap, List events) - : base(beatmap, events) + public TestHeatmap(ScoreInfo score) + : base(score) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index cc3415a530..bcf8a19c61 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents().Cast().ToList(), + HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents() }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 178d6d95b5..d5ee50e636 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -7,9 +7,9 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(CreateDistributedHitEvents()) + new TimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs new file mode 100644 index 0000000000..908ac0c171 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Scoring +{ + public readonly struct HitEvent + { + /// + /// The time offset from the end of at which the event occurred. + /// + public readonly double TimeOffset; + + /// + /// The hit result. + /// + public readonly HitResult Result; + + /// + /// The on which the result occurred. + /// + public readonly HitObject HitObject; + + /// + /// The occurring prior to . + /// + [CanBeNull] + public readonly HitObject LastHitObject; + + /// + /// The player's position offset, if available, at the time of the event. + /// + [CanBeNull] + public readonly Vector2? PositionOffset; + + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) + { + TimeOffset = timeOffset; + Result = result; + HitObject = hitObject; + LastHitObject = lastHitObject; + PositionOffset = positionOffset; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 619547aef4..b9f51dfad3 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring @@ -61,6 +62,9 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; + private readonly List hitEvents = new List(); + private HitObject lastHitObject; + private double scoreMultiplier = 1; public ScoreProcessor() @@ -128,6 +132,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + hitEvents.Add(CreateHitEvent(result)); + lastHitObject = result.HitObject; + updateScore(); OnResultApplied(result); @@ -137,6 +144,9 @@ namespace osu.Game.Rulesets.Scoring { } + protected virtual HitEvent CreateHitEvent(JudgementResult result) + => new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null); + protected sealed override void RevertResultInternal(JudgementResult result) { Combo.Value = result.ComboAtJudgement; @@ -159,6 +169,10 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + Debug.Assert(hitEvents.Count > 0); + lastHitObject = hitEvents[^1].LastHitObject; + hitEvents.RemoveAt(hitEvents.Count - 1); + updateScore(); OnResultReverted(result); @@ -219,6 +233,8 @@ namespace osu.Game.Rulesets.Scoring base.Reset(storeResults); scoreResultCounts.Clear(); + hitEvents.Clear(); + lastHitObject = null; if (storeResults) { @@ -259,6 +275,8 @@ namespace osu.Game.Rulesets.Scoring foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) score.Statistics[result] = GetStatistic(result); + + score.HitEvents = new List(hitEvents); } /// diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 6fc5892b3c..84c0d5b54e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -168,7 +168,7 @@ namespace osu.Game.Scoring [NotMapped] [JsonIgnore] - public List HitEvents = new List(); + public List HitEvents { get; set; } [JsonIgnore] public List Files { get; set; } From 1cbbd6b4427130159832eade1e91ae557c4181e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:03:18 +0900 Subject: [PATCH 045/170] Move timing distribution graph to osu.Game --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 2 +- .../Visual/Ranking/TestSceneTimingDistributionGraph.cs | 8 ++++---- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs => osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs (96%) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 45980cb3d5..d99fee3b15 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph(score) + Child = new HitEventTimingDistributionGraph(score) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index bcf8a19c61..210abaef4e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents() + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents() }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index d5ee50e636..bfdc216aa1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -7,16 +7,16 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; using osuTK; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneTimingDistributionGraph : OsuTestScene + public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - public TestSceneTimingDistributionGraph() + public TestSceneHitEventTimingDistributionGraph() { Children = new Drawable[] { @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) + new HitEventTimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs similarity index 96% rename from osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs rename to osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index f3ccb0630e..b258e92aeb 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -12,9 +12,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Scoring; -namespace osu.Game.Rulesets.Osu.Statistics +namespace osu.Game.Screens.Ranking.Statistics { - public class TimingDistributionGraph : CompositeDrawable + public class HitEventTimingDistributionGraph : CompositeDrawable { /// /// The number of bins on each side of the timing distribution. @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; - public TimingDistributionGraph(ScoreInfo score) + public HitEventTimingDistributionGraph(ScoreInfo score) { this.score = score; } From 83e6c3efdb32c23f2af26fb2c4661ae49f62275c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:31:52 +0900 Subject: [PATCH 046/170] Adjust API for returning statistics --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 43 ++++++++----------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 3 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 18 ++++++++ .../TestSceneTimingDistributionGraph.cs | 3 +- osu.Game/Rulesets/Ruleset.cs | 1 + .../HitEventTimingDistributionGraph.cs | 26 +++++++---- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticItem.cs | 23 ++++++++++ .../Ranking/Statistics/StatisticRow.cs | 11 ++--- .../Ranking/Statistics/StatisticsPanel.cs | 5 ++- 10 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticItem.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d99fee3b15..aa313c92b3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,9 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using System.Linq; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -190,36 +192,29 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatistics(ScoreInfo score) { - new StatisticRow + var hitCircleEvents = score.HitEvents.Where(e => e.HitObject is HitCircle).ToList(); + + return new[] { - Content = new Drawable[] + new StatisticRow { - new StatisticContainer("Timing Distribution") + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 130, - Child = new HitEventTimingDistributionGraph(score) + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(hitCircleEvents) { - RelativeSizeAxes = Axes.Both - } - }, - new StatisticContainer("Accuracy Heatmap") - { - RelativeSizeAxes = Axes.Both, - Child = new Heatmap(score) + RelativeSizeAxes = Axes.X, + Height = 130 + }), + new StatisticItem("Accuracy Heatmap", new Heatmap(score) { - RelativeSizeAxes = Axes.Both - } - }, - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 130), + RelativeSizeAxes = Axes.X, + Height = 130 + }, new Dimension(GridSizeMode.Absolute, 130)), + } } - } - }; + }; + } } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 49d7f67b7f..86cb8e682f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -147,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Todo: This should probably not be done like this. float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; - foreach (var e in score.HitEvents) + foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { if (e.LastHitObject == null || e.PositionOffset == null) continue; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 4cdd1fbc24..cd4e699262 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -21,9 +21,12 @@ using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; +using System.Linq; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; +using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko @@ -155,5 +158,20 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }), + } + } + }; } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index bfdc216aa1..b34529cca7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) + new HitEventTimingDistributionGraph(CreateDistributedHitEvents()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f05685b6e9..52784e354f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,7 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + [NotNull] public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index b258e92aeb..4acbc7da3c 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.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.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -10,10 +11,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// A graph which displays the distribution of hit timing in a series of s. + /// public class HitEventTimingDistributionGraph : CompositeDrawable { /// @@ -32,27 +36,31 @@ namespace osu.Game.Screens.Ranking.Statistics private const int timing_distribution_centre_bin_index = timing_distribution_bins; /// - /// The number of data points shown on the axis below the graph. + /// The number of data points shown on each side of the axis below the graph. /// private const float axis_points = 5; - private readonly ScoreInfo score; + private readonly IReadOnlyList hitEvents; - public HitEventTimingDistributionGraph(ScoreInfo score) + /// + /// Creates a new . + /// + /// The s to display the timing distribution of. + public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.score = score; + this.hitEvents = hitEvents; } [BackgroundDependencyLoader] private void load() { - if (score.HitEvents == null || score.HitEvents.Count == 0) + if (hitEvents == null || hitEvents.Count == 0) return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = score.HitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - foreach (var e in score.HitEvents) + foreach (var e in hitEvents) { int binOffset = (int)(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; @@ -67,6 +75,8 @@ namespace osu.Game.Screens.Ranking.Statistics InternalChild = new GridContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Width = 0.8f, Content = new[] diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index d7b42c1c2f..b8dde8f85e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - public class StatisticContainer : Container + internal class StatisticContainer : Container { protected override Container Content => content; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs new file mode 100644 index 0000000000..2605ae9f1b --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticItem + { + public readonly string Name; + public readonly Drawable Content; + public readonly Dimension Dimension; + + public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + { + Name = name; + Content = content; + Dimension = dimension; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index 5d39ef57b2..ebab148fc2 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -1,15 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using JetBrains.Annotations; namespace osu.Game.Screens.Ranking.Statistics { public class StatisticRow { - public Drawable[] Content = Array.Empty(); - public Dimension[] ColumnDimensions = Array.Empty(); + /// + /// The columns of this . + /// + [ItemCanBeNull] + public StatisticItem[] Columns; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index acaf91246d..3d81229ac3 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,6 +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.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,8 +81,8 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { row.Content }, - ColumnDimensions = row.ColumnDimensions, + Content = new[] { row.Columns?.Select(c => c?.Content).ToArray() }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0).Select(i => row.Columns[i]?.Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From ad3bc99e7c9bf6d39acb5f7595d97e1369ad4c56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:48:53 +0900 Subject: [PATCH 047/170] Fix hit event position offset not being set --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 3 +++ osu.Game/Rulesets/Scoring/HitEvent.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 231a24cac5..86ec76e373 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,9 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + protected override HitEvent CreateHitEvent(JudgementResult result) + => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) { switch (hitObject) diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index 908ac0c171..a2770ec580 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -44,5 +44,7 @@ namespace osu.Game.Rulesets.Scoring LastHitObject = lastHitObject; PositionOffset = positionOffset; } + + public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, Result, HitObject, LastHitObject, positionOffset); } } From 34a8fcfd2f6a3cd1d380400801c95e02930016c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:53:24 +0900 Subject: [PATCH 048/170] Fix potential off-by-one --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 4acbc7da3c..43de862007 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking.Statistics return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); foreach (var e in hitEvents) { From 5ce2c712d343e46e939f62d36f9ad39e047e414d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:53:43 +0900 Subject: [PATCH 049/170] Fix statistics not being wrapped by containers --- osu.Game/Rulesets/Ruleset.cs | 5 +++++ .../Ranking/Statistics/StatisticContainer.cs | 10 ++++++++-- .../Ranking/Statistics/StatisticItem.cs | 20 +++++++++++++++++++ .../Ranking/Statistics/StatisticRow.cs | 5 ++++- .../Ranking/Statistics/StatisticsPanel.cs | 11 ++++++++-- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 52784e354f..a325e641a4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,11 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + /// + /// Creates the statistics for a to be displayed in the results screen. + /// + /// The to create the statistics for. The score is guaranteed to have populated. + /// The s to display. Each may contain 0 or more . [NotNull] public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index b8dde8f85e..b063893633 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -19,9 +19,13 @@ namespace osu.Game.Screens.Ranking.Statistics public StatisticContainer(string name) { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Content = new[] { new Drawable[] @@ -56,13 +60,15 @@ namespace osu.Game.Screens.Ranking.Statistics { content = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, } }, }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), } }; } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 2605ae9f1b..a3ef5bf99e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -7,12 +7,32 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// An item to be displayed in a row of statistics inside the results screen. + /// public class StatisticItem { + /// + /// The name of this item. + /// public readonly string Name; + + /// + /// The content to be displayed. + /// public readonly Drawable Content; + + /// + /// The of this row. This can be thought of as the column dimension of an encompassing . + /// public readonly Dimension Dimension; + /// + /// Creates a new , to be displayed inside a in the results screen. + /// + /// The name of this item. + /// The content to be displayed. + /// The of this row. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) { Name = name; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index ebab148fc2..e1ca9799a3 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -5,12 +5,15 @@ using JetBrains.Annotations; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// A row of statistics to be displayed in the results screen. + /// public class StatisticRow { /// /// The columns of this . /// - [ItemCanBeNull] + [ItemNotNull] public StatisticItem[] Columns; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 3d81229ac3..328b6933a0 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -81,8 +81,15 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { row.Columns?.Select(c => c?.Content).ToArray() }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0).Select(i => row.Columns[i]?.Dimension ?? new Dimension()).ToArray(), + Content = new[] + { + row.Columns?.Select(c => new StatisticContainer(c.Name) + { + Child = c.Content + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) + .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From 8aea8267fb989172c535a331aacddcfbe048e3b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:58:05 +0900 Subject: [PATCH 050/170] Add some padding --- osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index b063893633..d9e5e1294a 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 15 } } }, }, From 89a863a3379262c600f9774728e63117c8037567 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:02:20 +0900 Subject: [PATCH 051/170] Refactor OsuRuleset --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 33 ++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index aa313c92b3..3fb8f574b3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,29 +192,24 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] { - var hitCircleEvents = score.HitEvents.Where(e => e.HitObject is HitCircle).ToList(); - - return new[] + new StatisticRow { - new StatisticRow + Columns = new[] { - Columns = new[] + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle).ToList()) { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(hitCircleEvents) - { - RelativeSizeAxes = Axes.X, - Height = 130 - }), - new StatisticItem("Accuracy Heatmap", new Heatmap(score) - { - RelativeSizeAxes = Axes.X, - Height = 130 - }, new Dimension(GridSizeMode.Absolute, 130)), - } + RelativeSizeAxes = Axes.X, + Height = 130 + }), + new StatisticItem("Accuracy Heatmap", new Heatmap(score) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }, new Dimension(GridSizeMode.Absolute, 130)), } - }; - } + } + }; } } From 49997c54d01be791bcc1bdfe4d2ee52abf063edb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:14:17 +0900 Subject: [PATCH 052/170] Remove unused class --- osu.Game.Rulesets.Osu/Scoring/HitOffset.cs | 23 ---------------------- 1 file changed, 23 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Scoring/HitOffset.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs b/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs deleted file mode 100644 index e6a5a01b48..0000000000 --- a/osu.Game.Rulesets.Osu/Scoring/HitOffset.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. - -using osuTK; - -namespace osu.Game.Rulesets.Osu.Scoring -{ - public class HitOffset - { - public readonly Vector2 Position1; - public readonly Vector2 Position2; - public readonly Vector2 HitPosition; - public readonly float Radius; - - public HitOffset(Vector2 position1, Vector2 position2, Vector2 hitPosition, float radius) - { - Position1 = position1; - Position2 = position2; - HitPosition = hitPosition; - Radius = radius; - } - } -} From 863666f7c483d83dda88a3acf9b1bb1b33f70bce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:14:31 +0900 Subject: [PATCH 053/170] Move accuracy heatmap to osu! ruleset, rename, remove magic number --- .../TestSceneAccuracyHeatmap.cs | 16 ++++++++-------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../{Heatmap.cs => AccuracyHeatmap.cs} | 11 ++++++----- 3 files changed, 15 insertions(+), 14 deletions(-) rename {osu.Game.Tests/Visual/Ranking => osu.Game.Rulesets.Osu.Tests}/TestSceneAccuracyHeatmap.cs (86%) rename osu.Game.Rulesets.Osu/Statistics/{Heatmap.cs => AccuracyHeatmap.cs} (95%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs similarity index 86% rename from osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index d8b0594803..f2a36ea017 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -9,21 +9,21 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Ranking +namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene { private Box background; private Drawable object1; private Drawable object2; - private TestHeatmap heatmap; + private TestAccuracyHeatmap accuracyHeatmap; private ScheduledDelegate automaticAdditionDelegate; [SetUp] @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(100, 300), }, - heatmap = new TestHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) + accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Ranking RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). - heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + accuracyHeatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); }, 1, true); }); @@ -85,13 +85,13 @@ namespace osu.Game.Tests.Visual.Ranking protected override bool OnMouseDown(MouseDownEvent e) { - heatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); + accuracyHeatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); return true; } - private class TestHeatmap : Heatmap + private class TestAccuracyHeatmap : AccuracyHeatmap { - public TestHeatmap(ScoreInfo score) + public TestAccuracyHeatmap(ScoreInfo score) : base(score) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 3fb8f574b3..65f26c0647 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -203,7 +203,7 @@ namespace osu.Game.Rulesets.Osu RelativeSizeAxes = Axes.X, Height = 130 }), - new StatisticItem("Accuracy Heatmap", new Heatmap(score) + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score) { RelativeSizeAxes = Axes.X, Height = 130 diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Statistics/Heatmap.cs rename to osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 86cb8e682f..10ca3eb9be 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; using osuTK; @@ -16,17 +17,17 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Statistics { - public class Heatmap : CompositeDrawable + public class AccuracyHeatmap : CompositeDrawable { /// - /// Size of the inner circle containing the "hit" points, relative to the size of this . + /// Size of the inner circle containing the "hit" points, relative to the size of this . /// All other points outside of the inner circle are "miss" points. /// private const float inner_portion = 0.8f; /// /// Number of rows/columns of points. - /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. /// private const int points_per_dimension = 32; @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; - public Heatmap(ScoreInfo score) + public AccuracyHeatmap(ScoreInfo score) { this.score = score; } @@ -170,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Convert the above into the local search space. Vector2 localCentre = new Vector2(points_per_dimension) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. - double localAngle = finalAngle + 3 * Math.PI / 4; // The angle inside the heatmap on which the closest point lies. + double localAngle = finalAngle + Math.PI - MathUtils.DegreesToRadians(rotation); // The angle inside the heatmap on which the closest point lies. Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. From 81ad257a17ae2bab2df18ab9ceddb1e77202c149 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:18:58 +0900 Subject: [PATCH 054/170] Add timing distribution to mania ruleset --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a37aaa8cc4..b8725af856 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Mania { @@ -307,6 +308,21 @@ namespace osu.Game.Rulesets.Mania { return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } + + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }), + } + } + }; } public enum PlayfieldType From 25abdc290331e6d2ffea212259771d25cbf5b3b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:41:48 +0900 Subject: [PATCH 055/170] General cleanups --- osu.Game/Rulesets/Scoring/HitEvent.cs | 18 +++++++- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 ------ osu.Game/Screens/Ranking/ScorePanel.cs | 18 +++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 43 ++++++++++++++----- .../Ranking/ScorePanelTrackingContainer.cs | 17 +++++++- .../Ranking/Statistics/StatisticContainer.cs | 23 ++++++---- .../Ranking/Statistics/StatisticItem.cs | 4 +- .../Ranking/Statistics/StatisticsPanel.cs | 9 ++-- 9 files changed, 98 insertions(+), 48 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index a2770ec580..ea2975a6c4 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -7,6 +7,9 @@ using osuTK; namespace osu.Game.Rulesets.Scoring { + /// + /// A generated by the containing extra statistics around a . + /// public readonly struct HitEvent { /// @@ -31,11 +34,19 @@ namespace osu.Game.Rulesets.Scoring public readonly HitObject LastHitObject; /// - /// The player's position offset, if available, at the time of the event. + /// A position offset, if available, at the time of the event. /// [CanBeNull] public readonly Vector2? PositionOffset; + /// + /// Creates a new . + /// + /// The time offset from the end of at which the event occurs. + /// The . + /// The that triggered the event. + /// The previous . + /// A positional offset. public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) { TimeOffset = timeOffset; @@ -45,6 +56,11 @@ namespace osu.Game.Rulesets.Scoring PositionOffset = positionOffset; } + /// + /// Creates a new with an optional positional offset. + /// + /// The positional offset. + /// The new . public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, Result, HitObject, LastHitObject, positionOffset); } } diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 77acbd4137..018b50bd3d 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Scoring /// Retrieves the with the largest hit window that produces a successful hit. /// /// The lowest allowed successful . - public HitResult LowestSuccessfulHitResult() + protected HitResult LowestSuccessfulHitResult() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) { diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b9f51dfad3..22ec023f58 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -136,12 +136,6 @@ namespace osu.Game.Rulesets.Scoring lastHitObject = result.HitObject; updateScore(); - - OnResultApplied(result); - } - - protected virtual void OnResultApplied(JudgementResult result) - { } protected virtual HitEvent CreateHitEvent(JudgementResult result) @@ -174,12 +168,6 @@ namespace osu.Game.Rulesets.Scoring hitEvents.RemoveAt(hitEvents.Count - 1); updateScore(); - - OnResultReverted(result); - } - - protected virtual void OnResultReverted(JudgementResult result) - { } private void updateScore() diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 257279bdc9..9633f5c533 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -76,12 +76,11 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; - public Action PostExpandAction; /// - /// Whether this can enter into an state. + /// An action to be invoked if this is clicked while in an expanded state. /// - public bool CanExpand = true; + public Action PostExpandAction; public readonly ScoreInfo Score; @@ -250,6 +249,7 @@ namespace osu.Game.Screens.Ranking { base.Size = value; + // Auto-size isn't used to avoid 1-frame issues and because the score panel is removed/re-added to the container. if (trackingContainer != null) trackingContainer.Size = value; } @@ -259,8 +259,7 @@ namespace osu.Game.Screens.Ranking { if (State == PanelState.Contracted) { - if (CanExpand) - State = PanelState.Expanded; + State = PanelState.Expanded; return true; } @@ -276,6 +275,15 @@ namespace osu.Game.Screens.Ranking private ScorePanelTrackingContainer trackingContainer; + /// + /// Creates a which this can reside inside. + /// The will track the size of this . + /// + /// + /// This is immediately added as a child of the . + /// + /// The . + /// If a already exists. public ScorePanelTrackingContainer CreateTrackingContainer() { if (trackingContainer != null) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 8f9064c2d1..9ebd7822c0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,12 +26,13 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + /// + /// An action to be invoked if a is clicked while in an expanded state. + /// public Action PostExpandAction; public readonly Bindable SelectedScore = new Bindable(); - public float CurrentScrollPosition => scroll.Current; - private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -47,16 +48,13 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. - Children = new Drawable[] + Child = flow = new Flow { - flow = new Flow - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(panel_spacing, 0), - AutoSizeAxes = Axes.Both, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, } }; } @@ -166,6 +164,10 @@ namespace osu.Game.Screens.Ranking private bool handleInput = true; + /// + /// Whether this or any of the s contained should handle scroll or click input. + /// Setting to false will also hide the scrollbar. + /// public bool HandleInput { get => handleInput; @@ -180,10 +182,24 @@ namespace osu.Game.Screens.Ranking public override bool PropagateNonPositionalInputSubTree => HandleInput && base.PropagateNonPositionalInputSubTree; + /// + /// Enumerates all s contained in this . + /// + /// public IEnumerable GetScorePanels() => flow.Select(t => t.Panel); + /// + /// Finds the corresponding to a . + /// + /// The to find the corresponding for. + /// The . public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel; + /// + /// Detaches a from its , allowing the panel to be moved elsewhere in the hierarchy. + /// + /// The to detach. + /// If is not a part of this . public void Detach(ScorePanel panel) { var container = flow.FirstOrDefault(t => t.Panel == panel); @@ -193,6 +209,11 @@ namespace osu.Game.Screens.Ranking container.Detach(); } + /// + /// Attaches a to its in this . + /// + /// The to attach. + /// If is not a part of this . public void Attach(ScorePanel panel) { var container = flow.FirstOrDefault(t => t.Panel == panel); diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index f6f26d0f8a..c8010d1c32 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -6,16 +6,27 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking { + /// + /// A which tracks the size of a , to which the can be added or removed. + /// public class ScorePanelTrackingContainer : CompositeDrawable { + /// + /// The that created this . + /// public readonly ScorePanel Panel; - public ScorePanelTrackingContainer(ScorePanel panel) + internal ScorePanelTrackingContainer(ScorePanel panel) { Panel = panel; Attach(); } + /// + /// Detaches the from this , removing it as a child. + /// This will continue tracking any size changes. + /// + /// If the is already detached. public void Detach() { if (InternalChildren.Count == 0) @@ -24,6 +35,10 @@ namespace osu.Game.Screens.Ranking RemoveInternal(Panel); } + /// + /// Attaches the to this , adding it as a child. + /// + /// If the is already attached. public void Attach() { if (InternalChildren.Count > 0) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index d9e5e1294a..ed98698411 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -1,6 +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.Diagnostics.CodeAnalysis; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,13 +12,16 @@ using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - internal class StatisticContainer : Container + /// + /// Wraps a to add a header and suitable layout for use in . + /// + internal class StatisticContainer : CompositeDrawable { - protected override Container Content => content; - - private readonly Container content; - - public StatisticContainer(string name) + /// + /// Creates a new . + /// + /// The to display. + public StatisticContainer([NotNull] StatisticItem item) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -50,7 +54,7 @@ namespace osu.Game.Screens.Ranking.Statistics { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = name, + Text = item.Name, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), } } @@ -58,11 +62,12 @@ namespace osu.Game.Screens.Ranking.Statistics }, new Drawable[] { - content = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 15 } + Margin = new MarginPadding { Top = 15 }, + Child = item.Content } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index a3ef5bf99e..e959ed24fc 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -30,9 +30,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Creates a new , to be displayed inside a in the results screen. /// - /// The name of this item. + /// The name of the item. /// The content to be displayed. - /// The of this row. This can be thought of as the column dimension of an encompassing . + /// The of this item. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) { Name = name; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 328b6933a0..c560cc9852 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -83,10 +83,7 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c.Name) - { - Child = c.Content - }).Cast().ToArray() + row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() }, ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), @@ -105,8 +102,8 @@ namespace osu.Game.Screens.Ranking.Statistics } } - protected override void PopIn() => this.FadeIn(); + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(); + protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); } } From 49bdd897758bf918224e8fafa8a0e02e9d0af70a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:54:09 +0900 Subject: [PATCH 056/170] Cleanup ReplayPlayer adjustments --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..cfcef5155d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -460,7 +460,7 @@ namespace osu.Game.Screens.Play { var score = new ScoreInfo { - Beatmap = Beatmap.Value.BeatmapInfo, + Beatmap = gameplayBeatmap.BeatmapInfo, Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), }; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index d7580ea271..8a925958fd 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,7 +1,6 @@ // 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.Screens; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -25,16 +24,18 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(score); } - protected override void GotoRanking() - { - this.Push(CreateResults(CreateScore())); - } - protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); - // protected override ScoreInfo CreateScore() - // { - // return score.ScoreInfo; - // } + protected override ScoreInfo CreateScore() + { + var baseScore = base.CreateScore(); + + // Since the replay score doesn't contain statistics, we'll pass them through here. + // We also have to pass in the beatmap to get the post-mod-application version. + score.ScoreInfo.Beatmap = baseScore.Beatmap; + score.ScoreInfo.HitEvents = baseScore.HitEvents; + + return score.ScoreInfo; + } } } From 740b01c049592483adb0dbc6f8e0b2d4cbf9e9d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:05:58 +0900 Subject: [PATCH 057/170] Add xmldoc --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 22ec023f58..9c1bc35169 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -138,6 +138,11 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + /// + /// Creates the that describes a . + /// + /// The to describe. + /// The . protected virtual HitEvent CreateHitEvent(JudgementResult result) => new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null); From 486b899e8f31c262b5ab911792bb5269c0edc880 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:11:29 +0900 Subject: [PATCH 058/170] Rename method --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b8725af856..44e8f343d5 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -309,7 +309,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 65f26c0647..8222eba339 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index cd4e699262..92b04e8397 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a325e641a4..f9c2b09be9 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -216,6 +216,6 @@ namespace osu.Game.Rulesets /// The to create the statistics for. The score is guaranteed to have populated. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); + public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index c560cc9852..efb9397a23 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Ranking.Statistics Spacing = new Vector2(30, 15), }; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatistics(newScore)) + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore)) { rows.Add(new GridContainer { From 4cb49cd606a57ec3aa4770ad8ab1caf5b11eeaee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:21:34 +0900 Subject: [PATCH 059/170] Add minimum height to the timing distribution graph --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 43de862007..9b46bea2cb 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Ranking.Statistics int maxCount = bins.Max(); var bars = new Drawable[total_timing_distribution_bins]; for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)bins[i] / maxCount }; + bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; Container axisFlow; From 2814433d7cf572a78218db8da6c055e7139e1962 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:22:07 +0900 Subject: [PATCH 060/170] Rename test file --- ...butionGraph.cs => TestSceneHitEventTimingDistributionGraph.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Ranking/{TestSceneTimingDistributionGraph.cs => TestSceneHitEventTimingDistributionGraph.cs} (100%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs similarity index 100% rename from osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs From 037bd3b46330bc2f2942bf761f0e883455b02d5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:47:55 +0900 Subject: [PATCH 061/170] Fix possible nullref --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 6 ++++++ osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 210abaef4e..8700fbeb42 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -29,6 +29,12 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); } + [Test] + public void TestNullScore() + { + loadPanel(null); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new StatisticsPanel diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index efb9397a23..cac2bf866b 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -62,6 +62,9 @@ namespace osu.Game.Screens.Ranking.Statistics var newScore = score.NewValue; + if (newScore == null) + return; + if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) content.Add(new MessagePlaceholder("Score has no statistics :(")); else From 0046cc08e913572866146cebdb512aed9f2ec725 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Fri, 19 Jun 2020 18:40:36 +0100 Subject: [PATCH 062/170] Add test cases for different mods and rates. Cleanup test scene. --- .../Gameplay/TestSceneStoryboardSamples.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 60911d6792..0803da6678 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -10,9 +10,8 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Audio; -using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -73,26 +72,39 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); } - [Test] - public void TestSamplePlaybackWithRateMods() + [TestCase(typeof(OsuModDoubleTime), 1.5)] + [TestCase(typeof(OsuModHalfTime), 0.75)] + [TestCase(typeof(ModWindUp), 1.5)] + [TestCase(typeof(ModWindDown), 0.75)] + [TestCase(typeof(OsuModDoubleTime), 2)] + [TestCase(typeof(OsuModHalfTime), 0.5)] + [TestCase(typeof(ModWindUp), 2)] + [TestCase(typeof(ModWindDown), 0.5)] + public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) { GameplayClockContainer gameplayContainer = null; TestDrawableStoryboardSample sample = null; - OsuModDoubleTime doubleTimeMod = null; + Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; - AddStep("create container", () => + switch (testedMod) { - var beatmap = Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + case ModRateAdjust m: + m.SpeedChange.Value = expectedRate; + break; - Add(gameplayContainer = new GameplayClockContainer(beatmap, new[] { doubleTimeMod = new OsuModDoubleTime() }, 0)); + case ModTimeRamp m: + m.SpeedChange.Value = expectedRate; + break; + } - SelectedMods.Value = new[] { doubleTimeMod }; - Beatmap.Value = new TestCustomSkinWorkingBeatmap(beatmap.Beatmap, gameplayContainer.GameplayClock, Audio); - }); - - AddStep("create storyboard sample", () => + AddStep("setup storyboard sample", () => { + Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); + SelectedMods.Value = new[] { testedMod }; + + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock @@ -101,7 +113,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == doubleTimeMod.SpeedChange.Value); + AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -138,8 +150,8 @@ namespace osu.Game.Tests.Gameplay { private readonly AudioManager audio; - public TestCustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio) - : base(beatmap, null, referenceClock, audio) + public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio) + : base(ruleset, null, audio) { this.audio = audio; } From 1d5084c35554939390b33db0c8d8191e0382724a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:11:12 +0200 Subject: [PATCH 063/170] Use {Initial,Final}Rate instead of SpeedChange --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 0803da6678..295fcc5b58 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Gameplay break; case ModTimeRamp m: - m.SpeedChange.Value = expectedRate; + m.InitialRate.Value = m.FinalRate.Value = expectedRate; break; } From 34476f6c2fa0d1e3ad922e46980fa9133302ee29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:12:17 +0200 Subject: [PATCH 064/170] Delegate to base in a more consistent manner --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 295fcc5b58..b30870d057 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Gameplay { } - public SampleChannel TestChannel => Channel; + public new SampleChannel Channel => base.Channel; } } } From 53861cdde81dec2aba2edfad6c92d89208a477ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:13:43 +0200 Subject: [PATCH 065/170] Privatise setter --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 04df46410e..60cb9b94a6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards.Drawables private readonly StoryboardSampleInfo sampleInfo; - protected SampleChannel Channel; + protected SampleChannel Channel { get; private set; } public override bool RemoveWhenNotAlive => false; From 470d5bfce3497c2c2a7048ee5db8a4a65249b153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:15:14 +0200 Subject: [PATCH 066/170] Invert if to reduce nesting --- .../Storyboards/Drawables/DrawableStoryboardSample.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 60cb9b94a6..8eaf9ac652 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -35,14 +35,13 @@ namespace osu.Game.Storyboards.Drawables private void load(IBindable beatmap, IBindable> mods) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); + if (Channel == null) + return; - if (Channel != null) - { - Channel.Volume.Value = sampleInfo.Volume / 100.0; + Channel.Volume.Value = sampleInfo.Volume / 100.0; - foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(Channel); - } + foreach (var mod in mods.Value.OfType()) + mod.ApplyToSample(Channel); } protected override void Update() From 3ede095b9c88e3d2f99a483c8b6171eb7f889f03 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 15:42:55 +0900 Subject: [PATCH 067/170] Apply refactorings from review --- osu.Game/Screens/Ranking/ResultsScreen.cs | 20 ++++++++------------ osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 133efd6e7b..193d975e42 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -236,13 +236,11 @@ namespace osu.Game.Screens.Ranking scorePanelList.Detach(expandedPanel); detachedPanelContainer.Add(expandedPanel); - // Move into its original location in the local container. + // Move into its original location in the local container first, then to the final location. var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos); - expandedPanel.MoveTo(origLocation); - expandedPanel.MoveToX(origLocation.X); - - // Move into the final location. - expandedPanel.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); + expandedPanel.MoveTo(origLocation) + .Then() + .MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint); // Hide contracted panels. foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) @@ -262,13 +260,11 @@ namespace osu.Game.Screens.Ranking detachedPanelContainer.Remove(detachedPanel); scorePanelList.Attach(detachedPanel); - // Move into its original location in the attached container. + // Move into its original location in the attached container first, then to the final location. var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); - detachedPanel.MoveTo(origLocation); - detachedPanel.MoveToX(origLocation.X); - - // Move into the final location. - detachedPanel.MoveToX(0, 150, Easing.OutQuint); + detachedPanel.MoveTo(origLocation) + .Then() + .MoveTo(new Vector2(0, origLocation.Y), 150, Easing.OutQuint); // Show contracted panels. foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 9ebd7822c0..0f8bc82ac0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Ranking /// If is not a part of this . public void Detach(ScorePanel panel) { - var container = flow.FirstOrDefault(t => t.Panel == panel); + var container = flow.SingleOrDefault(t => t.Panel == panel); if (container == null) throw new InvalidOperationException("Panel is not contained by the score panel list."); @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Ranking /// If is not a part of this . public void Attach(ScorePanel panel) { - var container = flow.FirstOrDefault(t => t.Panel == panel); + var container = flow.SingleOrDefault(t => t.Panel == panel); if (container == null) throw new InvalidOperationException("Panel is not contained by the score panel list."); From 21f776e51feafd8a06e397e90ef88bace4900d0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 15:48:42 +0900 Subject: [PATCH 068/170] Simplify/optimise heatmap point additoin --- .../Statistics/AccuracyHeatmap.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 10ca3eb9be..f05bfce8d7 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -175,25 +174,10 @@ namespace osu.Game.Rulesets.Osu.Statistics Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. - double minDist = double.PositiveInfinity; - HitPoint point = null; + int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); + int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); - for (int r = 0; r < points_per_dimension; r++) - { - for (int c = 0; c < points_per_dimension; c++) - { - float dist = Vector2.Distance(new Vector2(c, r), localPoint); - - if (dist < minDist) - { - minDist = dist; - point = (HitPoint)pointGrid.Content[r][c]; - } - } - } - - Debug.Assert(point != null); - point.Increment(); + ((HitPoint)pointGrid.Content[r][c]).Increment(); } private class HitPoint : Circle From 533d6e72eb5dc403408b0917af2ca431e2890e86 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:05:21 +0900 Subject: [PATCH 069/170] Refactor + comment angle math --- .../Statistics/AccuracyHeatmap.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f05bfce8d7..f8ab03aad0 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -167,11 +167,28 @@ namespace osu.Game.Rulesets.Osu.Statistics double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - // Convert the above into the local search space. + // Consider two objects placed horizontally, with the start on the left and the end on the right. + // The above calculated the angle between {end, start}, and the angle between {end, hitPoint}, in the form: + // +pi | 0 + // O --------- O -----> Note: Math.Atan2 has a range (-pi <= theta <= +pi) + // -pi | 0 + // E.g. If the hit point was directly above end, it would have an angle pi/2. + // + // It also calculated the angle separating hitPoint from the line joining {start, end}, that is anti-clockwise in the form: + // 0 | pi + // O --------- O -----> + // 2pi | pi + // + // However keep in mind that cos(0)=1 and cos(2pi)=1, whereas we actually want these values to appear on the left, so the x-coordinate needs to be inverted. + // Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted. + // + // We also need to apply the anti-clockwise rotation. + var rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); + var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); + Vector2 localCentre = new Vector2(points_per_dimension) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. - double localAngle = finalAngle + Math.PI - MathUtils.DegreesToRadians(rotation); // The angle inside the heatmap on which the closest point lies. - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; // Find the most relevant hit point. int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); From 9dbd230ad30f9f4e8564ae986c0f66f99415b131 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:06:52 +0900 Subject: [PATCH 070/170] Don't consider slider tails in timing distribution --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8222eba339..a164265290 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle).ToList()) + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) { RelativeSizeAxes = Axes.X, Height = 130 From 261adfc4e682973738960c6ace4c284f502811fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:38:41 +0900 Subject: [PATCH 071/170] Create a local playable beatmap instead --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../TestSceneAccuracyHeatmap.cs | 6 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +- .../Statistics/AccuracyHeatmap.cs | 7 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 3 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 2 - .../Ranking/Statistics/StatisticsPanel.cs | 67 ++++++++++++------- 9 files changed, 57 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f8fa5d4c40..411956e120 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -311,7 +311,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index f2a36ea017..49b469ba24 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333"), }, - object1 = new BorderCircle + object2 = new BorderCircle { Position = new Vector2(256, 192), Colour = Color4.Yellow, }, - object2 = new BorderCircle + object1 = new BorderCircle { Position = new Vector2(100, 300), }, @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestAccuracyHeatmap : AccuracyHeatmap { public TestAccuracyHeatmap(ScoreInfo score) - : base(score) + : base(score, new TestBeatmap(new OsuRuleset().RulesetInfo)) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a164265290..2ba2f4b097 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { @@ -203,7 +203,7 @@ namespace osu.Game.Rulesets.Osu RelativeSizeAxes = Axes.X, Height = 130 }), - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score) + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, Height = 130 diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f8ab03aad0..58089553a4 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; using osuTK; @@ -35,10 +36,12 @@ namespace osu.Game.Rulesets.Osu.Statistics private GridContainer pointGrid; private readonly ScoreInfo score; + private readonly IBeatmap playableBeatmap; - public AccuracyHeatmap(ScoreInfo score) + public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; + this.playableBeatmap = playableBeatmap; } [BackgroundDependencyLoader] @@ -146,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Statistics return; // Todo: This should probably not be done like this. - float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2; foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 92b04e8397..17d0800228 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f9c2b09be9..3a7f433a37 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -214,8 +214,9 @@ namespace osu.Game.Rulesets /// Creates the statistics for a to be displayed in the results screen. /// /// The to create the statistics for. The score is guaranteed to have populated. + /// The , converted for this with all relevant s applied. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => Array.Empty(); + public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c2bb75b8f3..d3b88e56ae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -463,7 +463,7 @@ namespace osu.Game.Screens.Play { var score = new ScoreInfo { - Beatmap = gameplayBeatmap.BeatmapInfo, + Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), }; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 8a925958fd..7f5c17a265 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Play var baseScore = base.CreateScore(); // Since the replay score doesn't contain statistics, we'll pass them through here. - // We also have to pass in the beatmap to get the post-mod-application version. - score.ScoreInfo.Beatmap = baseScore.Beatmap; score.ScoreInfo.HitEvents = baseScore.HitEvents; return score.ScoreInfo; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cac2bf866b..8aceaa335c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,14 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osuTK; @@ -22,6 +26,9 @@ namespace osu.Game.Screens.Ranking.Statistics protected override bool StartHidden => true; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private readonly Container content; private readonly LoadingSpinner spinner; @@ -71,37 +78,47 @@ namespace osu.Game.Screens.Ranking.Statistics { spinner.Show(); - var rows = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - }; + var localCancellationSource = loadCancellation = new CancellationTokenSource(); + IBeatmap playableBeatmap = null; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore)) + // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. + Task.Run(() => { - rows.Add(new GridContainer + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); + }, loadCancellation.Token).ContinueWith(t => + { + var rows = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 15), + }; + + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + { + rows.Add(new GridContainer { - row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) - .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) + .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } - LoadComponentAsync(rows, d => - { - if (Score.Value != newScore) - return; + LoadComponentAsync(rows, d => + { + if (Score.Value != newScore) + return; - spinner.Hide(); - content.Add(d); - }, (loadCancellation = new CancellationTokenSource()).Token); + spinner.Hide(); + content.Add(d); + }, localCancellationSource.Token); + }, localCancellationSource.Token); } } From 2b7fb2b71d352a97031b7477315c50f0a9f8ba70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:04:51 +0900 Subject: [PATCH 072/170] Rename to Position --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 4 ++-- osu.Game/Rulesets/Scoring/HitEvent.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 58089553a4..40bdeeaa88 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { - if (e.LastHitObject == null || e.PositionOffset == null) + if (e.LastHitObject == null || e.Position == null) continue; - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.Position.Value, radius); } } diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index ea2975a6c4..0ebbec62ba 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Scoring public readonly HitObject LastHitObject; /// - /// A position offset, if available, at the time of the event. + /// A position, if available, at the time of the event. /// [CanBeNull] - public readonly Vector2? PositionOffset; + public readonly Vector2? Position; /// /// Creates a new . @@ -46,14 +46,14 @@ namespace osu.Game.Rulesets.Scoring /// The . /// The that triggered the event. /// The previous . - /// A positional offset. - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) + /// A position corresponding to the event. + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) { TimeOffset = timeOffset; Result = result; HitObject = hitObject; LastHitObject = lastHitObject; - PositionOffset = positionOffset; + Position = position; } /// From 30aa6ec2d3ff6dc63c0bf9b3d33cb5aef820c35c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:05:41 +0900 Subject: [PATCH 073/170] Don't consider slider tails in accuracy heatmap --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 40bdeeaa88..cba753e003 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Todo: This should probably not be done like this. float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2; - foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) + foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle))) { if (e.LastHitObject == null || e.Position == null) continue; From 988baad16f296bb4f3df9f2c5e3478651057ceff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:20:43 +0900 Subject: [PATCH 074/170] Expand statistics to fill more of the screen --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 13 +++++++++---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Screens/Ranking/Statistics/StatisticsPanel.cs | 8 +++++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 411956e120..a27485dd06 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Mania new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2ba2f4b097..e488ba65c8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,7 +30,6 @@ using osu.Game.Scoring; using osu.Game.Skinning; using System; using System.Linq; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -201,13 +200,19 @@ namespace osu.Game.Rulesets.Osu new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), + } + }, + new StatisticRow + { + Columns = new[] + { new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 130 - }, new Dimension(GridSizeMode.Absolute, 130)), + Height = 250 + }), } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 17d0800228..156905fa9c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8aceaa335c..d2d2adb2f4 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -98,11 +98,17 @@ namespace osu.Game.Screens.Ranking.Statistics { rows.Add(new GridContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() + row.Columns?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() }, ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), From 5c4df2e32c0a1cd8f79f8d5e1e99e9fef6bfd228 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:20:42 +0900 Subject: [PATCH 075/170] Cancel load on dispose --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index d2d2adb2f4..651cdc4b0f 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -63,6 +63,7 @@ namespace osu.Game.Screens.Ranking.Statistics private void populateStatistics(ValueChangedEvent score) { loadCancellation?.Cancel(); + loadCancellation = null; foreach (var child in content) child.FadeOut(150).Expire(); @@ -131,5 +132,12 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); + + protected override void Dispose(bool isDisposing) + { + loadCancellation?.Cancel(); + + base.Dispose(isDisposing); + } } } From ff2f3a8484022a209b09d5c72343561e26e4128a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:32:04 +0900 Subject: [PATCH 076/170] Fix div-by-zero errors with autoplay --- ...estSceneHitEventTimingDistributionGraph.cs | 28 ++++++++++++++++--- .../HitEventTimingDistributionGraph.cs | 4 +++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index b34529cca7..7ca1fc842f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -15,7 +17,25 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - public TestSceneHitEventTimingDistributionGraph() + [Test] + public void TestManyDistributedEvents() + { + createTest(CreateDistributedHitEvents()); + } + + [Test] + public void TestZeroTimeOffset() + { + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + } + + [Test] + public void TestNoEvents() + { + createTest(new List()); + } + + private void createTest(List events) => AddStep("create test", () => { Children = new Drawable[] { @@ -24,14 +44,14 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(CreateDistributedHitEvents()) + new HitEventTimingDistributionGraph(events) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(400, 130) + Size = new Vector2(600, 130) } }; - } + }); public static List CreateDistributedHitEvents() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 9b46bea2cb..8ec7e863b1 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -58,8 +58,12 @@ namespace osu.Game.Screens.Ranking.Statistics return; int[] bins = new int[total_timing_distribution_bins]; + double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); + // Prevent div-by-0 by enforcing a minimum bin size + binSize = Math.Max(1, binSize); + foreach (var e in hitEvents) { int binOffset = (int)(e.TimeOffset / binSize); From 6afd6efdeba5fe08b9598c9de1c089a162bcc467 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:33:08 +0900 Subject: [PATCH 077/170] Return default beatmap if local beatmap can't be retrieved --- osu.Game/Beatmaps/BeatmapManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2cf3a21975..637833fb5d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -240,6 +240,9 @@ namespace osu.Game.Beatmaps beatmapInfo = QueryBeatmap(b => b.ID == info.ID); } + if (beatmapInfo == null) + return DefaultBeatmap; + lock (workingCache) { var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); From 983f0ada2da68527f5892ffd6ff0202c05d1d439 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:44:39 +0900 Subject: [PATCH 078/170] Increase number of points to ensure there's a centre --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index cba753e003..23539f3a12 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Statistics /// /// Number of rows/columns of points. - /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// ~4px per point @ 128x128 size (the contents of the are always square). 1089 total points. /// - private const int points_per_dimension = 32; + private const int points_per_dimension = 33; private const float rotation = 45; From 1aec1ea53fc9566e384f03fd96e5e5af72f3a2be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:45:44 +0900 Subject: [PATCH 079/170] Fix off-by-one causing auto to not be centred --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 23539f3a12..0d6d05292a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Statistics var rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); - Vector2 localCentre = new Vector2(points_per_dimension) / 2; + Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; From beb6e6ea88af3dbf213808accea49d247e358ce8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:00:13 +0900 Subject: [PATCH 080/170] Buffer the accuracy heatmap for performance --- .../Statistics/AccuracyHeatmap.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 0d6d05292a..6e1b6ef9b5 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; + private BufferedContainer bufferedGrid; private GridContainer pointGrid; private readonly ScoreInfo score; @@ -112,10 +113,16 @@ namespace osu.Game.Rulesets.Osu.Statistics } } }, - pointGrid = new GridContainer + bufferedGrid = new BufferedContainer { - RelativeSizeAxes = Axes.Both - } + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + BackgroundColour = Color4Extensions.FromHex("#202624").Opacity(0), + Child = pointGrid = new GridContainer + { + RelativeSizeAxes = Axes.Both + } + }, } }; @@ -198,6 +205,8 @@ namespace osu.Game.Rulesets.Osu.Statistics int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); ((HitPoint)pointGrid.Content[r][c]).Increment(); + + bufferedGrid.ForceRedraw(); } private class HitPoint : Circle From b3e200ee7face7f246ef82ee56faba604a216740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:00:35 +0900 Subject: [PATCH 081/170] Re-invert test --- osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index 49b469ba24..10d9d7ffde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333"), }, - object2 = new BorderCircle + object1 = new BorderCircle { Position = new Vector2(256, 192), Colour = Color4.Yellow, }, - object1 = new BorderCircle + object2 = new BorderCircle { Position = new Vector2(100, 300), }, From cb03e6faa9cf975719a8d3753ca063bb986ed8bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:09:47 +0900 Subject: [PATCH 082/170] Improve visual display of arrow --- .../Statistics/AccuracyHeatmap.cs | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 6e1b6ef9b5..94d47ecb32 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -74,41 +74,52 @@ namespace osu.Game.Rulesets.Osu.Statistics new Container { RelativeSizeAxes = Axes.Both, - Masking = true, Children = new Drawable[] { - new Box + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(1), + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + } + }, }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 10, - Height = 2f, + Height = 2, }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Y = -1, - Width = 2f, + Width = 2, Height = 10, } } From 1bf00e0c820c8c391a0b7574b8cfc28ea0374c43 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 23:22:49 +0900 Subject: [PATCH 083/170] Schedule continuation --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 651cdc4b0f..77f3bd7b5c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics Task.Run(() => { playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); - }, loadCancellation.Token).ContinueWith(t => + }, loadCancellation.Token).ContinueWith(t => Schedule(() => { var rows = new FillFlowContainer { @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Hide(); content.Add(d); }, localCancellationSource.Token); - }, localCancellationSource.Token); + }), localCancellationSource.Token); } } From e827b14abf5212aa0809256b4830456acda994e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 16:40:05 +0200 Subject: [PATCH 084/170] Add LayeredHitSamples skin config lookup --- osu.Game/Skinning/GlobalSkinConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs index 8774fe5a97..d405702ea5 100644 --- a/osu.Game/Skinning/GlobalSkinConfiguration.cs +++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum GlobalSkinConfiguration { - AnimationFramerate + AnimationFramerate, + LayeredHitSounds, } } From c5049b51c5835ab6950d1f9244d0d355157439a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 16:43:21 +0200 Subject: [PATCH 085/170] Mark normal-hitnormal sample as layered --- .../Objects/Legacy/ConvertHitObjectParser.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9e936c7717..77075b2abe 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -12,6 +12,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Objects.Legacy { @@ -356,7 +357,10 @@ namespace osu.Game.Rulesets.Objects.Legacy Bank = bankInfo.Normal, Name = HitSampleInfo.HIT_NORMAL, Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank + CustomSampleBank = bankInfo.CustomSampleBank, + // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. + // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds + IsLayered = type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal) } }; @@ -409,7 +413,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - internal class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo { private int customSampleBank; @@ -424,6 +428,15 @@ namespace osu.Game.Rulesets.Objects.Legacy Suffix = value.ToString(); } } + + /// + /// Whether this hit sample is layered. + /// + /// + /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled + /// using the skin config option. + /// + public bool IsLayered { get; set; } } private class FileHitSampleInfo : LegacyHitSampleInfo From c7d2ce12eb1cbf0cd6a8f5d1c72ac482d6ed62a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 18:52:15 +0200 Subject: [PATCH 086/170] Add failing test cases --- ...a-hitobject-beatmap-custom-sample-bank.osu | 10 ++++ ...a-hitobject-beatmap-normal-sample-bank.osu | 10 ++++ .../TestSceneManiaHitObjectSamples.cs | 49 +++++++++++++++ .../Gameplay/TestSceneHitObjectSamples.cs | 60 +++++++++++++++++++ .../hitobject-beatmap-custom-sample-bank.osu | 7 +++ .../Tests/Beatmaps/HitObjectSampleTest.cs | 12 +++- 6 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..4f8e1b68dd --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu new file mode 100644 index 0000000000..f22901e304 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,1,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs new file mode 100644 index 0000000000..0d726e1a50 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneManiaHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples))); + + /// + /// Tests that when a normal sample bank is used, the normal hitsound will be looked up. + /// + [Test] + public void TestManiaHitObjectNormalSampleBank() + { + const string expected_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, expected_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-normal-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + } + + /// + /// Tests that when a custom sample bank is used, layered hitsounds are not played + /// (only the sample from the custom bank is looked up). + /// + [Test] + public void TestManiaHitObjectCustomSampleBank() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index ef6efb7fec..737946e1e0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -6,6 +6,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -167,5 +168,64 @@ namespace osu.Game.Tests.Gameplay AssertBeatmapLookup(expected_sample); } + + /// + /// Tests that when a custom sample bank is used, both the normal and additional sounds will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBank() + { + string[] expectedSamples = + { + "normal-hitnormal2", + "normal-hitwhistle2" + }; + + SetupSkins(expectedSamples[0], expectedSamples[1]); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSamples[0]); + AssertUserLookup(expectedSamples[1]); + } + + /// + /// Tests that when a custom sample bank is used, but is disabled, + /// only the additional sound will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + + /// + /// Tests that when a normal sample bank is used and is disabled, + /// the normal sound will be looked up anyway. + /// + [Test] + public void TestHitObjectNormalSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitnormal"; + + SetupSkins(expected_sample, expected_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-sample.osu"); + + AssertBeatmapLookup(expected_sample); + } + + private void disableLayeredHitSounds() + => AddStep("set LayeredHitSounds to false", () => Skin.Configuration.ConfigDictionary[GlobalSkinConfiguration.LayeredHitSounds.ToString()] = "0"); } } diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..c50c921839 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index b4ce322165..ab4fb38657 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -24,6 +24,10 @@ namespace osu.Game.Tests.Beatmaps public abstract class HitObjectSampleTest : PlayerTestScene { protected abstract IResourceStore Resources { get; } + protected LegacySkin Skin { get; private set; } + + [Resolved] + private RulesetStore rulesetStore { get; set; } private readonly SkinInfo userSkinInfo = new SkinInfo(); @@ -64,6 +68,9 @@ namespace osu.Game.Tests.Beatmaps { using (var reader = new LineBufferedReader(Resources.GetStream($"Resources/SampleLookups/{filename}"))) currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); + + // populate ruleset for beatmap converters that require it to be present. + currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); }); }); } @@ -91,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps }; // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); + dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); }); } @@ -101,6 +108,9 @@ namespace osu.Game.Tests.Beatmaps protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); + protected void AssertNoLookup(string name) => AddAssert($"\"{name}\" not looked up", + () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && !userSkinResourceStore.PerformedLookups.Contains(name)); + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer { public ISkinSource SkinSource; From 8233f5fbc4e532cedc6a02b54d453ab106f5bf64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 22:44:35 +0200 Subject: [PATCH 087/170] Check skin option in skin transformers --- .../Skinning/ManiaLegacySkinTransformer.cs | 12 ++++++++++++ osu.Game/Skinning/LegacySkinTransformer.cs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 84e88a10be..e167135556 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -9,6 +9,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using System.Collections.Generic; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Rulesets.Mania.Skinning { @@ -129,6 +132,15 @@ namespace osu.Game.Rulesets.Mania.Skinning return this.GetAnimation(filename, true, true); } + public override SampleChannel GetSample(ISampleInfo sampleInfo) + { + // layered hit sounds never play in mania + if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } + public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 1131c93288..94a7a32f05 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { @@ -28,7 +29,17 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => Source.GetTexture(componentName); - public virtual SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(sampleInfo); + public virtual SampleChannel GetSample(ISampleInfo sampleInfo) + { + if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) + return Source.GetSample(sampleInfo); + + var playLayeredHitSounds = GetConfig(GlobalSkinConfiguration.LayeredHitSounds); + if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } public abstract IBindable GetConfig(TLookup lookup); } From 6d19fd936ef5e05fd0e95fc4aa12417e29d3f36c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 15:13:30 +0900 Subject: [PATCH 088/170] Change test scene to not inherit unused ScreenTestScene --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ac364b5233..f5c5a4d75c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -25,7 +25,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { [TestFixture] - public class TestSceneResultsScreen : ScreenTestScene + public class TestSceneResultsScreen : OsuManualInputManagerTestScene { private BeatmapManager beatmaps; From 6bcc693c2f8fc80649e5af944bf87c1e8c946145 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 15:21:23 +0900 Subject: [PATCH 089/170] Add ability to close statistics by clicking anywhere --- .../Visual/Ranking/TestSceneResultsScreen.cs | 40 +++++++++++++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 7 ++++ 2 files changed, 47 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index f5c5a4d75c..74808bc2f5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -20,6 +20,7 @@ using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Statistics; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -87,6 +88,45 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestShowHideStatisticsViaOutsideClick() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddUntilStep("expanded panel at the left of the screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150; + }); + + AddStep("click to right of panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(100, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddUntilStep("expanded panel in centre of screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1); + }); + } + [Test] public void TestShowHideStatistics() { diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 77f3bd7b5c..7f406331cd 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; @@ -129,6 +130,12 @@ namespace osu.Game.Screens.Ranking.Statistics } } + protected override bool OnClick(ClickEvent e) + { + ToggleVisibility(); + return true; + } + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); From 1387a9e2c63a0d46200a4ea7ef71e28cdb68c893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 16:57:17 +0900 Subject: [PATCH 090/170] Move all tournament tests to using placeholder data rather than reading from bracket --- .../Components/TestSceneMatchScoreDisplay.cs | 2 +- osu.Game.Tournament.Tests/LadderTestScene.cs | 146 ------------------ .../Screens/TestSceneLadderEditorScreen.cs | 2 +- .../Screens/TestSceneLadderScreen.cs | 2 +- .../Screens/TestSceneMapPoolScreen.cs | 2 +- .../Screens/TestSceneRoundEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingScreen.cs | 2 +- .../Screens/TestSceneTeamEditorScreen.cs | 2 +- .../Screens/TestSceneTeamIntroScreen.cs | 2 +- .../Screens/TestSceneTeamWinScreen.cs | 2 +- .../TournamentTestScene.cs | 141 +++++++++++++++++ 12 files changed, 151 insertions(+), 156 deletions(-) delete mode 100644 osu.Game.Tournament.Tests/LadderTestScene.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs index 77119f7a60..acd5d53310 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.Gameplay.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneMatchScoreDisplay : LadderTestScene + public class TestSceneMatchScoreDisplay : TournamentTestScene { [Cached(Type = typeof(MatchIPCInfo))] private MatchIPCInfo matchInfo = new MatchIPCInfo(); diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs deleted file mode 100644 index 2f4373679c..0000000000 --- a/osu.Game.Tournament.Tests/LadderTestScene.cs +++ /dev/null @@ -1,146 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Tournament.Models; -using osu.Game.Users; - -namespace osu.Game.Tournament.Tests -{ - [TestFixture] - public abstract class LadderTestScene : TournamentTestScene - { - [Cached] - protected LadderInfo Ladder { get; private set; } = new LadderInfo(); - - [Resolved] - private RulesetStore rulesetStore { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); - - Ruleset.BindTo(Ladder.Ruleset); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - TournamentMatch match = CreateSampleMatch(); - - Ladder.Rounds.Add(match.Round.Value); - Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value); - Ladder.Teams.Add(match.Team2.Value); - - Ladder.CurrentMatch.Value = match; - } - - public static TournamentMatch CreateSampleMatch() => new TournamentMatch - { - Team1 = - { - Value = new TournamentTeam - { - FlagName = { Value = "JP" }, - FullName = { Value = "Japan" }, - LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, - SeedingResults = - { - new SeedingResult - { - Mod = { Value = "NM" }, - Seed = { Value = 10 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 12345672, - Seed = { Value = 24 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 12 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 16 }, - } - } - }, - new SeedingResult - { - Mod = { Value = "DT" }, - Seed = { Value = 5 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 3 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 6 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 12 }, - } - } - } - }, - Players = - { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, - } - } - }, - Team2 = - { - Value = new TournamentTeam - { - FlagName = { Value = "US" }, - FullName = { Value = "United States" }, - Players = - { - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - } - } - }, - Round = - { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } - } - }; - - public static BeatmapInfo CreateSampleBeatmapInfo() => - new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; - } -} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs index a45c5de2bd..bceb3e6b74 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderEditorScreen : LadderTestScene + public class TestSceneLadderEditorScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs index 2be0564c82..c4c100d506 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Ladder; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderScreen : LadderTestScene + public class TestSceneLadderScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index a4538be384..f4032fdd54 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Tournament.Screens.MapPool; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneMapPoolScreen : LadderTestScene + public class TestSceneMapPoolScreen : TournamentTestScene { private MapPoolScreen screen; diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs index e15ac416b0..5c2b59df3a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneRoundEditorScreen : LadderTestScene + public class TestSceneRoundEditorScreen : TournamentTestScene { public TestSceneRoundEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs index 8d12d5393d..2722021216 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingEditorScreen : LadderTestScene + public class TestSceneSeedingEditorScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index 4269f8f56a..d414d8e36e 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingScreen : LadderTestScene + public class TestSceneSeedingScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs index 097bad4a02..fc6574ec8a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamEditorScreen : LadderTestScene + public class TestSceneTeamEditorScreen : TournamentTestScene { public TestSceneTeamEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index e36b594ff2..b3f78c92d9 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamIntroScreen : LadderTestScene + public class TestSceneTeamIntroScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 1a2faa76c1..6873fb0f4b 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamWin; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamWinScreen : LadderTestScene + public class TestSceneTeamWinScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 18ac3230da..a7b141cf43 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -1,13 +1,154 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Tests.Visual; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Users; namespace osu.Game.Tournament.Tests { public abstract class TournamentTestScene : OsuTestScene { + [Cached] + protected LadderInfo Ladder { get; private set; } = new LadderInfo(); + + [Resolved] + private RulesetStore rulesetStore { get; set; } + + [Cached] + protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); + + Ruleset.BindTo(Ladder.Ruleset); + Dependencies.CacheAs(new StableInfo(storage)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TournamentMatch match = CreateSampleMatch(); + + Ladder.Rounds.Add(match.Round.Value); + Ladder.Matches.Add(match); + Ladder.Teams.Add(match.Team1.Value); + Ladder.Teams.Add(match.Team2.Value); + + Ladder.CurrentMatch.Value = match; + } + + public static TournamentMatch CreateSampleMatch() => new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; + + public static BeatmapInfo CreateSampleBeatmapInfo() => + new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; + protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner(); public class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner From 92e272ebb6f301a5b896ea2a3166d2cbf761be99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 16:57:40 +0900 Subject: [PATCH 091/170] Remove unnecessary prefixes --- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 23fcb01db7..2c539cdd43 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament public const float STREAM_AREA_WIDTH = 1366; - public const double REQUIRED_WIDTH = TournamentSceneManager.CONTROL_AREA_WIDTH * 2 + TournamentSceneManager.STREAM_AREA_WIDTH; + public const double REQUIRED_WIDTH = CONTROL_AREA_WIDTH * 2 + STREAM_AREA_WIDTH; [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); From eb3e1b2b2698ab5bc843bd5a90c945ca01cd7d5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 17:03:22 +0900 Subject: [PATCH 092/170] Fix incorrect inheritance on remaining test scene --- .../Components/TestSceneTournamentBeatmapPanel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 77fa411058..bc32a12ab7 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -8,12 +8,11 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneTournamentBeatmapPanel : OsuTestScene + public class TestSceneTournamentBeatmapPanel : TournamentTestScene { [Resolved] private IAPIProvider api { get; set; } From 5fd6246d1b5e3b0d650cf4117d10df84b6d9f5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 17:57:07 +0900 Subject: [PATCH 093/170] Fix remaining test scenes --- .../Screens/TestSceneTeamWinScreen.cs | 10 ++-------- osu.Game.Tournament.Tests/TournamentTestScene.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 6873fb0f4b..3ca58dcaf4 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -4,25 +4,19 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.TeamWin; namespace osu.Game.Tournament.Tests.Screens { public class TestSceneTeamWinScreen : TournamentTestScene { - [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - [BackgroundDependencyLoader] private void load() { - var match = new TournamentMatch(); - match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); - match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); + var match = Ladder.CurrentMatch.Value; + match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); match.Completed.Value = true; - ladder.CurrentMatch.Value = match; Add(new TeamWinScreen { diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index a7b141cf43..d22da25f9d 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -31,14 +31,6 @@ namespace osu.Game.Tournament.Tests { Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); - Ruleset.BindTo(Ladder.Ruleset); - Dependencies.CacheAs(new StableInfo(storage)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - TournamentMatch match = CreateSampleMatch(); Ladder.Rounds.Add(match.Round.Value); @@ -47,6 +39,9 @@ namespace osu.Game.Tournament.Tests Ladder.Teams.Add(match.Team2.Value); Ladder.CurrentMatch.Value = match; + + Ruleset.BindTo(Ladder.Ruleset); + Dependencies.CacheAs(new StableInfo(storage)); } public static TournamentMatch CreateSampleMatch() => new TournamentMatch @@ -55,6 +50,7 @@ namespace osu.Game.Tournament.Tests { Value = new TournamentTeam { + Acronym = { Value = "JPN" }, FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, LastYearPlacing = { Value = 10 }, @@ -128,6 +124,7 @@ namespace osu.Game.Tournament.Tests { Value = new TournamentTeam { + Acronym = { Value = "USA" }, FlagName = { Value = "US" }, FullName = { Value = "United States" }, Players = From 9e1bf71233b66a88b2419339db6cf181a7705534 Mon Sep 17 00:00:00 2001 From: Viktor Rosvall Date: Wed, 24 Jun 2020 11:29:38 +0200 Subject: [PATCH 094/170] Added text explaining a second copy will be made --- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 20494829ae..272f9566d5 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?"; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; From 6bc507d49ed2bc76cbbaffc5dc9ddac087a5bd8f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Jun 2020 18:53:52 +0900 Subject: [PATCH 095/170] Increase coordinate parsing limits --- osu.Game/Beatmaps/Formats/Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index c3efb8c760..c4795a6931 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.Formats /// public static class Parsing { - public const int MAX_COORDINATE_VALUE = 65536; + public const int MAX_COORDINATE_VALUE = 131072; public const double MAX_PARSE_VALUE = int.MaxValue; From 0d3bc1ac29685628e57cceddd94d525b6ef48ea2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 22:29:30 +0900 Subject: [PATCH 096/170] Add basic heatmap colour scaling based on peak value --- .../Statistics/AccuracyHeatmap.cs | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 94d47ecb32..eeb8b519ca 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; + /// + /// The highest count of any point currently being displayed. + /// + protected float PeakValue { get; private set; } + public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; @@ -152,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Statistics ? HitPointType.Hit : HitPointType.Miss; - var point = new HitPoint(pointType) + var point = new HitPoint(pointType, this) { Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }; @@ -215,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Statistics int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); - ((HitPoint)pointGrid.Content[r][c]).Increment(); + PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); bufferedGrid.ForceRedraw(); } @@ -223,21 +229,56 @@ namespace osu.Game.Rulesets.Osu.Statistics private class HitPoint : Circle { private readonly HitPointType pointType; + private readonly AccuracyHeatmap heatmap; - public HitPoint(HitPointType pointType) + public override bool IsPresent => count > 0; + + public HitPoint(HitPointType pointType, AccuracyHeatmap heatmap) { this.pointType = pointType; + this.heatmap = heatmap; RelativeSizeAxes = Axes.Both; - Alpha = 0; + Alpha = 1; } - public void Increment() + private int count; + + /// + /// Increment the value of this point by one. + /// + /// The value after incrementing. + public int Increment() { - if (Alpha < 1) - Alpha += 0.1f; - else if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(0.1f); + return ++count; + } + + protected override void Update() + { + base.Update(); + + // the point at which alpha is saturated and we begin to adjust colour lightness. + const float lighten_cutoff = 0.95f; + + // the amount of lightness to attribute regardless of relative value to peak point. + const float non_relative_portion = 0.2f; + + float amount = 0; + + // give some amount of alpha regardless of relative count + amount += non_relative_portion * Math.Min(1, count / 10f); + + // add relative portion + amount += (1 - non_relative_portion) * (count / heatmap.PeakValue); + + // apply easing + amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); + + Debug.Assert(amount <= 1); + + Alpha = Math.Min(amount / lighten_cutoff, 1); + if (pointType == HitPointType.Hit) + Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff)); } } From 4c283476866d7cf9d276a8e3219fa3a069f7604f Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 15:34:20 +0100 Subject: [PATCH 097/170] Adjust sample rate by UserPlaybackRate --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..71a97da5c2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Play public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - protected GameplayClockContainer GameplayClockContainer { get; private set; } + public GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8eaf9ac652..3dc7eab968 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -32,7 +34,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods) + private void load(IBindable beatmap, IBindable> mods, Player player) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); if (Channel == null) @@ -42,6 +44,8 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) mod.ApplyToSample(Channel); + + Channel.AddAdjustment(AdjustableProperty.Frequency, player.GameplayClockContainer.UserPlaybackRate); } protected override void Update() From 992ada46700d6f6420a6f22b0904376bb7ec7c58 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 16:17:18 +0100 Subject: [PATCH 098/170] Revert UserPlaybackRate changes --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 3dc7eab968..5aeadb2e1f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -34,7 +34,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods, Player player) + private void load(IBindable beatmap, IBindable> mods) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); if (Channel == null) @@ -44,8 +44,6 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) mod.ApplyToSample(Channel); - - Channel.AddAdjustment(AdjustableProperty.Frequency, player.GameplayClockContainer.UserPlaybackRate); } protected override void Update() From f2a48a339ea7e643ab5156764f99450d53f30bf2 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 16:33:19 +0100 Subject: [PATCH 099/170] Remove unused usings --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 5aeadb2e1f..8eaf9ac652 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { From ac5cd8f25a3a1f08c3adc3fe562e0fbdc5b0585c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:40:26 +0900 Subject: [PATCH 100/170] Fix colours with 0 alpha being invisible in legacy skins --- osu.Game/Skinning/LegacySkin.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b2b723440..bbc64a24e7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -271,7 +271,15 @@ namespace osu.Game.Skinning } private IBindable getCustomColour(IHasCustomColours source, string lookup) - => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + { + if (!source.CustomColours.TryGetValue(lookup, out var col)) + return null; + + if (col.A == 0) + col.A = 1; + + return new Bindable(col); + } private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; From 4c601af207c3374eb37fd588e989e2ee9f129acb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:43:14 +0900 Subject: [PATCH 101/170] Match condition --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bbc64a24e7..be6d694efe 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -275,7 +275,7 @@ namespace osu.Game.Skinning if (!source.CustomColours.TryGetValue(lookup, out var col)) return null; - if (col.A == 0) + if (col.A <= 0 || col.A >= 255) col.A = 1; return new Bindable(col); From 8b84aa454d61cd61390fcce8f8f79be4e8ab1ebb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:43:56 +0900 Subject: [PATCH 102/170] Fix incorrect upper bound --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index be6d694efe..ea630b9b8d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -275,7 +275,7 @@ namespace osu.Game.Skinning if (!source.CustomColours.TryGetValue(lookup, out var col)) return null; - if (col.A <= 0 || col.A >= 255) + if (col.A <= 0 || col.A >= 1) col.A = 1; return new Bindable(col); From 4ff9a910121d85957cdb94011a8ed5dba653c3ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 14:15:26 +0900 Subject: [PATCH 103/170] Adjust at parse time instead --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 7 ++++++- osu.Game/Skinning/LegacySkin.cs | 10 +--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6406bd88a5..a0e83554a3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -103,7 +103,12 @@ namespace osu.Game.Beatmaps.Formats try { - colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); + byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; + + if (alpha == 0) + alpha = 255; + + colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ea630b9b8d..0b2b723440 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -271,15 +271,7 @@ namespace osu.Game.Skinning } private IBindable getCustomColour(IHasCustomColours source, string lookup) - { - if (!source.CustomColours.TryGetValue(lookup, out var col)) - return null; - - if (col.A <= 0 || col.A >= 1) - col.A = 1; - - return new Bindable(col); - } + => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; From 531a69650f390ef3825f449daf46f0a32149895a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 14:22:40 +0900 Subject: [PATCH 104/170] Add test --- osu.Game.Tests/Resources/skin-zero-alpha-colour.ini | 5 +++++ osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 osu.Game.Tests/Resources/skin-zero-alpha-colour.ini diff --git a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini new file mode 100644 index 0000000000..3c0dae6b13 --- /dev/null +++ b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini @@ -0,0 +1,5 @@ +[General] +Version: latest + +[Colours] +Combo1: 255,255,255,0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index aedf26ee75..c408d2f182 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -108,5 +108,15 @@ namespace osu.Game.Tests.Skins using (var stream = new LineBufferedReader(resStream)) Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } + + [Test] + public void TestDecodeColourWithZeroAlpha() + { + var decoder = new LegacySkinDecoder(); + + using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f)); + } } } From fd13c0a6ddb5c8ea3260e64f956af86ab63bee5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 18:44:04 +0900 Subject: [PATCH 105/170] Standardise line thickness --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index eeb8b519ca..89707b3ebb 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; + private const float line_thickness = 2; + /// /// The highest count of any point currently being displayed. /// @@ -69,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Statistics RelativeSizeAxes = Axes.Both, Size = new Vector2(inner_portion), Masking = true, - BorderThickness = 2f, + BorderThickness = line_thickness, BorderColour = Color4.White, Child = new Box { @@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, + Width = line_thickness, Rotation = -rotation, Alpha = 0.3f, }, @@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, + Width = line_thickness, Rotation = rotation }, } From c095753f2444ce052b8fa7588c9cfb1eb0956cef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 19:02:04 +0900 Subject: [PATCH 106/170] Add better line smoothing --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 89707b3ebb..20adbc1c02 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -98,9 +98,10 @@ namespace osu.Game.Rulesets.Osu.Statistics { Anchor = Anchor.Centre, Origin = Anchor.Centre, + EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = line_thickness, + Width = line_thickness / 2, Rotation = -rotation, Alpha = 0.3f, }, @@ -108,9 +109,10 @@ namespace osu.Game.Rulesets.Osu.Statistics { Anchor = Anchor.Centre, Origin = Anchor.Centre, + EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = line_thickness, + Width = line_thickness / 2, // adjust for edgesmoothness Rotation = rotation }, } @@ -121,13 +123,15 @@ namespace osu.Game.Rulesets.Osu.Statistics Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 10, - Height = 2, + EdgeSmoothness = new Vector2(1), + Height = line_thickness / 2, // adjust for edgesmoothness }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Width = 2, + EdgeSmoothness = new Vector2(1), + Width = line_thickness / 2, // adjust for edgesmoothness Height = 10, } } From d7742766d054ca1d036985b6ca6c62ab946851c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 19:47:23 +0900 Subject: [PATCH 107/170] Add key/press repeat support to carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 63 ++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e174c46610..6611955cce 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -452,32 +452,49 @@ namespace osu.Game.Screens.Select /// public void ScrollToSelected() => scrollPositionCache.Invalidate(); + #region Key / button selection logic + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) { case Key.Left: - SelectNext(-1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(-1, true), e.Key); return true; case Key.Right: - SelectNext(1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(1, true), e.Key); return true; } return false; } + protected override void OnKeyUp(KeyUpEvent e) + { + switch (e.Key) + { + case Key.Left: + case Key.Right: + endRepeatSelection(e.Key); + break; + } + + base.OnKeyUp(e); + } + public bool OnPressed(GlobalAction action) { switch (action) { case GlobalAction.SelectNext: - SelectNext(1, false); + beginRepeatSelection(() => SelectNext(1, false), action); return true; case GlobalAction.SelectPrevious: - SelectNext(-1, false); + beginRepeatSelection(() => SelectNext(-1, false), action); return true; } @@ -486,8 +503,46 @@ namespace osu.Game.Screens.Select public void OnReleased(GlobalAction action) { + switch (action) + { + case GlobalAction.SelectNext: + case GlobalAction.SelectPrevious: + endRepeatSelection(action); + break; + } } + private const double repeat_interval = 120; + + private ScheduledDelegate repeatDelegate; + private object lastRepeatSource; + + /// + /// Begin repeating the specified selection action. + /// + /// The action to perform. + /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). + private void beginRepeatSelection(Action action, object source) + { + endRepeatSelection(); + + lastRepeatSource = source; + Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + } + + private void endRepeatSelection(object source = null) + { + // only the most recent source should be able to cancel the current action. + if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) + return; + + repeatDelegate?.Cancel(); + repeatDelegate = null; + lastRepeatSource = null; + } + + #endregion + protected override void Update() { base.Update(); From c36d9d4fc3fa87aef05600dae76ada50bf5c076d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 20:01:29 +0900 Subject: [PATCH 108/170] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 2f12194ede..073d75692e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -17,11 +17,12 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestSceneBeatmapCarousel : OsuTestScene + public class TestSceneBeatmapCarousel : OsuManualInputManagerTestScene { private TestBeatmapCarousel carousel; private RulesetStore rulesets; @@ -39,6 +40,43 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestKeyRepeat() + { + loadBeatmaps(); + advanceSelection(false); + + AddStep("press down arrow", () => InputManager.PressKey(Key.Down)); + + BeatmapInfo selection = null; + + checkSelectionIterating(true); + + AddStep("press up arrow", () => InputManager.PressKey(Key.Up)); + + checkSelectionIterating(true); + + AddStep("release down arrow", () => InputManager.ReleaseKey(Key.Down)); + + checkSelectionIterating(true); + + AddStep("release up arrow", () => InputManager.ReleaseKey(Key.Up)); + + checkSelectionIterating(false); + + void checkSelectionIterating(bool isIterating) + { + for (int i = 0; i < 3; i++) + { + AddStep("store selection", () => selection = carousel.SelectedBeatmap); + if (isIterating) + AddUntilStep("selection changed", () => carousel.SelectedBeatmap != selection); + else + AddUntilStep("selection not changed", () => carousel.SelectedBeatmap == selection); + } + } + } + [Test] public void TestRecommendedSelection() { From d704a4597dc60d882d4b8f54d6245cbca81ab67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 21:33:02 +0900 Subject: [PATCH 109/170] Use existing helper function for key repeat --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6611955cce..ad19c9661f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -19,6 +19,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; @@ -527,7 +528,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); } private void endRepeatSelection(object source = null) From 65a2fc3bfc052a150d67d0b14a19fece40cf47cb Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:53:14 +0100 Subject: [PATCH 110/170] Revert access modifier --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 71a97da5c2..83991ad027 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Play public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - public GameplayClockContainer GameplayClockContainer { get; private set; } + protected GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } From e3d654d33f2f5d1daeed1f72e263e1e943104ed3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:02 +0900 Subject: [PATCH 111/170] Cleanup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ad19c9661f..c58b34f9f2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -513,8 +513,6 @@ namespace osu.Game.Screens.Select } } - private const double repeat_interval = 120; - private ScheduledDelegate repeatDelegate; private object lastRepeatSource; From 1b4c31a84f3e2e4654ea065f0dee3095bcae47c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:08 +0900 Subject: [PATCH 112/170] Remove double schedule --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c58b34f9f2..5fbe917943 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -526,7 +526,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); + repeatDelegate = this.BeginKeyRepeat(Scheduler, action); } private void endRepeatSelection(object source = null) From 8f6d52550f6ddfb46a7f9f8384fa1bd7fbc5c34b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:32:13 +0900 Subject: [PATCH 113/170] Fix potential exception if button is pressed before selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5fbe917943..71ccd6fada 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,6 +279,9 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { + if (selectedBeatmap == null) + return; + if (beatmapSets.All(s => s.Filtered.Value)) return; From 099416b4c3d981d7e844ca4b57833597f8e7715a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 21:03:34 +0900 Subject: [PATCH 114/170] Move check inside next difficulty selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 71ccd6fada..6f913a3177 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,9 +279,6 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (selectedBeatmap == null) - return; - if (beatmapSets.All(s => s.Filtered.Value)) return; @@ -305,6 +302,9 @@ namespace osu.Game.Screens.Select private void selectNextDifficulty(int direction) { + if (selectedBeatmap == null) + return; + var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); int index = unfilteredDifficulties.IndexOf(selectedBeatmap); From 97a212a7f6a35a17a098e9ae11ff2a9b27833666 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 14:32:01 +0200 Subject: [PATCH 115/170] Hide red tint based on "Show health display even when you can't fail" setting --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 25 ++++++++++++++++++++++- osu.Game/Screens/Play/HUDOverlay.cs | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index a49aa89a7c..6fda5a1214 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + public readonly Bindable HUDEnabled = new Bindable(); private readonly Bindable enabled = new Bindable(); private readonly Container boxes; @@ -74,7 +75,7 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); + enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue ? true : false), true); } protected override void LoadComplete() @@ -105,6 +106,28 @@ namespace osu.Game.Screens.Play.HUD enabled.Value = false; } + /// + /// Tries to fade based on "Fade playfield when health is low" setting + /// + /// Duration of the fade + /// Type of easing + /// True when you want to fade in, false when you want to fade out + public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) + { + if (HUDEnabled.Value) + { + if (fadeIn) + { + if (enabled.Value) + this.FadeIn(fadeDuration, easing); + } + else + this.FadeOut(fadeDuration, easing); + } + else + this.FadeOut(fadeDuration, easing); + } + protected override void Update() { double target = Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 5114efd9a9..73b93582ef 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Diagnostics.Runtime.Interop; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -153,6 +154,8 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); + + FailingLayer.HUDEnabled.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); @@ -168,11 +171,13 @@ namespace osu.Game.Screens.Play if (healthBar.NewValue) { HealthDisplay.FadeIn(fade_duration, fade_easing); + FailingLayer.TryToFade(fade_duration, fade_easing, true); topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { HealthDisplay.FadeOut(fade_duration, fade_easing); + FailingLayer.TryToFade(fade_duration, fade_easing, false); topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From efeaa1cc10ddd6d0b80ebd16651908a4be7c818a Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 14:58:42 +0200 Subject: [PATCH 116/170] Make some changes, fix and add tests --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 27 +++++++++++++++++++ osu.Game/Screens/Play/HUD/FailingLayer.cs | 3 ++- osu.Game/Screens/Play/HUDOverlay.cs | 2 -- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index a95e806862..83d9e888f1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; @@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; + private Bindable enabledHUD = new Bindable(); + [Resolved] private OsuConfigManager config { get; set; } @@ -24,8 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { Child = layer = new FailingLayer(); layer.BindHealthProcessor(new DrainingHealthProcessor(1)); + layer.HUDEnabled.BindTo(enabledHUD); }); + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -69,5 +74,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); } + + [Test] + public void TestLayerVisibilityWithDifferentOptions() + { + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + + AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer fade is visible", () => layer.IsPresent); + } } } diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 6fda5a1214..d982764c30 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -75,7 +75,8 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue ? true : false), true); + enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + HUDEnabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 73b93582ef..d4c548dce7 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -171,13 +171,11 @@ namespace osu.Game.Screens.Play if (healthBar.NewValue) { HealthDisplay.FadeIn(fade_duration, fade_easing); - FailingLayer.TryToFade(fade_duration, fade_easing, true); topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { HealthDisplay.FadeOut(fade_duration, fade_easing); - FailingLayer.TryToFade(fade_duration, fade_easing, false); topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From 798e8e7a8deea5d1ac665bc9491604c0f082e5ed Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 15:12:01 +0200 Subject: [PATCH 117/170] Fix CI fail --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 83d9e888f1..3eda47627b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; - private Bindable enabledHUD = new Bindable(); + private readonly Bindable enabledHUD = new Bindable(); [Resolved] private OsuConfigManager config { get; set; } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d4c548dce7..b55a93db1f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Diagnostics.Runtime.Interop; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; From bd1f38cc3ef41c0ca8bbd586c81b1cbf8b6a6d9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 23:21:44 +0900 Subject: [PATCH 118/170] Fix crash due to unsafe mod deserialisation --- .../Online/TestAPIModSerialization.cs | 59 ++++++++++++++++++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs index d9318aa822..5948582d77 100644 --- a/osu.Game.Tests/Online/TestAPIModSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModSerialization.cs @@ -49,9 +49,32 @@ namespace osu.Game.Tests.Online Assert.That(converted.TestSetting.Value, Is.EqualTo(2)); } + [Test] + public void TestDeserialiseTimeRampMod() + { + // Create the mod with values different from default. + var apiMod = new APIMod(new TestModTimeRamp + { + AdjustPitch = { Value = false }, + InitialRate = { Value = 1.25 }, + FinalRate = { Value = 0.25 } + }); + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + var converted = (TestModTimeRamp)deserialised.ToMod(new TestRuleset()); + + Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false)); + Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25)); + Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); + } + private class TestRuleset : Ruleset { - public override IEnumerable GetModsFor(ModType type) => new[] { new TestMod() }; + public override IEnumerable GetModsFor(ModType type) => new Mod[] + { + new TestMod(), + new TestModTimeRamp(), + }; public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); @@ -78,5 +101,39 @@ namespace osu.Game.Tests.Online Precision = 0.01, }; } + + private class TestModTimeRamp : ModTimeRamp + { + public override string Name => "Test Mod"; + public override string Acronym => "TMTR"; + public override double ScoreMultiplier => 1; + + [SettingSource("Initial rate", "The starting speed of the track")] + public override BindableNumber InitialRate { get; } = new BindableDouble + { + MinValue = 1, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; + + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 1, + Default = 0.5, + Value = 0.5, + Precision = 0.01, + }; + + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public override BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index cbd07efa97..839d97f04e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Mods private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { // remove existing old adjustment - track.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) From c233dc476800e7df39ee242a7515cc6bc8beb5e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 27 Jun 2020 00:16:16 +0900 Subject: [PATCH 119/170] Add some global error handling --- osu.Game/Screens/Multi/RoomManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 4d6ac46c84..b8c969a845 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -166,8 +166,16 @@ namespace osu.Game.Screens.Multi var r = listing[i]; r.Position.Value = i; - update(r, r); - addRoom(r); + try + { + update(r, r); + addRoom(r); + } + catch (Exception ex) + { + Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + rooms.Remove(r); + } } RoomsUpdated?.Invoke(); From e8d36bc3cbb5c6aa2c0f0fd6dde7d18d34799590 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 27 Jun 2020 00:19:22 +0900 Subject: [PATCH 120/170] Don't trigger the same exception multiple times --- osu.Game/Screens/Multi/RoomManager.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index b8c969a845..5083fb2ee3 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -142,6 +143,8 @@ namespace osu.Game.Screens.Multi joinedRoom = null; } + private readonly List roomsFailedUpdate = new List(); + /// /// Invoked when the listing of all s is received from the server. /// @@ -173,7 +176,14 @@ namespace osu.Game.Screens.Multi } catch (Exception ex) { - Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + Debug.Assert(r.RoomID.Value != null); + + if (!roomsFailedUpdate.Contains(r.RoomID.Value.Value)) + { + Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + roomsFailedUpdate.Add(r.RoomID.Value.Value); + } + rooms.Remove(r); } } From 3783fe8d6a2797925e4ad77525c676226fcf9bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:03:41 +0200 Subject: [PATCH 121/170] Rename fields for clarity --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 14 +++++------ osu.Game/Screens/Play/HUD/FailingLayer.cs | 23 ++++++++++--------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 3eda47627b..1c55595c97 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; - private readonly Bindable enabledHUD = new Bindable(); + private readonly Bindable showHealth = new Bindable(); [Resolved] private OsuConfigManager config { get; set; } @@ -27,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { Child = layer = new FailingLayer(); layer.BindHealthProcessor(new DrainingHealthProcessor(1)); - layer.HUDEnabled.BindTo(enabledHUD); + layer.ShowHealth.BindTo(showHealth); }); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -80,19 +80,19 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("set health to 0.10", () => layer.Current.Value = 0.1); - AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("don't show health", () => showHealth.Value = false); AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("don't show health", () => showHealth.Value = false); AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is visible", () => layer.IsPresent); } diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index d982764c30..e8c99c2ed8 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,11 +31,12 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; - public readonly Bindable HUDEnabled = new Bindable(); - private readonly Bindable enabled = new Bindable(); + public readonly Bindable ShowHealth = new Bindable(); + + private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; - private Bindable configEnabled; + private Bindable fadePlayfieldWhenHealthLowSetting; private HealthProcessor healthProcessor; public FailingLayer() @@ -74,9 +75,9 @@ namespace osu.Game.Screens.Play.HUD { boxes.Colour = color.Red; - configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); - HUDEnabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + fadePlayfieldWhenHealthLow.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + ShowHealth.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); } protected override void LoadComplete() @@ -98,13 +99,13 @@ namespace osu.Game.Screens.Play.HUD if (LoadState < LoadState.Ready) return; - enabled.UnbindBindings(); + fadePlayfieldWhenHealthLow.UnbindBindings(); // Don't display ever if the ruleset is not using a draining health display. if (healthProcessor is DrainingHealthProcessor) - enabled.BindTo(configEnabled); + fadePlayfieldWhenHealthLow.BindTo(fadePlayfieldWhenHealthLowSetting); else - enabled.Value = false; + fadePlayfieldWhenHealthLow.Value = false; } /// @@ -115,11 +116,11 @@ namespace osu.Game.Screens.Play.HUD /// True when you want to fade in, false when you want to fade out public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) { - if (HUDEnabled.Value) + if (ShowHealth.Value) { if (fadeIn) { - if (enabled.Value) + if (fadePlayfieldWhenHealthLow.Value) this.FadeIn(fadeDuration, easing); } else diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b55a93db1f..96e9625f76 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); - FailingLayer.HUDEnabled.BindTo(ShowHealthbar); + FailingLayer.ShowHealth.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); From 415e1c05ff7c83f9a2ef0f7981a80ecf24f60d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:06:41 +0200 Subject: [PATCH 122/170] Simplify implementation --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 26 +++++------------------ 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index e8c99c2ed8..22b7950d31 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -76,8 +76,8 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - fadePlayfieldWhenHealthLow.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); - ShowHealth.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState(), true); + ShowHealth.BindValueChanged(_ => updateState(), true); } protected override void LoadComplete() @@ -108,26 +108,10 @@ namespace osu.Game.Screens.Play.HUD fadePlayfieldWhenHealthLow.Value = false; } - /// - /// Tries to fade based on "Fade playfield when health is low" setting - /// - /// Duration of the fade - /// Type of easing - /// True when you want to fade in, false when you want to fade out - public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) + private void updateState() { - if (ShowHealth.Value) - { - if (fadeIn) - { - if (fadePlayfieldWhenHealthLow.Value) - this.FadeIn(fadeDuration, easing); - } - else - this.FadeOut(fadeDuration, easing); - } - else - this.FadeOut(fadeDuration, easing); + var showLayer = fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } protected override void Update() From a63b6a3ddf571bb941b858347fe903a4b82a1c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:22:30 +0200 Subject: [PATCH 123/170] Simplify binding --- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 96e9625f76..f09745cf71 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -153,8 +153,6 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); - - FailingLayer.ShowHealth.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); @@ -264,7 +262,10 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual FailingLayer CreateFailingLayer() => new FailingLayer(); + protected virtual FailingLayer CreateFailingLayer() => new FailingLayer + { + ShowHealth = { BindTarget = ShowHealthbar } + }; protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { From 02f590309d9b67c40e582a1c4f4302ee216204f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:22:45 +0200 Subject: [PATCH 124/170] Add xmldoc for public property --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 22b7950d31..d4faa4bbb7 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,6 +31,9 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + /// + /// Whether the current player health should be shown on screen. + /// public readonly Bindable ShowHealth = new Bindable(); private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); From 3637bf2f9bc4929cd58cffb4aeb8830e1ceee690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:23:42 +0200 Subject: [PATCH 125/170] Clean up member order & access modifiers --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index d4faa4bbb7..b96cfd170e 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -18,10 +18,15 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { /// - /// An overlay layer on top of the playfield which fades to red when the current player health falls below a certain threshold defined by . + /// An overlay layer on top of the playfield which fades to red when the current player health falls below a certain threshold defined by . /// public class FailingLayer : HealthDisplay { + /// + /// Whether the current player health should be shown on screen. + /// + public readonly Bindable ShowHealth = new Bindable(); + private const float max_alpha = 0.4f; private const int fade_time = 400; private const float gradient_size = 0.3f; @@ -29,12 +34,7 @@ namespace osu.Game.Screens.Play.HUD /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// - public double LowHealthThreshold = 0.20f; - - /// - /// Whether the current player health should be shown on screen. - /// - public readonly Bindable ShowHealth = new Bindable(); + private const double low_health_threshold = 0.20f; private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - double target = Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); + double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha); boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f); From c47f762f24c007fe504144694d93945565fbeafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 15:59:26 +0200 Subject: [PATCH 126/170] Update test scene to allow checking samples --- .../ManiaBeatmapSampleConversionTest.cs | 20 +++++++++++++------ .../convert-samples-expected-conversion.json | 9 ++++++--- .../Testing/Beatmaps/convert-samples.osu | 2 +- .../mania-samples-expected-conversion.json | 6 ++++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index d8f87195d1..dd1b2e1745 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -29,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Tests StartTime = hitObject.StartTime, EndTime = hitObject.GetEndTime(), Column = ((ManiaHitObject)hitObject).Column, - NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples) + Samples = getSampleNames(hitObject.Samples), + NodeSamples = getNodeSampleNames((hitObject as HoldNote)?.NodeSamples) }; } - private IList> getSampleNames(List> hitSampleInfo) - => hitSampleInfo?.Select(samples => - (IList)samples.Select(sample => sample.LookupNames.First()).ToList()) + private IList getSampleNames(IList hitSampleInfo) + => hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList(); + + private IList> getNodeSampleNames(List> hitSampleInfo) + => hitSampleInfo?.Select(getSampleNames) .ToList(); protected override Ruleset CreateRuleset() => new ManiaRuleset(); @@ -51,14 +54,19 @@ namespace osu.Game.Rulesets.Mania.Tests public double StartTime; public double EndTime; public int Column; + public IList Samples; public IList> NodeSamples; public bool Equals(SampleConvertValue other) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) - && samplesEqual(NodeSamples, other.NodeSamples); + && samplesEqual(Samples, other.Samples) + && nodeSamplesEqual(NodeSamples, other.NodeSamples); - private static bool samplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) + private static bool samplesEqual(ICollection firstSampleList, ICollection secondSampleList) + => firstSampleList.SequenceEqual(secondSampleList); + + private static bool nodeSamplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) { if (firstSampleList == null && secondSampleList == null) return true; diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index b8ce85eef5..fec1360b26 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -9,7 +9,8 @@ ["normal-hitnormal"], ["soft-hitnormal"], ["drum-hitnormal"] - ] + ], + "Samples": ["drum-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -17,14 +18,16 @@ "NodeSamples": [ ["soft-hitnormal"], ["drum-hitnormal"] - ] + ], + "Samples": ["drum-hitnormal"] }] }, { "StartTime": 3750.0, "Objects": [{ "StartTime": 3750.0, "EndTime": 3750.0, - "Column": 3 + "Column": 3, + "Samples": ["normal-hitnormal"] }] }] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu index 16b73992d2..fea1de6614 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu @@ -13,4 +13,4 @@ SliderTickRate:1 [HitObjects] 88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0: -259,118,3750,1,0,0:0:0:0: +259,118,3750,1,0,1:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json index e22540614d..1aca75a796 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json @@ -8,7 +8,8 @@ "NodeSamples": [ ["normal-hitnormal"], [] - ] + ], + "Samples": ["normal-hitnormal"] }] }, { "StartTime": 2000.0, @@ -19,7 +20,8 @@ "NodeSamples": [ ["drum-hitnormal"], [] - ] + ], + "Samples": ["drum-hitnormal"] }] }] } \ No newline at end of file From 5e92809401122afcd4504ebf99ad17e234c6dbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 16:46:43 +0200 Subject: [PATCH 127/170] Add failing test case --- .../ManiaBeatmapSampleConversionTest.cs | 1 + ...r-convert-samples-expected-conversion.json | 21 +++++++++++++++++++ .../Beatmaps/slider-convert-samples.osu | 15 +++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index dd1b2e1745..c8feb4ae24 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase("convert-samples")] [TestCase("mania-samples")] + [TestCase("slider-convert-samples")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json new file mode 100644 index 0000000000..e3768a90d7 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json @@ -0,0 +1,21 @@ +{ + "Mappings": [{ + "StartTime": 8470.0, + "Objects": [{ + "StartTime": 8470.0, + "EndTime": 8470.0, + "Column": 0, + "Samples": ["normal-hitnormal", "normal-hitclap"] + }, { + "StartTime": 8626.470587768974, + "EndTime": 8626.470587768974, + "Column": 1, + "Samples": ["normal-hitnormal"] + }, { + "StartTime": 8782.941175537948, + "EndTime": 8782.941175537948, + "Column": 2, + "Samples": ["normal-hitnormal", "normal-hitclap"] + }] + }] +} diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu new file mode 100644 index 0000000000..08e90ce807 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu @@ -0,0 +1,15 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:8 +ApproachRate:9.5 +SliderMultiplier:2.00000000596047 +SliderTickRate:1 + +[TimingPoints] +0,312.941176470588,4,1,0,100,1,0 + +[HitObjects] +82,216,8470,6,0,P|52:161|99:113,2,100,8|0|8,1:0|1:0|1:0,0:0:0:0: From 1551c42c122119172a67c9a0900ef8d8376284fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 16:49:14 +0200 Subject: [PATCH 128/170] Avoid division when slicing node sample list --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 9fbdf58e21..a09ef6d5b6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -483,9 +483,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (!(HitObject is IHasPathWithRepeats curveData)) return null; - double segmentTime = (EndTime - HitObject.StartTime) / spanCount; - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + // mathematically speaking this could be done by calculating (time - HitObject.StartTime) / SegmentDuration + // however, floating-point operations can introduce inaccuracies - therefore resort to iterated addition + // (all callers use this method to calculate repeat point times, so this way is consistent and deterministic) + int index = 0; + for (double nodeTime = HitObject.StartTime; nodeTime < time; nodeTime += SegmentDuration) + index += 1; // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); From 082c94f98dfd7b00515846a06045e7b3949205b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 28 Jun 2020 13:14:46 +0200 Subject: [PATCH 129/170] Temporarily disable masking of tournament song bar --- osu.Game.Tournament/Components/SongBar.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index fc7fcef892..cafec0a88b 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; @@ -66,6 +67,9 @@ namespace osu.Game.Tournament.Components } } + // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + [BackgroundDependencyLoader] private void load() { @@ -77,8 +81,6 @@ namespace osu.Game.Tournament.Components flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, - // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away. - Height = 1, AutoSizeAxes = Axes.Y, LayoutDuration = 500, LayoutEasing = Easing.OutQuint, From 006adf0fb50a903c67c7317b3c721ff653615506 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Jun 2020 22:45:13 +0900 Subject: [PATCH 130/170] Change logic to ignore rooms completely after first error --- osu.Game/Screens/Multi/RoomManager.cs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 5083fb2ee3..642378d8d5 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Multi joinedRoom = null; } - private readonly List roomsFailedUpdate = new List(); + private readonly HashSet ignoredRooms = new HashSet(); /// /// Invoked when the listing of all s is received from the server. @@ -166,25 +166,26 @@ namespace osu.Game.Screens.Multi continue; } - var r = listing[i]; - r.Position.Value = i; + var room = listing[i]; + + Debug.Assert(room.RoomID.Value != null); + + if (ignoredRooms.Contains(room.RoomID.Value.Value)) + continue; + + room.Position.Value = i; try { - update(r, r); - addRoom(r); + update(room, room); + addRoom(room); } catch (Exception ex) { - Debug.Assert(r.RoomID.Value != null); + Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); - if (!roomsFailedUpdate.Contains(r.RoomID.Value.Value)) - { - Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); - roomsFailedUpdate.Add(r.RoomID.Value.Value); - } - - rooms.Remove(r); + ignoredRooms.Add(room.RoomID.Value.Value); + rooms.Remove(room); } } From 678767918e29dee961730f5f8f18a2a0e6e98c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 28 Jun 2020 23:32:04 +0200 Subject: [PATCH 131/170] Centralise logic further --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 30 ++++++----------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index b96cfd170e..84dbb35f68 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -36,10 +36,9 @@ namespace osu.Game.Screens.Play.HUD /// private const double low_health_threshold = 0.20f; - private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; - private Bindable fadePlayfieldWhenHealthLowSetting; + private Bindable fadePlayfieldWhenHealthLow; private HealthProcessor healthProcessor; public FailingLayer() @@ -78,15 +77,15 @@ namespace osu.Game.Screens.Play.HUD { boxes.Colour = color.Red; - fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState(), true); - ShowHealth.BindValueChanged(_ => updateState(), true); + fadePlayfieldWhenHealthLow = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState()); + ShowHealth.BindValueChanged(_ => updateState()); } protected override void LoadComplete() { base.LoadComplete(); - updateBindings(); + updateState(); } public override void BindHealthProcessor(HealthProcessor processor) @@ -94,26 +93,13 @@ namespace osu.Game.Screens.Play.HUD base.BindHealthProcessor(processor); healthProcessor = processor; - updateBindings(); - } - - private void updateBindings() - { - if (LoadState < LoadState.Ready) - return; - - fadePlayfieldWhenHealthLow.UnbindBindings(); - - // Don't display ever if the ruleset is not using a draining health display. - if (healthProcessor is DrainingHealthProcessor) - fadePlayfieldWhenHealthLow.BindTo(fadePlayfieldWhenHealthLowSetting); - else - fadePlayfieldWhenHealthLow.Value = false; + updateState(); } private void updateState() { - var showLayer = fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + // Don't display ever if the ruleset is not using a draining health display. + var showLayer = healthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } From ffbce61ca884320098351a331bcd658041fe79b2 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:39:49 +0200 Subject: [PATCH 132/170] Add the option to loop the intro in the main menu --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Overlays/Settings/Sections/Audio/MainMenuSettings.cs | 5 +++++ osu.Game/Screens/Menu/IntroScreen.cs | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..aa9b5340f6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,6 +55,7 @@ namespace osu.Game.Configuration Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); Set(OsuSetting.MenuVoice, true); + Set(OsuSetting.MenuMusicLoop, true); Set(OsuSetting.MenuMusic, true); Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -191,6 +192,7 @@ namespace osu.Game.Configuration AudioOffset, VolumeInactive, MenuMusic, + MenuMusicLoop, MenuVoice, CursorRotation, MenuParallax, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index a303f93b34..7ec123c04c 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -28,6 +28,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "osu! music theme", Bindable = config.GetBindable(OsuSetting.MenuMusic) }, + new SettingsCheckbox + { + LabelText = "loop the music theme", + Bindable = config.GetBindable(OsuSetting.MenuMusicLoop) + }, new SettingsDropdown { LabelText = "Intro sequence", diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 88d18d0073..57f93690a8 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } + private IBindable menuMusicLoop { get; set; } private WorkingBeatmap initialBeatmap; @@ -73,6 +74,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); + menuMusicLoop = config.GetBindable(OsuSetting.MenuMusicLoop); seeya = audio.Samples.Get(SeeyaSampleName); @@ -152,6 +154,8 @@ namespace osu.Game.Screens.Menu // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (UsingThemedIntro) Track.Restart(); + if (menuMusicLoop.Value) + Track.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) From 5689f279871de69937a37342e10efcb98e5232e1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:54:06 +0200 Subject: [PATCH 133/170] Make sure it only loops for themed intros if true --- osu.Game/Screens/Menu/IntroScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 57f93690a8..fa8a641203 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -152,8 +152,10 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (UsingThemedIntro) - Track.Restart(); + if (!UsingThemedIntro) + return; + + Track.Restart(); if (menuMusicLoop.Value) Track.Looping = true; } From 270384e71e1fe41226eaf4864b6955fe5abcc4b1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:59:44 +0200 Subject: [PATCH 134/170] Remove redundant get set --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fa8a641203..8ef7ebe5e6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } - private IBindable menuMusicLoop { get; set; } + private IBindable menuMusicLoop; private WorkingBeatmap initialBeatmap; From 24dceb9f84e49bf778ad575076f917065e6d5f67 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 01:41:47 +0200 Subject: [PATCH 135/170] Make only "Welcome" loop --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- .../Settings/Sections/Audio/MainMenuSettings.cs | 5 ----- osu.Game/Screens/Menu/IntroScreen.cs | 11 ++--------- osu.Game/Screens/Menu/IntroWelcome.cs | 2 ++ 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index aa9b5340f6..9d31bc9bba 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,7 +55,6 @@ namespace osu.Game.Configuration Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); Set(OsuSetting.MenuVoice, true); - Set(OsuSetting.MenuMusicLoop, true); Set(OsuSetting.MenuMusic, true); Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -192,7 +191,6 @@ namespace osu.Game.Configuration AudioOffset, VolumeInactive, MenuMusic, - MenuMusicLoop, MenuVoice, CursorRotation, MenuParallax, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 7ec123c04c..a303f93b34 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -28,11 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "osu! music theme", Bindable = config.GetBindable(OsuSetting.MenuMusic) }, - new SettingsCheckbox - { - LabelText = "loop the music theme", - Bindable = config.GetBindable(OsuSetting.MenuMusicLoop) - }, new SettingsDropdown { LabelText = "Intro sequence", diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 8ef7ebe5e6..5f91aaad15 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,7 +40,6 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } - private IBindable menuMusicLoop; private WorkingBeatmap initialBeatmap; @@ -74,8 +73,6 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - menuMusicLoop = config.GetBindable(OsuSetting.MenuMusicLoop); - seeya = audio.Samples.Get(SeeyaSampleName); BeatmapSetInfo setInfo = null; @@ -152,12 +149,8 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (!UsingThemedIntro) - return; - - Track.Restart(); - if (menuMusicLoop.Value) - Track.Looping = true; + if (UsingThemedIntro) + Track.Restart(); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index abd4a68d4f..bf42e36e8c 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Menu welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); + + Track.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) From 444504f2b9c7765d6219b643e1b10464b8810240 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 02:10:40 +0200 Subject: [PATCH 136/170] Expose MainMenu Track as internal get private set --- osu.Game/Screens/Menu/MainMenu.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index f0da2482d6..9245df2a7d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -6,6 +6,7 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; + internal Track Track { get; private set; } + private Bindable holdDelay; private Bindable loginDisplayed; @@ -173,15 +176,15 @@ namespace osu.Game.Screens.Menu base.OnEntering(last); buttons.FadeInFromZero(500); - var track = Beatmap.Value.Track; + Track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && track != null) + if (last is IntroScreen && Track != null) { - if (!track.IsRunning) + if (!Track.IsRunning) { - track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); - track.Start(); + Track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * Track.Length); + Track.Start(); } } } From 0c4b06b48562fc15ef0ccdec53932ffaefb9d109 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 02:16:19 +0200 Subject: [PATCH 137/170] Add visualtest to check if Track loops in Welcome --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 12 ++++++------ .../Visual/Menus/TestSceneIntroWelcome.cs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 2d2f1a1618..f71d13ed35 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -19,10 +19,10 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; + protected OsuScreenStack IntroStack; + protected IntroTestScene() { - OsuScreenStack introStack = null; - Children = new Drawable[] { new Box @@ -45,17 +45,17 @@ namespace osu.Game.Tests.Visual.Menus logo.FinishTransforms(); logo.IsTracking = false; - introStack?.Expire(); + IntroStack?.Expire(); - Add(introStack = new OsuScreenStack + Add(IntroStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both, }); - introStack.Push(CreateScreen()); + IntroStack.Push(CreateScreen()); }); - AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu); + AddUntilStep("wait for menu", () => IntroStack.CurrentScreen is MainMenu); } protected abstract IScreen CreateScreen(); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 905f17ef0b..1347bae2ad 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -11,5 +11,18 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneIntroWelcome : IntroTestScene { protected override IScreen CreateScreen() => new IntroWelcome(); + + public TestSceneIntroWelcome() + { + AddAssert("check if menu music loops", () => + { + var menu = IntroStack?.CurrentScreen as MainMenu; + + if (menu == null) + return false; + + return menu.Track.Looping; + }); + } } } From af7494b2325e31a4a3fef36fc263b19a9b3e9dfb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Jun 2020 13:58:35 +0900 Subject: [PATCH 138/170] Improve quality of song select beatmap wedge --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7a8a1593b9..27ce9e82dd 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -155,7 +155,6 @@ namespace osu.Game.Screens.Select var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); CacheDrawnFrameBuffer = true; - RedrawOnScale = false; RelativeSizeAxes = Axes.Both; From 5db103dc613d238413b56ea3b2d31312b68e67cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Jun 2020 14:38:50 +0900 Subject: [PATCH 139/170] Improve quality of taiko hit target --- osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs index 7de1593ab6..caddc8b122 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), + Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, From bb81f908fb163f0d77d2d2fb74c54be563cbb859 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 29 Jun 2020 15:44:10 +0800 Subject: [PATCH 140/170] Exclude EmptyHitWindow from being considered in TimingDistributionGraph --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 8ec7e863b1..527da429ed 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents; + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows)).ToList(); } [BackgroundDependencyLoader] From 51f5083c2d71a87eb8fcda3f6aa1b1a748f86b47 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 17:17:52 +0000 Subject: [PATCH 141/170] Bump Sentry from 2.1.3 to 2.1.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.3 to 2.1.4. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.3...2.1.4) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 26d81a1004..5f326a361d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From 1701c844a6a34f035e09c20b3c7a62950290be9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jun 2020 16:36:53 +0900 Subject: [PATCH 142/170] Fix scroll container height on smaller ui scales --- osu.Game/Screens/Ranking/ResultsScreen.cs | 68 +++++++++++++---------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 193d975e42..968b446df9 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -70,41 +70,33 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - new Container + new VerticalScrollContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + ScrollbarVisible = false, + Child = new Container { - new OsuScrollContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new Container + scorePanelList = new ScorePanelList { - RelativeSizeAxes = Axes.X, - Height = screen_height, - Children = new Drawable[] - { - scorePanelList = new ScorePanelList - { - RelativeSizeAxes = Axes.Both, - SelectedScore = { BindTarget = SelectedScore }, - PostExpandAction = () => statisticsPanel.ToggleVisibility() - }, - detachedPanelContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - statisticsPanel = new StatisticsPanel - { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, - } - } - }, + RelativeSizeAxes = Axes.Both, + SelectedScore = { BindTarget = SelectedScore }, + PostExpandAction = () => statisticsPanel.ToggleVisibility() + }, + detachedPanelContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + }, + } } - } + }, }, new[] { @@ -277,5 +269,23 @@ namespace osu.Game.Screens.Ranking detachedPanel = null; } } + + private class VerticalScrollContainer : OsuScrollContainer + { + protected override Container Content => content; + + private readonly Container content; + + public VerticalScrollContainer() + { + base.Content.Add(content = new Container { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + content.Height = Math.Max(screen_height, DrawHeight); + } + } } } From 85c42456f25c9a27cd4871fb2cc71e1e3caf1b17 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jun 2020 21:38:51 +0900 Subject: [PATCH 143/170] Improve performance of sequential scrolling algorithm --- .../Algorithms/SequentialScrollAlgorithm.cs | 164 +++++++++++------- 1 file changed, 104 insertions(+), 60 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index 0052c877f6..a1f68d7201 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -3,21 +3,26 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using JetBrains.Annotations; using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { public class SequentialScrollAlgorithm : IScrollAlgorithm { - private readonly Dictionary positionCache; + private static readonly IComparer by_position_comparer = Comparer.Create((c1, c2) => c1.Position.CompareTo(c2.Position)); private readonly IReadOnlyList controlPoints; + /// + /// Stores a mapping of time -> position for each control point. + /// + private readonly List positionMappings = new List(); + public SequentialScrollAlgorithm(IReadOnlyList controlPoints) { this.controlPoints = controlPoints; - - positionCache = new Dictionary(); } public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) @@ -27,55 +32,31 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { - var objectLength = relativePositionAtCached(endTime, timeRange) - relativePositionAtCached(startTime, timeRange); + var objectLength = relativePositionAt(endTime, timeRange) - relativePositionAt(startTime, timeRange); return (float)(objectLength * scrollLength); } public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) { - // Caching is not used here as currentTime is unlikely to have been previously cached - double timelinePosition = relativePositionAt(currentTime, timeRange); - return (float)((relativePositionAtCached(time, timeRange) - timelinePosition) * scrollLength); + double timelineLength = relativePositionAt(time, timeRange) - relativePositionAt(currentTime, timeRange); + return (float)(timelineLength * scrollLength); } public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) { - // Convert the position to a length relative to time = 0 - double length = position / scrollLength + relativePositionAt(currentTime, timeRange); + if (controlPoints.Count == 0) + return position * timeRange; - // We need to consider all timing points until the specified time and not just the currently-active one, - // since each timing point individually affects the positions of _all_ hitobjects after its start time - for (int i = 0; i < controlPoints.Count; i++) - { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + // Find the position at the current time, and the given length. + double relativePosition = relativePositionAt(currentTime, timeRange) + position / scrollLength; - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + var positionMapping = findControlPointMapping(timeRange, new PositionMapping(0, null, relativePosition), by_position_comparer); - // Figure out the length of control point - var currentLength = currentDuration / timeRange * current.Multiplier; - - if (currentLength > length) - { - // The point is within this control point - return current.StartTime + length * timeRange / current.Multiplier; - } - - length -= currentLength; - } - - return 0; // Should never occur + // Begin at the control point's time and add the remaining time to reach the given position. + return positionMapping.Time + (relativePosition - positionMapping.Position) * timeRange / positionMapping.ControlPoint.Multiplier; } - private double relativePositionAtCached(double time, double timeRange) - { - if (!positionCache.TryGetValue(time, out double existing)) - positionCache[time] = existing = relativePositionAt(time, timeRange); - return existing; - } - - public void Reset() => positionCache.Clear(); + public void Reset() => positionMappings.Clear(); /// /// Finds the position which corresponds to a point in time. @@ -84,37 +65,100 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The time to find the position at. /// The amount of time visualised by the scrolling area. /// A positive value indicating the position at . - private double relativePositionAt(double time, double timeRange) + private double relativePositionAt(in double time, in double timeRange) { if (controlPoints.Count == 0) return time / timeRange; - double length = 0; + var mapping = findControlPointMapping(timeRange, new PositionMapping(time)); - // We need to consider all timing points until the specified time and not just the currently-active one, - // since each timing point individually affects the positions of _all_ hitobjects after its start time - for (int i = 0; i < controlPoints.Count; i++) + // Begin at the control point's position and add the remaining distance to reach the given time. + return mapping.Position + (time - mapping.Time) / timeRange * mapping.ControlPoint.Multiplier; + } + + /// + /// Finds a 's that is relevant to a given . + /// + /// + /// This is used to find the last occuring prior to a time value, or prior to a position value (if is used). + /// + /// The time range. + /// The to find the closest to. + /// The comparison. If null, the default comparer is used (by time). + /// The 's that is relevant for . + private PositionMapping findControlPointMapping(in double timeRange, in PositionMapping search, IComparer comparer = null) + { + generatePositionMappings(timeRange); + + var mappingIndex = positionMappings.BinarySearch(search, comparer ?? Comparer.Default); + + if (mappingIndex < 0) { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + // If the search value isn't found, the _next_ control point is returned, but we actually want the _previous_ control point. + // In doing so, we must make sure to not underflow the position mapping list (i.e. always use the 0th control point for time < first_control_point_time). + mappingIndex = Math.Max(0, ~mappingIndex - 1); - // We don't need to consider any control points beyond the current time, since it will not yet - // affect any hitobjects - if (i > 0 && current.StartTime > time) - continue; - - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - - // We want to consider the minimal amount of time that this control point has affected, - // which may be either its duration, or the amount of time that has passed within it - var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); - - // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier - length += durationInCurrent / timeRange * current.Multiplier; + Debug.Assert(mappingIndex < positionMappings.Count); } - return length; + var mapping = positionMappings[mappingIndex]; + Debug.Assert(mapping.ControlPoint != null); + + return mapping; + } + + /// + /// Generates the mapping of (and their respective start times) to their relative position from 0. + /// + /// The time range. + private void generatePositionMappings(in double timeRange) + { + if (positionMappings.Count > 0) + return; + + if (controlPoints.Count == 0) + return; + + positionMappings.Add(new PositionMapping(controlPoints[0].StartTime, controlPoints[0])); + + for (int i = 0; i < controlPoints.Count - 1; i++) + { + var current = controlPoints[i]; + var next = controlPoints[i + 1]; + + // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier + float length = (float)((next.StartTime - current.StartTime) / timeRange * current.Multiplier); + + positionMappings.Add(new PositionMapping(next.StartTime, next, positionMappings[^1].Position + length)); + } + } + + private readonly struct PositionMapping : IComparable + { + /// + /// The time corresponding to this position. + /// + public readonly double Time; + + /// + /// The at . + /// + [CanBeNull] + public readonly MultiplierControlPoint ControlPoint; + + /// + /// The relative position from 0 of . + /// + public readonly double Position; + + public PositionMapping(double time, MultiplierControlPoint controlPoint = null, double position = default) + { + Time = time; + ControlPoint = controlPoint; + Position = position; + } + + public int CompareTo(PositionMapping other) => Time.CompareTo(other.Time); } } } From 508d34fd3ac6d48dae4e5aa578e0c112f609cb3a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 30 Jun 2020 19:51:10 +0200 Subject: [PATCH 144/170] Fix notification redirecting to the old log folder when game installation has been migrated to another location. --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b0d7b14d34..92233f143d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -767,7 +767,7 @@ namespace osu.Game Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { - Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); return true; } })); From 39cfbb67ad7962f1b75beb72fd793e445de66512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jun 2020 20:16:19 +0200 Subject: [PATCH 145/170] Replace iterated addition with rounding --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index a09ef6d5b6..d03eb0b3c9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -483,12 +483,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (!(HitObject is IHasPathWithRepeats curveData)) return null; - // mathematically speaking this could be done by calculating (time - HitObject.StartTime) / SegmentDuration - // however, floating-point operations can introduce inaccuracies - therefore resort to iterated addition - // (all callers use this method to calculate repeat point times, so this way is consistent and deterministic) - int index = 0; - for (double nodeTime = HitObject.StartTime; nodeTime < time; nodeTime += SegmentDuration) - index += 1; + // mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind + var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero); // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); From ab15b6031d662fb660149f0c9085be99f5c33b59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 17:12:07 +0900 Subject: [PATCH 146/170] Update with framework-side storage changes --- osu.Game/IO/OsuStorage.cs | 6 +++--- osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 499bcb4063..f5ce1c0105 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -24,12 +24,12 @@ namespace osu.Game.IO "storage.ini" }; - public OsuStorage(GameHost host) - : base(host.Storage, string.Empty) + public OsuStorage(GameHost host, Storage defaultStorage) + : base(defaultStorage, string.Empty) { this.host = host; - storageConfig = new StorageConfigManager(host.Storage); + storageConfig = new StorageConfigManager(defaultStorage); var customStoragePath = storageConfig.Get(StorageConfig.FullPath); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3e7311092e..c79f710151 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -312,11 +312,13 @@ namespace osu.Game base.SetHost(host); // may be non-null for certain tests - Storage ??= new OsuStorage(host); + Storage ??= host.Storage; LocalConfig ??= new OsuConfigManager(Storage); } + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + private readonly List fileImporters = new List(); public async Task Import(params string[] paths) From cdcad94e9f0a8ce75c6be8572408795aaa6bde16 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 17:47:29 +0900 Subject: [PATCH 147/170] Handle exception thrown due to custom stoage on startup --- osu.Game/IO/OsuStorage.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index f5ce1c0105..8bcc0941c1 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -34,7 +34,17 @@ namespace osu.Game.IO var customStoragePath = storageConfig.Get(StorageConfig.FullPath); if (!string.IsNullOrEmpty(customStoragePath)) - ChangeTargetStorage(host.GetStorage(customStoragePath)); + { + try + { + ChangeTargetStorage(host.GetStorage(customStoragePath)); + } + catch (Exception ex) + { + Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); + ChangeTargetStorage(defaultStorage); + } + } } protected override void ChangeTargetStorage(Storage newStorage) From 5f577797a7dd491d8ccecd49b28967ca826eb038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:41:00 +0900 Subject: [PATCH 148/170] Expose transform helpers in SkinnableSound --- osu.Game/Skinning/SkinnableSound.cs | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 30320c89a6..24d6648273 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -7,8 +7,10 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; using osu.Game.Audio; namespace osu.Game.Skinning @@ -43,6 +45,34 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => + samplesContainer.VolumeTo(newVolume, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => + samplesContainer.BalanceTo(newBalance, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => + samplesContainer.FrequencyTo(newFrequency, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => + samplesContainer.TempoTo(newTempo, duration, easing); + public bool Looping { get => looping; From 6f6376d53c56e5f592a6ae253349b4cc1923f5e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:52:05 +0900 Subject: [PATCH 149/170] Update framework --- .idea/.idea.osu.Desktop/.idea/modules.xml | 1 - osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml index 366f172c30..fe63f5faf3 100644 --- a/.idea/.idea.osu.Desktop/.idea/modules.xml +++ b/.idea/.idea.osu.Desktop/.idea/modules.xml @@ -2,7 +2,6 @@ - diff --git a/osu.Android.props b/osu.Android.props index 493b1f5529..a2c97ead2f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5f326a361d..3ef53a2a53 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 72f09ee287..492bf89fab 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 49aa839872b5291e2df9011c410f8d72edf3823b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:54:11 +0900 Subject: [PATCH 150/170] Update RulesetInputManager to use new method --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index ba30fe28d5..f2ac61eaf4 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -18,9 +18,6 @@ using osu.Game.Input.Handlers; using osu.Game.Screens.Play; using osuTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; -using JoystickState = osu.Framework.Input.States.JoystickState; -using KeyboardState = osu.Framework.Input.States.KeyboardState; -using MouseState = osu.Framework.Input.States.MouseState; namespace osu.Game.Rulesets.UI { @@ -42,11 +39,7 @@ namespace osu.Game.Rulesets.UI } } - protected override InputState CreateInitialState() - { - var state = base.CreateInitialState(); - return new RulesetInputManagerInputState(state.Mouse, state.Keyboard, state.Joystick); - } + protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); protected readonly KeyBindingContainer KeyBindingContainer; @@ -203,8 +196,8 @@ namespace osu.Game.Rulesets.UI { public ReplayState LastReplayState; - public RulesetInputManagerInputState(MouseState mouse = null, KeyboardState keyboard = null, JoystickState joystick = null) - : base(mouse, keyboard, joystick) + public RulesetInputManagerInputState(InputState state = null) + : base(state) { } } From 4e839e4f1fb595740caa29f901f7072fc2858f23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 19:02:05 +0900 Subject: [PATCH 151/170] Fix "welcome" intro test failure due to no wait logic --- .../Visual/Menus/TestSceneIntroWelcome.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 1347bae2ad..8f20e38494 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Game.Screens.Menu; @@ -14,15 +15,11 @@ namespace osu.Game.Tests.Visual.Menus public TestSceneIntroWelcome() { - AddAssert("check if menu music loops", () => - { - var menu = IntroStack?.CurrentScreen as MainMenu; + AddUntilStep("wait for load", () => getTrack() != null); - if (menu == null) - return false; - - return menu.Track.Looping; - }); + AddAssert("check if menu music loops", () => getTrack().Looping); } + + private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track; } } From 1edfac4923623a1d78b1379c4f2c7e8e4177a01b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 23:21:08 +0900 Subject: [PATCH 152/170] Fix test failing --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..8ea0e34214 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -127,6 +127,9 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); + // Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes. + string originalDirectory = storage.GetFullPath("."); + // ensure we perform a save host.Dependencies.Get().Save(); @@ -145,25 +148,25 @@ namespace osu.Game.Tests.NonVisual Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath)); // ensure cache was not moved - Assert.That(host.Storage.ExistsDirectory("cache")); + Assert.That(Directory.Exists(Path.Combine(originalDirectory, "cache"))); // ensure nested cache was moved - Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); + Assert.That(!Directory.Exists(Path.Combine(originalDirectory, "test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); foreach (var file in OsuStorage.IGNORE_FILES) { - Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(File.Exists(Path.Combine(originalDirectory, file))); Assert.That(storage.Exists(file), Is.False); } foreach (var dir in OsuStorage.IGNORE_DIRECTORIES) { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); Assert.That(storage.ExistsDirectory(dir), Is.False); } - Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); + Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); } finally { From 3278a1d7d821e4fd5cfe9d4bde6125ef9a77a09c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 2 Jul 2020 00:21:45 +0900 Subject: [PATCH 153/170] Standardize osu!catch coordinate system There were two coordinate systems used: - 0..512 (used in osu!stable) - 0..1 (relative coordinate) This commit replaces the usage of the relative coordinate system to the coordinate system of 0..512. --- .../CatchBeatmapConversionTest.cs | 3 +-- .../TestSceneAutoJuiceStream.cs | 6 +++--- .../TestSceneCatchStacker.cs | 10 +++++++++- .../TestSceneCatcherArea.cs | 4 ++-- .../TestSceneDrawableHitObjects.cs | 4 ++-- .../TestSceneHyperDash.cs | 8 ++++---- .../TestSceneJuiceStream.cs | 5 +++-- .../Beatmaps/CatchBeatmapConverter.cs | 5 ++--- .../Beatmaps/CatchBeatmapProcessor.cs | 14 +++++++------- .../Preprocessing/CatchDifficultyHitObject.cs | 5 ++--- .../Difficulty/Skills/Movement.cs | 5 ++--- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 4 ++++ .../Objects/Drawables/DrawableCatchHitObject.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 9 ++++----- .../Replays/CatchAutoGenerator.cs | 4 ++-- .../Replays/CatchReplayFrame.cs | 5 ++--- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 11 ++++++++++- .../UI/CatchPlayfieldAdjustmentContainer.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 13 +++++-------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 10 ++-------- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 21 files changed, 70 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index f4749be370..df54df7b01 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; @@ -83,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests public float Position { - get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position; + get => HitObject?.X ?? position; set => position = value; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 7c2304694f..d6bba3d55e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -27,15 +27,15 @@ namespace osu.Game.Rulesets.Catch.Tests for (int i = 0; i < 100; i++) { - float width = (i % 10 + 1) / 20f; + float width = (i % 10 + 1) / 20f * CatchPlayfield.WIDTH; beatmap.HitObjects.Add(new JuiceStream { - X = 0.5f - width / 2, + X = CatchPlayfield.CENTER_X - width / 2, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) + new Vector2(width, 0) }), StartTime = i * 2000, NewCombo = i % 8 == 0 diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 44672b6526..1ff31697b8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Tests { @@ -22,7 +23,14 @@ namespace osu.Game.Rulesets.Catch.Tests }; for (int i = 0; i < 512; i++) - beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); + { + beatmap.HitObjects.Add(new Fruit + { + X = (0.5f + i / 2048f * (i % 10 - 5)) * CatchPlayfield.WIDTH, + StartTime = i * 100, + NewCombo = i % 8 == 0 + }); + } return beatmap; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 2b30edb70b..fbb22a8498 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Catch.Tests RelativeSizeAxes = Axes.Both, Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation }, }); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index a7094c00be..d35f828e28 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -158,8 +158,8 @@ namespace osu.Game.Rulesets.Catch.Tests private float getXCoords(bool hit) { - const float x_offset = 0.2f; - float xCoords = drawableRuleset.Playfield.Width / 2; + const float x_offset = 0.2f * CatchPlayfield.WIDTH; + float xCoords = CatchPlayfield.CENTER_X; if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield) catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index a0dcb86d57..ad24adf352 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -47,13 +47,13 @@ namespace osu.Game.Rulesets.Catch.Tests }; // Should produce a hyper-dash (edge case test) - beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56 / 512f, NewCombo = true }); - beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308 / 512f, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true }); double startTime = 3000; - const float left_x = 0.02f; - const float right_x = 0.98f; + const float left_x = 0.02f * CatchPlayfield.WIDTH; + const float right_x = 0.98f * CatchPlayfield.WIDTH; createObjects(() => new Fruit { X = left_x }); createObjects(() => new TestJuiceStream(right_x), 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index ffcf61a4bf..269e783899 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests { new JuiceStream { - X = 0.5f, + X = CatchPlayfield.CENTER_X, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, new Banana { - X = 0.5f, + X = CatchPlayfield.CENTER_X, StartTime = 1000, NewCombo = true } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 0de2060e2d..145a40f5f5 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System.Linq; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Framework.Extensions.IEnumerableExtensions; @@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Path = curveData.Path, NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, + X = positionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 @@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH + X = positionData?.X ?? 0 }.Yield(); } } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 7c81bcdf0c..bb14988414 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case BananaShower bananaShower: foreach (var banana in bananaShower.NestedHitObjects.OfType()) { - banana.XOffset = (float)rng.NextDouble(); + banana.XOffset = (float)(rng.NextDouble() * CatchPlayfield.WIDTH); rng.Next(); // osu!stable retrieved a random banana type rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana colour @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case JuiceStream juiceStream: // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead. - lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH; + lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X; // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead. lastStartTime = juiceStream.StartTime; @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps catchObject.XOffset = 0; if (catchObject is TinyDroplet) - catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X); else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } // ReSharper disable once PossibleLossOfFraction - if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3) + if (Math.Abs(positionDiff) < timeDiff / 3) applyOffset(ref offsetPosition, positionDiff); hitObject.XOffset = offsetPosition - hitObject.X; @@ -149,12 +149,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng) { bool right = rng.NextBool(); - float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; + float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))); if (right) { // Clamp to the right bound - if (position + rand <= 1) + if (position + rand <= CatchPlayfield.WIDTH) position += rand; else position -= rand; @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); - double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2; + double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; int lastDirection = 0; double lastExcess = halfCatcherWidth; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 360af1a8c9..3e21b8fbaf 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; @@ -33,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; - NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + NormalizedPosition = BaseObject.X * scalingFactor; + LastNormalizedPosition = LastObject.X * scalingFactor; // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 918ed77683..e679231638 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills } // Bonus for edge dashes. - if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f) { if (!catchCurrent.LastObject.HyperDash) edgeDashBonus += 5.7; @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values + distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } lastPlayerPosition = playerPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index f3b566f340..04932ecdbb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -17,6 +18,9 @@ namespace osu.Game.Rulesets.Catch.Objects private float x; + /// + /// The horizontal position of the fruit between 0 and . + /// public float X { get => x + XOffset; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index b12cdd4ccb..c6345a9df7 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -70,12 +71,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; - protected override float SamplePlaybackPosition => HitObject.X; + protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { - RelativePositionAxes = Axes.X; X = hitObject.X; } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2c96ee2b19..6b8b70ed54 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -7,7 +7,6 @@ using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -80,7 +79,7 @@ namespace osu.Game.Rulesets.Catch.Objects { StartTime = t + lastEvent.Value.Time, X = X + Path.PositionAt( - lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, }); } } @@ -97,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(e.PathProgress).X, }); break; @@ -108,14 +107,14 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, StartTime = e.Time, - X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(e.PathProgress).X, }); break; } } } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + public float EndX => X + this.CurvePositionAt(1).X; public double Duration { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 7a33cb0577..5d11c574b1 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Replays // todo: add support for HT DT const double dash_speed = Catcher.BASE_SPEED; const double movement_speed = dash_speed / 2; - float lastPosition = 0.5f; + float lastPosition = CatchPlayfield.CENTER_X; double lastTime = 0; void moveToNext(CatchHitObject h) @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Replays bool impossibleJump = speedRequired > movement_speed * 2; // todo: get correct catcher size, based on difficulty CS. - const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; + const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f; if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 9dab3ed630..7efd832f62 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { - Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; + Position = currentFrame.Position.X; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; if (Dashing) @@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1; - return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state); + return new LegacyReplayFrame(Time, Position, null, state); } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2319c5ac1f..d034f3c7d4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -16,7 +16,16 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : ScrollingPlayfield { - public const float BASE_WIDTH = 512; + /// + /// The width of the playfield. + /// The horizontal movement of the catcher is confined in the area of this width. + /// + public const float WIDTH = 512; + + /// + /// The center position of the playfield. + /// + public const float CENTER_X = WIDTH / 2; internal readonly CatcherArea CatcherArea; diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index b8d3dc9017..8ee23461ba 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); - Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH); + Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH); Size = Vector2.Divide(Vector2.One, Scale); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9cce46d730..82cbbefcca 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. /// - public const double BASE_SPEED = 1.0 / 512; + public const double BASE_SPEED = 1.0; public Container ExplodingFruitTarget; @@ -104,9 +104,6 @@ namespace osu.Game.Rulesets.Catch.UI { this.trailsTarget = trailsTarget; - RelativePositionAxes = Axes.X; - X = 0.5f; - Origin = Anchor.TopCentre; Size = new Vector2(CatcherArea.CATCHER_SIZE); @@ -209,8 +206,8 @@ namespace osu.Game.Rulesets.Catch.UI var halfCatchWidth = catchWidth * 0.5f; // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. - var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; - var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; + var catchObjectPosition = fruit.X; + var catcherPosition = Position.X; var validCatch = catchObjectPosition >= catcherPosition - halfCatchWidth && @@ -224,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.UI { var target = fruit.HyperDashTarget; var timeDifference = target.StartTime - fruit.StartTime; - double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition; + double positionDifference = target.X - catcherPosition; var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); SetHyperDashState(Math.Abs(velocity), target.X); @@ -331,7 +328,7 @@ namespace osu.Game.Rulesets.Catch.UI public void UpdatePosition(float position) { - position = Math.Clamp(position, 0, 1); + position = Math.Clamp(position, 0, CatchPlayfield.WIDTH); if (position == X) return; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 37d177b936..bf1ac5bc0e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -31,14 +31,8 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherArea(BeatmapDifficulty difficulty = null) { - RelativeSizeAxes = Axes.X; - Height = CATCHER_SIZE; - Child = MovableCatcher = new Catcher(this, difficulty); - } - - public static float GetCatcherSize(BeatmapDifficulty difficulty) - { - return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); + Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }; } public void OnResult(DrawableCatchHitObject fruit, JudgementResult result) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index cefb47893c..57555cce90 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case 2: - position.X = ((IHasXPosition)hitObject).X * 512; + position.X = ((IHasXPosition)hitObject).X; break; case 3: From 5c1f1ab622c8a4e862a7652564b557c76b9514ab Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 1 Jul 2020 14:31:06 -0700 Subject: [PATCH 154/170] Fix avatar in score panel being unclickable when statistics panel is visible --- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 968b446df9..49ce07b708 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -79,6 +79,11 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + }, scorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, @@ -89,11 +94,6 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both }, - statisticsPanel = new StatisticsPanel - { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, } } }, From fa252d5e950d685699632b9cfb78ea7d25a95c58 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 1 Jul 2020 17:37:38 -0700 Subject: [PATCH 155/170] Fix score panel not showing silver s/ss badges on hd/fl plays --- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index ee53ee9879..213c1692ee 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osuTK; @@ -191,8 +193,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding { Vertical = -15, Horizontal = -20 }, Children = new[] { - new RankBadge(1f, ScoreRank.X), - new RankBadge(0.95f, ScoreRank.S), + new RankBadge(1f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X), + new RankBadge(0.95f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S), new RankBadge(0.9f, ScoreRank.A), new RankBadge(0.8f, ScoreRank.B), new RankBadge(0.7f, ScoreRank.C), From 718f06c69075b9199ac07de2db66e6b8124182e5 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 Jul 2020 12:35:32 -0700 Subject: [PATCH 156/170] Use Mod.AdjustRank() instead --- .../Expanded/Accuracy/AccuracyCircle.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 213c1692ee..45da23f1f9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -193,18 +193,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding { Vertical = -15, Horizontal = -20 }, Children = new[] { - new RankBadge(1f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X), - new RankBadge(0.95f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S), - new RankBadge(0.9f, ScoreRank.A), - new RankBadge(0.8f, ScoreRank.B), - new RankBadge(0.7f, ScoreRank.C), - new RankBadge(0.35f, ScoreRank.D), + new RankBadge(1f, getRank(ScoreRank.X)), + new RankBadge(0.95f, getRank(ScoreRank.S)), + new RankBadge(0.9f, getRank(ScoreRank.A)), + new RankBadge(0.8f, getRank(ScoreRank.B)), + new RankBadge(0.7f, getRank(ScoreRank.C)), + new RankBadge(0.35f, getRank(ScoreRank.D)), } }, rankText = new RankText(score.Rank) }; } + private ScoreRank getRank(ScoreRank rank) + { + foreach (var mod in score.Mods.OfType()) + rank = mod.AdjustRank(rank, score.Accuracy); + + return rank; + } + protected override void LoadComplete() { base.LoadComplete(); From d66b97868c4db2e057bf7340200d49eace3dd16f Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 Jul 2020 12:39:37 -0700 Subject: [PATCH 157/170] Adjust rank when flashlight is enabled --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 35a8334237..6e94a84e7d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -47,9 +47,25 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { Combo.BindTo(scoreProcessor.Combo); + + // Default value of ScoreProcessor's Rank in Flashlight Mod should be SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; } - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + + case ScoreRank.S: + return ScoreRank.SH; + + default: + return rank; + } + } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 02871c960ba37153075a2781f94de943e49b5ac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jul 2020 23:25:06 +0900 Subject: [PATCH 158/170] 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 a2c97ead2f..ff86ac6574 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ef53a2a53..afe2348c6e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 492bf89fab..80c37ab8f9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From cd6bdcdb88035e5d0715d90520f5e083c47ea6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Jul 2020 00:25:01 +0200 Subject: [PATCH 159/170] Replace further spinner transforms with manual lerp --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3c8ab0f5ab..bb1b6fdd26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -14,6 +14,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; @@ -193,9 +194,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables SpmCounter.SetRotation(Disc.RotationAbsolute); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; - Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); + float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; + Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() From d229993e5c727ff9c10328a1360ad776606053b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Jul 2020 02:12:26 +0200 Subject: [PATCH 160/170] Use RotationAbsolute instead --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index bb1b6fdd26..4d37622be5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.RotationAbsolute / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() From ec689ce824c0ff9930b6d8f4a38322f40e5a61af Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 5 Jul 2020 12:31:16 +0800 Subject: [PATCH 161/170] add support for custom mania skin paths for stage decorations --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 0806676fde..aebc229f7c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -115,6 +115,7 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("NoteImage"): case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): + case string _ when pair.Key.StartsWith("Stage"): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } From 5c2959eeb6844552a82ef8f2085448a1dc9a6b1d Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 5 Jul 2020 13:02:50 +0800 Subject: [PATCH 162/170] allow lookup of stage decoration paths and add test images --- .../{mania-stage-left.png => mania/stage-left.png} | Bin .../stage-right.png} | Bin .../Resources/special-skin/skin.ini | 4 +++- osu.Game/Skinning/LegacySkin.cs | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) rename osu.Game.Rulesets.Mania.Tests/Resources/special-skin/{mania-stage-left.png => mania/stage-left.png} (100%) rename osu.Game.Rulesets.Mania.Tests/Resources/special-skin/{mania-stage-right.png => mania/stage-right.png} (100%) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-left.png similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png rename to osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-left.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-right.png similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png rename to osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-right.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 941abac1da..36765d61bf 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -9,4 +9,6 @@ Hit50: mania/hit50 Hit100: mania/hit100 Hit200: mania/hit200 Hit300: mania/hit300 -Hit300g: mania/hit300g \ No newline at end of file +Hit300g: mania/hit300g +StageLeft: mania/stage-left +StageRight: mania/stage-right \ No newline at end of file diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b2b723440..4b70ccc6ad 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -250,6 +250,15 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.RightStageImage: return SkinUtils.As(getManiaImage(existing, "StageRight")); + case LegacyManiaSkinConfigurationLookups.BottomStageImage: + return SkinUtils.As(getManiaImage(existing, "StageBottom")); + + case LegacyManiaSkinConfigurationLookups.LightImage: + return SkinUtils.As(getManiaImage(existing, "StageLight")); + + case LegacyManiaSkinConfigurationLookups.HitTargetImage: + return SkinUtils.As(getManiaImage(existing, "StageHint")); + case LegacyManiaSkinConfigurationLookups.LeftLineWidth: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value])); From dbbee481f60b21911b5c67fa1d575272ac7a3a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:01:45 +0900 Subject: [PATCH 163/170] Expose dialog body text getter --- osu.Game/Overlays/Dialog/PopupDialog.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 02ef900dc5..1bcbe4dd2f 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -42,25 +42,34 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } - private string text; + private string headerText; public string HeaderText { - get => text; + get => headerText; set { - if (text == value) + if (headerText == value) return; - text = value; - + headerText = value; header.Text = value; } } + private string bodyText; + public string BodyText { - set => body.Text = value; + get => bodyText; + set + { + if (bodyText == value) + return; + + bodyText = value; + body.Text = value; + } } public IEnumerable Buttons From 1effe71ec2279ea53aafe07e230160312612b5e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:03:09 +0900 Subject: [PATCH 164/170] Add dialog for storage options --- osu.Game/IO/OsuStorage.cs | 95 ++++++++++++++++++++++++++----- osu.Game/Screens/Menu/MainMenu.cs | 79 +++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 13 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8bcc0941c1..3d6903c56f 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -13,12 +15,30 @@ namespace osu.Game.IO { public class OsuStorage : WrappedStorage { + /// + /// Indicates the error (if any) that occurred when initialising the custom storage during initial startup. + /// + public readonly OsuStorageError Error; + + /// + /// The custom storage path as selected by the user. + /// + [CanBeNull] + public string CustomStoragePath => storageConfig.Get(StorageConfig.FullPath); + + /// + /// The default storage path to be used if a custom storage path hasn't been selected or is not accessible. + /// + [NotNull] + public string DefaultStoragePath => defaultStorage.GetFullPath("."); + private readonly GameHost host; private readonly StorageConfigManager storageConfig; + private readonly Storage defaultStorage; - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; + public static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - internal static readonly string[] IGNORE_FILES = + public static readonly string[] IGNORE_FILES = { "framework.ini", "storage.ini" @@ -28,23 +48,53 @@ namespace osu.Game.IO : base(defaultStorage, string.Empty) { this.host = host; + this.defaultStorage = defaultStorage; storageConfig = new StorageConfigManager(defaultStorage); - var customStoragePath = storageConfig.Get(StorageConfig.FullPath); + if (!string.IsNullOrEmpty(CustomStoragePath)) + TryChangeToCustomStorage(out Error); + } - if (!string.IsNullOrEmpty(customStoragePath)) + /// + /// Resets the custom storage path, changing the target storage to the default location. + /// + public void ResetCustomStoragePath() + { + storageConfig.Set(StorageConfig.FullPath, string.Empty); + storageConfig.Save(); + + ChangeTargetStorage(defaultStorage); + } + + /// + /// Attempts to change to the user's custom storage path. + /// + /// The error that occurred. + /// Whether the custom storage path was used successfully. If not, will be populated with the reason. + public bool TryChangeToCustomStorage(out OsuStorageError error) + { + Debug.Assert(!string.IsNullOrEmpty(CustomStoragePath)); + + error = OsuStorageError.None; + Storage lastStorage = UnderlyingStorage; + + try { - try - { - ChangeTargetStorage(host.GetStorage(customStoragePath)); - } - catch (Exception ex) - { - Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); - ChangeTargetStorage(defaultStorage); - } + Storage userStorage = host.GetStorage(CustomStoragePath); + + if (!userStorage.GetFiles(".").Any()) + error = OsuStorageError.AccessibleButEmpty; + + ChangeTargetStorage(userStorage); } + catch + { + error = OsuStorageError.NotAccessible; + ChangeTargetStorage(lastStorage); + } + + return error == OsuStorageError.None; } protected override void ChangeTargetStorage(Storage newStorage) @@ -155,4 +205,23 @@ namespace osu.Game.IO } } } + + public enum OsuStorageError + { + /// + /// No error. + /// + None, + + /// + /// Occurs when the target storage directory is accessible but does not already contain game files. + /// Only happens when the user changes the storage directory and then moves the files manually or mounts a different device to the same path. + /// + AccessibleButEmpty, + + /// + /// Occurs when the target storage directory cannot be accessed at all. + /// + NotAccessible, + } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 9245df2a7d..c391742c45 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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 osuTK; using osuTK.Graphics; @@ -15,6 +16,7 @@ using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -171,6 +173,9 @@ namespace osu.Game.Screens.Menu return s; } + [Resolved] + private Storage storage { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -187,6 +192,9 @@ namespace osu.Game.Screens.Menu Track.Start(); } } + + if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) + dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); } private bool exitConfirmed; @@ -308,5 +316,76 @@ namespace osu.Game.Screens.Menu }; } } + + private class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" + + "Is it on a removable device that is not currently connected?"; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Use the default path from now on", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Only use the default path for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText += "The custom storage path is currently being used but is empty.\n\n" + + "Have you moved the files elsewhere?"; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Reset to default", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Keep using the custom path" + } + }); + + break; + } + + Buttons = buttons; + } + } } } From 8f792603ee6b03d19bba1ffc7d74203904205a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jul 2020 22:40:45 +0900 Subject: [PATCH 165/170] Apply suggestions from code review Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/MainMenu.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c391742c45..d64d9b69fe 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,13 +332,11 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; switch (error) { case OsuStorageError.NotAccessible: - BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" - + "Is it on a removable device that is not currently connected?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; buttons.AddRange(new PopupDialogButton[] { @@ -353,31 +351,30 @@ namespace osu.Game.Screens.Menu }, new PopupDialogOkButton { - Text = "Use the default path from now on", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Only use the default path for this session", + Text = "Use default location for this session", }, }); break; case OsuStorageError.AccessibleButEmpty: - BodyText += "The custom storage path is currently being used but is empty.\n\n" - + "Have you moved the files elsewhere?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { new PopupDialogOkButton { - Text = "Reset to default", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Keep using the custom path" + Text = "Start fresh at specified location" } }); From ddac511c8c5c3b4ef641c1a80e4e9dbbe6359ce4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:51 +0900 Subject: [PATCH 166/170] Move start fresh button above --- osu.Game/Screens/Menu/MainMenu.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d64d9b69fe..dcb141cce5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,7 +332,6 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - switch (error) { case OsuStorageError.NotAccessible: @@ -367,15 +366,15 @@ namespace osu.Game.Screens.Menu // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - } }); break; From 00a2fbce06ac67d5bc077f10e43c302c98af629e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:58 +0900 Subject: [PATCH 167/170] Fix test failures --- osu.Game/IO/OsuStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 3d6903c56f..1d15294666 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -83,7 +83,7 @@ namespace osu.Game.IO { Storage userStorage = host.GetStorage(CustomStoragePath); - if (!userStorage.GetFiles(".").Any()) + if (!userStorage.ExistsDirectory(".") || !userStorage.GetFiles(".").Any()) error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); From a650a5ec83ca93d17709c9c34fb5922857e23d90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:44:26 +0900 Subject: [PATCH 168/170] Move dialog classes to own file --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 34 ++++++++ osu.Game/Screens/Menu/MainMenu.cs | 96 --------------------- osu.Game/Screens/Menu/StorageErrorDialog.cs | 79 +++++++++++++++++ 3 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Screens/Menu/ConfirmExitDialog.cs create mode 100644 osu.Game/Screens/Menu/StorageErrorDialog.cs diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs new file mode 100644 index 0000000000..d120eb21a8 --- /dev/null +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class ConfirmExitDialog : PopupDialog + { + public ConfirmExitDialog(Action confirm, Action cancel) + { + HeaderText = "Are you sure you want to exit?"; + BodyText = "Last chance to back out."; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Goodbye", + Action = confirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more", + Action = cancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcb141cce5..76950982e6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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. -using System; -using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; @@ -10,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; @@ -19,7 +16,6 @@ using osu.Game.Graphics.Containers; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; @@ -291,97 +287,5 @@ namespace osu.Game.Screens.Menu this.FadeOut(3000); return base.OnExiting(next); } - - private class ConfirmExitDialog : PopupDialog - { - public ConfirmExitDialog(Action confirm, Action cancel) - { - HeaderText = "Are you sure you want to exit?"; - BodyText = "Last chance to back out."; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Goodbye", - Action = confirm - }, - new PopupDialogCancelButton - { - Text = @"Just a little more", - Action = cancel - }, - }; - } - } - - private class StorageErrorDialog : PopupDialog - { - [Resolved] - private DialogOverlay dialogOverlay { get; set; } - - [Resolved] - private OsuGameBase osuGame { get; set; } - - public StorageErrorDialog(OsuStorage storage, OsuStorageError error) - { - HeaderText = "osu! storage error"; - Icon = FontAwesome.Solid.ExclamationTriangle; - - var buttons = new List(); - - switch (error) - { - case OsuStorageError.NotAccessible: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; - - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = "Try again", - Action = () => - { - if (!storage.TryChangeToCustomStorage(out var nextError)) - dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); - } - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, - }); - break; - - case OsuStorageError.AccessibleButEmpty: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; - - // Todo: Provide the option to search for the files similar to migration. - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - }); - - break; - } - - Buttons = buttons; - } - } } } diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs new file mode 100644 index 0000000000..38a6c07ce7 --- /dev/null +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -0,0 +1,79 @@ +// 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.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.IO; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Use default location for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + }); + + break; + } + + Buttons = buttons; + } + } +} From 3f3bfb1ffbe001ed4016b6750ff830c5f983279b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:51:16 +0900 Subject: [PATCH 169/170] Minor reshuffling / recolouring --- osu.Game/Screens/Menu/StorageErrorDialog.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index 38a6c07ce7..dcaad4013a 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Menu buttons.AddRange(new PopupDialogButton[] { - new PopupDialogOkButton + new PopupDialogCancelButton { Text = "Try again", Action = () => @@ -41,15 +41,15 @@ namespace osu.Game.Screens.Menu dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); } }, + new PopupDialogCancelButton + { + Text = "Use default location until restart", + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, }); break; From ebbc8298917db15105130ea2b12e1dab67173a88 Mon Sep 17 00:00:00 2001 From: Rsplwe Date: Tue, 7 Jul 2020 00:15:27 +0800 Subject: [PATCH 170/170] disable HardwareAccelerated --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 2e5fa59d20..9839d16030 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -9,7 +9,7 @@ using osu.Framework.Android; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuGameAndroid();