From 7137315fa7612f02bbd963e55e6b2c6ac512b892 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 17 May 2021 19:46:50 +0900
Subject: [PATCH 1/7] Remove `HitErrorDisplay` container and hook up data

---
 osu.Game/Screens/Play/HUD/HitErrorDisplay.cs  | 127 ------------------
 .../HUD/HitErrorMeters/BarHitErrorMeter.cs    |  15 ++-
 .../HUD/HitErrorMeters/ColourHitErrorMeter.cs |   4 +-
 .../Play/HUD/HitErrorMeters/HitErrorMeter.cs  |  11 +-
 osu.Game/Screens/Play/HUDOverlay.cs           |  18 +--
 5 files changed, 20 insertions(+), 155 deletions(-)
 delete mode 100644 osu.Game/Screens/Play/HUD/HitErrorDisplay.cs

diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
deleted file mode 100644
index a24d9c10cb..0000000000
--- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Configuration;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Screens.Play.HUD.HitErrorMeters;
-
-namespace osu.Game.Screens.Play.HUD
-{
-    public class HitErrorDisplay : Container<HitErrorMeter>
-    {
-        private const int fade_duration = 200;
-        private const int margin = 10;
-
-        private readonly Bindable<ScoreMeterType> type = new Bindable<ScoreMeterType>();
-
-        private readonly HitWindows hitWindows;
-
-        public HitErrorDisplay(HitWindows hitWindows)
-        {
-            this.hitWindows = hitWindows;
-
-            RelativeSizeAxes = Axes.Both;
-        }
-
-        [BackgroundDependencyLoader]
-        private void load(OsuConfigManager config)
-        {
-            config.BindWith(OsuSetting.ScoreMeter, type);
-        }
-
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-            type.BindValueChanged(typeChanged, true);
-        }
-
-        private void typeChanged(ValueChangedEvent<ScoreMeterType> type)
-        {
-            Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint));
-
-            if (hitWindows == null)
-                return;
-
-            switch (type.NewValue)
-            {
-                case ScoreMeterType.HitErrorBoth:
-                    createBar(Anchor.CentreLeft);
-                    createBar(Anchor.CentreRight);
-                    break;
-
-                case ScoreMeterType.HitErrorLeft:
-                    createBar(Anchor.CentreLeft);
-                    break;
-
-                case ScoreMeterType.HitErrorRight:
-                    createBar(Anchor.CentreRight);
-                    break;
-
-                case ScoreMeterType.HitErrorBottom:
-                    createBar(Anchor.BottomCentre);
-                    break;
-
-                case ScoreMeterType.ColourBoth:
-                    createColour(Anchor.CentreLeft);
-                    createColour(Anchor.CentreRight);
-                    break;
-
-                case ScoreMeterType.ColourLeft:
-                    createColour(Anchor.CentreLeft);
-                    break;
-
-                case ScoreMeterType.ColourRight:
-                    createColour(Anchor.CentreRight);
-                    break;
-
-                case ScoreMeterType.ColourBottom:
-                    createColour(Anchor.BottomCentre);
-                    break;
-            }
-        }
-
-        private void createBar(Anchor anchor)
-        {
-            bool rightAligned = (anchor & Anchor.x2) > 0;
-            bool bottomAligned = (anchor & Anchor.y2) > 0;
-
-            var display = new BarHitErrorMeter(hitWindows, rightAligned)
-            {
-                Margin = new MarginPadding(margin),
-                Anchor = anchor,
-                Origin = bottomAligned ? Anchor.CentreLeft : anchor,
-                Alpha = 0,
-                Rotation = bottomAligned ? 270 : 0
-            };
-
-            completeDisplayLoading(display);
-        }
-
-        private void createColour(Anchor anchor)
-        {
-            bool bottomAligned = (anchor & Anchor.y2) > 0;
-
-            var display = new ColourHitErrorMeter(hitWindows)
-            {
-                Margin = new MarginPadding(margin),
-                Anchor = anchor,
-                Origin = bottomAligned ? Anchor.CentreLeft : anchor,
-                Alpha = 0,
-                Rotation = bottomAligned ? 270 : 0
-            };
-
-            completeDisplayLoading(display);
-        }
-
-        private void completeDisplayLoading(HitErrorMeter display)
-        {
-            Add(display);
-            display.FadeInFromZero(fade_duration, Easing.OutQuint);
-        }
-    }
-}
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index 0e147f9238..c303f3889d 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -43,10 +43,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 
         private double maxHitWindow;
 
-        public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false)
-            : base(hitWindows)
+        public BarHitErrorMeter()
         {
-            alignment = rightAligned ? Anchor.x0 : Anchor.x2;
+            // todo: investigate.
+            alignment = false ? Anchor.x0 : Anchor.x2;
 
             AutoSizeAxes = Axes.Both;
         }
@@ -152,14 +152,17 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
         {
             var windows = HitWindows.GetAllAvailableWindows().ToArray();
 
-            maxHitWindow = windows.First().length;
+            // max to avoid div-by-zero.
+            maxHitWindow = Math.Max(1, windows.First().length);
 
             for (var i = 0; i < windows.Length; i++)
             {
                 var (result, length) = windows[i];
 
-                colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
-                colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
+                var hitWindow = (float)(length / maxHitWindow);
+
+                colourBarsEarly.Add(createColourBar(result, hitWindow, i == 0));
+                colourBarsLate.Add(createColourBar(result, hitWindow, i == 0));
             }
 
             // a little nub to mark the centre point.
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
index 465439cf19..0eb2367f73 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
 using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Scoring;
 using osuTK;
 using osuTK.Graphics;
 
@@ -19,8 +18,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 
         private readonly JudgementFlow judgementsFlow;
 
-        public ColourHitErrorMeter(HitWindows hitWindows)
-            : base(hitWindows)
+        public ColourHitErrorMeter()
         {
             AutoSizeAxes = Axes.Both;
             InternalChild = judgementsFlow = new JudgementFlow();
diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
index 37e9ea43c5..b0f9928b13 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs
@@ -6,13 +6,15 @@ using osu.Framework.Graphics.Containers;
 using osu.Game.Graphics;
 using osu.Game.Rulesets.Judgements;
 using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Skinning;
 using osuTK.Graphics;
 
 namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 {
-    public abstract class HitErrorMeter : CompositeDrawable
+    public abstract class HitErrorMeter : CompositeDrawable, ISkinnableDrawable
     {
-        protected readonly HitWindows HitWindows;
+        protected HitWindows HitWindows { get; private set; }
 
         [Resolved]
         private ScoreProcessor processor { get; set; }
@@ -20,9 +22,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
         [Resolved]
         private OsuColour colours { get; set; }
 
-        protected HitErrorMeter(HitWindows hitWindows)
+        [BackgroundDependencyLoader(true)]
+        private void load(DrawableRuleset drawableRuleset)
         {
-            HitWindows = hitWindows;
+            HitWindows = drawableRuleset?.FirstAvailableHitWindows ?? HitWindows.Empty;
         }
 
         protected override void LoadComplete()
diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs
index dcee64ff0d..ab5b01cab6 100644
--- a/osu.Game/Screens/Play/HUDOverlay.cs
+++ b/osu.Game/Screens/Play/HUDOverlay.cs
@@ -87,22 +87,10 @@ namespace osu.Game.Screens.Play
                 visibilityContainer = new Container
                 {
                     RelativeSizeAxes = Axes.Both,
-                    Children = new Drawable[]
+                    Child = mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
                     {
-                        mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                        },
-                        new Container
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            Children = new Drawable[]
-                            {
-                                // still need to be migrated; a bit more involved.
-                                new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
-                            }
-                        },
-                    }
+                        RelativeSizeAxes = Axes.Both,
+                    },
                 },
                 topRightElements = new FillFlowContainer
                 {

From 829d326e36cdab8a4a1be3e9624a0003d4910d48 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 18 May 2021 14:50:10 +0900
Subject: [PATCH 2/7] Remove alignment logic completely for the time being

This was overly complex and does not play well with the new layout
customisation system. We can add it back as required.
---
 .../HUD/HitErrorMeters/BarHitErrorMeter.cs    | 56 +++++++++----------
 1 file changed, 25 insertions(+), 31 deletions(-)

diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index c303f3889d..32d7f3525f 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -20,8 +20,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 {
     public class BarHitErrorMeter : HitErrorMeter
     {
-        private readonly Anchor alignment;
-
         private const int arrow_move_duration = 400;
 
         private const int judgement_line_width = 6;
@@ -45,9 +43,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 
         public BarHitErrorMeter()
         {
-            // todo: investigate.
-            alignment = false ? Anchor.x0 : Anchor.x2;
-
             AutoSizeAxes = Axes.Both;
         }
 
@@ -63,33 +58,42 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
                 Margin = new MarginPadding(2),
                 Children = new Drawable[]
                 {
-                    judgementsContainer = new Container
+                    new Container
                     {
-                        Anchor = Anchor.y1 | alignment,
-                        Origin = Anchor.y1 | alignment,
-                        Width = judgement_line_width,
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
+                        Width = chevron_size,
                         RelativeSizeAxes = Axes.Y,
+                        Child = arrow = new SpriteIcon
+                        {
+                            Anchor = Anchor.TopCentre,
+                            Origin = Anchor.Centre,
+                            RelativePositionAxes = Axes.Y,
+                            Y = 0.5f,
+                            Icon = FontAwesome.Solid.ChevronRight,
+                            Size = new Vector2(chevron_size),
+                        }
                     },
                     colourBars = new Container
                     {
                         Width = bar_width,
                         RelativeSizeAxes = Axes.Y,
-                        Anchor = Anchor.y1 | alignment,
-                        Origin = Anchor.y1 | alignment,
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
                         Children = new Drawable[]
                         {
                             colourBarsEarly = new Container
                             {
-                                Anchor = Anchor.y1 | alignment,
-                                Origin = alignment,
+                                Anchor = Anchor.CentreLeft,
+                                Origin = Anchor.x2,
                                 RelativeSizeAxes = Axes.Both,
                                 Height = 0.5f,
                                 Scale = new Vector2(1, -1),
                             },
                             colourBarsLate = new Container
                             {
-                                Anchor = Anchor.y1 | alignment,
-                                Origin = alignment,
+                                Anchor = Anchor.CentreLeft,
+                                Origin = Anchor.x2,
                                 RelativeSizeAxes = Axes.Both,
                                 Height = 0.5f,
                             },
@@ -115,21 +119,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
                             }
                         }
                     },
-                    new Container
+                    judgementsContainer = new Container
                     {
-                        Anchor = Anchor.y1 | alignment,
-                        Origin = Anchor.y1 | alignment,
-                        Width = chevron_size,
+                        Anchor = Anchor.CentreLeft,
+                        Origin = Anchor.CentreLeft,
+                        Width = judgement_line_width,
                         RelativeSizeAxes = Axes.Y,
-                        Child = arrow = new SpriteIcon
-                        {
-                            Anchor = Anchor.TopCentre,
-                            Origin = Anchor.Centre,
-                            RelativePositionAxes = Axes.Y,
-                            Y = 0.5f,
-                            Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft,
-                            Size = new Vector2(chevron_size),
-                        }
                     },
                 }
             };
@@ -167,7 +162,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
 
             // a little nub to mark the centre point.
             var centre = createColourBar(windows.Last().result, 0.01f);
-            centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2);
+            centre.Anchor = centre.Origin = Anchor.CentreLeft;
             centre.Width = 2.5f;
             colourBars.Add(centre);
 
@@ -239,8 +234,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
             judgementsContainer.Add(new JudgementLine
             {
                 Y = getRelativeJudgementPosition(judgement.TimeOffset),
-                Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2,
-                Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2),
+                Origin = Anchor.CentreLeft,
             });
 
             arrow.MoveToY(

From c885ad87d5884105319658e0e2c476a45e759d3b Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 18 May 2021 15:12:29 +0900
Subject: [PATCH 3/7] Update `HitErrorDisplay` tests

---
 .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 71 +++++++++++++++----
 1 file changed, 58 insertions(+), 13 deletions(-)

diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index 6cefd01aab..2c5443fe08 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -1,6 +1,9 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.CodeAnalysis;
 using NUnit.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
@@ -11,29 +14,35 @@ using osu.Game.Graphics.Sprites;
 using osu.Game.Rulesets.Catch.Scoring;
 using osu.Game.Rulesets.Judgements;
 using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Osu.Scoring;
 using osu.Game.Rulesets.Scoring;
 using osu.Game.Rulesets.Taiko.Scoring;
+using osu.Game.Rulesets.UI;
+using osu.Game.Scoring;
 using osu.Game.Screens.Play.HUD.HitErrorMeters;
 
 namespace osu.Game.Tests.Visual.Gameplay
 {
     public class TestSceneHitErrorMeter : OsuTestScene
     {
-        private HitWindows hitWindows;
-
         [Cached]
         private ScoreProcessor scoreProcessor = new ScoreProcessor();
 
+        [Cached(typeof(DrawableRuleset))]
+        private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset();
+
         public TestSceneHitErrorMeter()
         {
             recreateDisplay(new OsuHitWindows(), 5);
 
             AddRepeatStep("New random judgement", () => newJudgement(), 40);
 
-            AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
-            AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
+            AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
+            AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
             AddStep("New fixed judgement (50ms)", () => newJudgement(50));
 
             AddStep("Judgement barrage", () =>
@@ -83,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay
 
         private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
         {
-            this.hitWindows = hitWindows;
-
             hitWindows?.SetDifficulty(overallDifficulty);
 
+            drawableRuleset.HitWindows = hitWindows;
+
             Clear();
 
             Add(new FillFlowContainer
@@ -103,40 +112,40 @@ namespace osu.Game.Tests.Visual.Gameplay
                 }
             });
 
-            Add(new BarHitErrorMeter(hitWindows, true)
+            Add(new BarHitErrorMeter
             {
                 Anchor = Anchor.CentreRight,
                 Origin = Anchor.CentreRight,
             });
 
-            Add(new BarHitErrorMeter(hitWindows, false)
+            Add(new BarHitErrorMeter
             {
                 Anchor = Anchor.CentreLeft,
                 Origin = Anchor.CentreLeft,
             });
 
-            Add(new BarHitErrorMeter(hitWindows, true)
+            Add(new BarHitErrorMeter
             {
                 Anchor = Anchor.BottomCentre,
                 Origin = Anchor.CentreLeft,
                 Rotation = 270,
             });
 
-            Add(new ColourHitErrorMeter(hitWindows)
+            Add(new ColourHitErrorMeter
             {
                 Anchor = Anchor.CentreRight,
                 Origin = Anchor.CentreRight,
                 Margin = new MarginPadding { Right = 50 }
             });
 
-            Add(new ColourHitErrorMeter(hitWindows)
+            Add(new ColourHitErrorMeter
             {
                 Anchor = Anchor.CentreLeft,
                 Origin = Anchor.CentreLeft,
                 Margin = new MarginPadding { Left = 50 }
             });
 
-            Add(new ColourHitErrorMeter(hitWindows)
+            Add(new ColourHitErrorMeter
             {
                 Anchor = Anchor.BottomCentre,
                 Origin = Anchor.CentreLeft,
@@ -147,11 +156,47 @@ namespace osu.Game.Tests.Visual.Gameplay
 
         private void newJudgement(double offset = 0)
         {
-            scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
+            scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement())
             {
                 TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
                 Type = HitResult.Perfect,
             });
         }
+
+        [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
+        private class TestDrawableRuleset : DrawableRuleset
+        {
+            public HitWindows HitWindows;
+
+            public override IEnumerable<HitObject> Objects => new[] { new HitCircle { HitWindows = HitWindows } };
+
+            public override event Action<JudgementResult> NewResult;
+            public override event Action<JudgementResult> RevertResult;
+
+            public override Playfield Playfield { get; }
+            public override Container Overlays { get; }
+            public override Container FrameStableComponents { get; }
+            public override IFrameStableClock FrameStableClock { get; }
+            public override IReadOnlyList<Mod> Mods { get; }
+
+            public override double GameplayStartTime { get; }
+            public override GameplayCursorContainer Cursor { get; }
+
+            public TestDrawableRuleset()
+                : base(new OsuRuleset())
+            {
+                // won't compile without this.
+                NewResult?.Invoke(null);
+                RevertResult?.Invoke(null);
+            }
+
+            public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();
+
+            public override void SetRecordTarget(Score score) => throw new NotImplementedException();
+
+            public override void RequestResume(Action continueResume) => throw new NotImplementedException();
+
+            public override void CancelResume() => throw new NotImplementedException();
+        }
     }
 }

From 5acb708939776029f96e6ec7bca92cec86ca3a40 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 18 May 2021 15:50:14 +0900
Subject: [PATCH 4/7] Remove customisation of hit error via standard settings

---
 osu.Game/Configuration/OsuConfigManager.cs    |  2 -
 osu.Game/Configuration/ScoreMeterType.cs      | 37 -------------------
 .../Sections/Gameplay/GeneralSettings.cs      |  5 ---
 3 files changed, 44 deletions(-)
 delete mode 100644 osu.Game/Configuration/ScoreMeterType.cs

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 09412b1f1b..43bbd725c3 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -104,7 +104,6 @@ namespace osu.Game.Configuration
             SetDefault(OsuSetting.KeyOverlay, false);
             SetDefault(OsuSetting.PositionalHitSounds, true);
             SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
-            SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
 
             SetDefault(OsuSetting.FloatingComments, false);
 
@@ -213,7 +212,6 @@ namespace osu.Game.Configuration
         KeyOverlay,
         PositionalHitSounds,
         AlwaysPlayFirstComboBreak,
-        ScoreMeter,
         FloatingComments,
         HUDVisibilityMode,
         ShowProgressGraph,
diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs
deleted file mode 100644
index ddbd2327c2..0000000000
--- a/osu.Game/Configuration/ScoreMeterType.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.ComponentModel;
-
-namespace osu.Game.Configuration
-{
-    public enum ScoreMeterType
-    {
-        [Description("None")]
-        None,
-
-        [Description("Hit Error (left)")]
-        HitErrorLeft,
-
-        [Description("Hit Error (right)")]
-        HitErrorRight,
-
-        [Description("Hit Error (left+right)")]
-        HitErrorBoth,
-
-        [Description("Hit Error (bottom)")]
-        HitErrorBottom,
-
-        [Description("Colour (left)")]
-        ColourLeft,
-
-        [Description("Colour (right)")]
-        ColourRight,
-
-        [Description("Colour (left+right)")]
-        ColourBoth,
-
-        [Description("Colour (bottom)")]
-        ColourBottom,
-    }
-}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index be464fa2b7..0b5ec4f338 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -73,11 +73,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
                     LabelText = "Always play first combo break sound",
                     Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
                 },
-                new SettingsEnumDropdown<ScoreMeterType>
-                {
-                    LabelText = "Score meter type",
-                    Current = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
-                },
                 new SettingsEnumDropdown<ScoringMode>
                 {
                     LabelText = "Score display mode",

From 10c730b37d569e13f0dc9c522b57911b6671d7b8 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 18 May 2021 15:50:40 +0900
Subject: [PATCH 5/7] Add new default locations for hit bar error displays

---
 osu.Game/Screens/Play/SongProgress.cs  | 10 +++++++---
 osu.Game/Skinning/DefaultSkin.cs       | 26 ++++++++++++++++++++++++++
 osu.Game/Skinning/HUDSkinComponents.cs |  2 ++
 osu.Game/Skinning/LegacySkin.cs        | 16 ++++++++++++++++
 4 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs
index b7939b5e75..cab44c7473 100644
--- a/osu.Game/Screens/Play/SongProgress.cs
+++ b/osu.Game/Screens/Play/SongProgress.cs
@@ -20,10 +20,14 @@ namespace osu.Game.Screens.Play
 {
     public class SongProgress : OverlayContainer, ISkinnableDrawable
     {
-        private const int info_height = 20;
-        private const int bottom_bar_height = 5;
+        public const float MAX_HEIGHT = info_height + bottom_bar_height + graph_height + handle_height;
+
+        private const float info_height = 20;
+        private const float bottom_bar_height = 5;
         private const float graph_height = SquareGraph.Column.WIDTH * 6;
-        private static readonly Vector2 handle_size = new Vector2(10, 18);
+        private const float handle_height = 18;
+
+        private static readonly Vector2 handle_size = new Vector2(10, handle_height);
 
         private const float transition_duration = 200;
 
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index d13ddcf22b..84f40df0cf 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -14,6 +14,7 @@ using osu.Game.Extensions;
 using osu.Game.IO;
 using osu.Game.Screens.Play;
 using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Play.HUD.HitErrorMeters;
 using osuTK;
 using osuTK.Graphics;
 
@@ -78,6 +79,23 @@ namespace osu.Game.Skinning
                                         combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5);
                                         combo.Anchor = Anchor.TopCentre;
                                     }
+
+                                    var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
+
+                                    if (hitError != null)
+                                    {
+                                        hitError.Anchor = Anchor.CentreLeft;
+                                        hitError.Origin = Anchor.CentreLeft;
+                                    }
+
+                                    var hitError2 = container.OfType<HitErrorMeter>().LastOrDefault();
+
+                                    if (hitError2 != null)
+                                    {
+                                        hitError2.Anchor = Anchor.CentreRight;
+                                        hitError2.Origin = Anchor.CentreLeft;
+                                        hitError2.Scale = new Vector2(-1, 1);
+                                    }
                                 }
                             })
                             {
@@ -88,6 +106,8 @@ namespace osu.Game.Skinning
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)),
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)),
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)),
+                                    GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)),
+                                    GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)),
                                 }
                             };
 
@@ -114,6 +134,12 @@ namespace osu.Game.Skinning
 
                         case HUDSkinComponents.SongProgress:
                             return new SongProgress();
+
+                        case HUDSkinComponents.BarHitErrorMeter:
+                            return new BarHitErrorMeter();
+
+                        case HUDSkinComponents.ColourHitErrorMeter:
+                            return new ColourHitErrorMeter();
                     }
 
                     break;
diff --git a/osu.Game/Skinning/HUDSkinComponents.cs b/osu.Game/Skinning/HUDSkinComponents.cs
index 2e6c3a9937..ea39c98635 100644
--- a/osu.Game/Skinning/HUDSkinComponents.cs
+++ b/osu.Game/Skinning/HUDSkinComponents.cs
@@ -10,5 +10,7 @@ namespace osu.Game.Skinning
         AccuracyCounter,
         HealthDisplay,
         SongProgress,
+        BarHitErrorMeter,
+        ColourHitErrorMeter,
     }
 }
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 6c8d6ee45a..7a64f38840 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -19,6 +19,7 @@ using osu.Game.IO;
 using osu.Game.Rulesets.Scoring;
 using osu.Game.Screens.Play;
 using osu.Game.Screens.Play.HUD;
+using osu.Game.Screens.Play.HUD.HitErrorMeters;
 using osuTK.Graphics;
 
 namespace osu.Game.Skinning
@@ -342,6 +343,20 @@ namespace osu.Game.Skinning
                                 {
                                     accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y;
                                 }
+
+                                var songProgress = container.OfType<SongProgress>().FirstOrDefault();
+
+                                var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
+
+                                if (hitError != null)
+                                {
+                                    hitError.Anchor = Anchor.BottomCentre;
+                                    hitError.Origin = Anchor.CentreLeft;
+                                    hitError.Rotation = -90;
+
+                                    if (songProgress != null)
+                                        hitError.Y -= SongProgress.MAX_HEIGHT;
+                                }
                             })
                             {
                                 Children = new[]
@@ -352,6 +367,7 @@ namespace osu.Game.Skinning
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
                                     GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(),
+                                    GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)) ?? new BarHitErrorMeter(),
                                 }
                             };
 

From 00ed6993400e35f6ba5c7416219932e90e63bd08 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 20 May 2021 01:53:24 +0900
Subject: [PATCH 6/7] Fix origin specifications using incorrect flags

---
 osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
index 32d7f3525f..5d0263772d 100644
--- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
+++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
                             colourBarsEarly = new Container
                             {
                                 Anchor = Anchor.CentreLeft,
-                                Origin = Anchor.x2,
+                                Origin = Anchor.TopRight,
                                 RelativeSizeAxes = Axes.Both,
                                 Height = 0.5f,
                                 Scale = new Vector2(1, -1),
@@ -93,7 +93,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
                             colourBarsLate = new Container
                             {
                                 Anchor = Anchor.CentreLeft,
-                                Origin = Anchor.x2,
+                                Origin = Anchor.TopRight,
                                 RelativeSizeAxes = Axes.Both,
                                 Height = 0.5f,
                             },

From 22337e0fc79c6e45783eb5af137db8df93378244 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 20 May 2021 01:59:30 +0900
Subject: [PATCH 7/7] Add comment explaining why origin is flipped

---
 osu.Game/Skinning/DefaultSkin.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index 84f40df0cf..ba31816a07 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -93,8 +93,9 @@ namespace osu.Game.Skinning
                                     if (hitError2 != null)
                                     {
                                         hitError2.Anchor = Anchor.CentreRight;
-                                        hitError2.Origin = Anchor.CentreLeft;
                                         hitError2.Scale = new Vector2(-1, 1);
+                                        // origin flipped to match scale above.
+                                        hitError2.Origin = Anchor.CentreLeft;
                                     }
                                 }
                             })