From b318b770d4ea82efdb6f24b11f1cf131af77d2bd Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Wed, 13 Jun 2018 19:18:11 +0900
Subject: [PATCH 01/14] Fix pixellation of volume meter progress bars

---
 osu.Game.Tests/Visual/TestCaseVolumePieces.cs |  13 +-
 osu.Game/Overlays/Volume/VolumeMeter.cs       | 200 +++++++++++-------
 2 files changed, 139 insertions(+), 74 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs
index 449f48b7d7..3c5b91ccd2 100644
--- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs
+++ b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using osu.Framework.Graphics;
 using osu.Game.Overlays.Volume;
+using OpenTK;
 using OpenTK.Graphics;
 
 namespace osu.Game.Tests.Visual
@@ -17,13 +18,21 @@ namespace osu.Game.Tests.Visual
         {
             VolumeMeter meter;
             MuteButton mute;
-            Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue));
+            Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue) { Position = new Vector2(10) });
+            AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
+
+            Add(new VolumeMeter("BIG", 250, Color4.Red)
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                Position = new Vector2(10),
+            });
+
             Add(mute = new MuteButton
             {
                 Margin = new MarginPadding { Top = 200 }
             });
 
-            AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
             AddToggleStep("mute", b => mute.Current.Value = b);
         }
     }
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index b2cf43704b..0e43945f8c 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Volume
     public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
     {
         private CircularProgress volumeCircle;
+        private CircularProgress volumeCircleGlow;
+
         public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 };
         private readonly float circleSize;
         private readonly Color4 meterColour;
@@ -44,90 +46,143 @@ namespace osu.Game.Overlays.Volume
         [BackgroundDependencyLoader]
         private void load(OsuColour colours)
         {
-            Add(new Container
-            {
-                Size = new Vector2(120, 20),
-                CornerRadius = 10,
-                Masking = true,
-                Margin = new MarginPadding { Left = circleSize + 10 },
-                Origin = Anchor.CentreLeft,
-                Anchor = Anchor.CentreLeft,
-                Children = new Drawable[]
-                {
-                    new Box
-                    {
-                        RelativeSizeAxes = Axes.Both,
-                        Colour = colours.Gray1,
-                        Alpha = 0.9f,
-                    },
-                    new OsuSpriteText
-                    {
-                        Anchor = Anchor.Centre,
-                        Origin = Anchor.Centre,
-                        Font = "Exo2.0-Bold",
-                        Text = name
-                    }
-                }
-            });
+            Color4 backgroundColour = colours.Gray1;
 
             CircularProgress bgProgress;
 
-            Add(new CircularContainer
+            const float progress_start_radius = 0.75f;
+            const float progress_size = 0.03f;
+            const float progress_end_radius = progress_start_radius + progress_size;
+
+            const float blur_amount = 5;
+
+            Children = new Drawable[]
             {
-                Masking = true,
-                Size = new Vector2(circleSize),
-                Children = new Drawable[]
+                new Container
                 {
-                    new Box
+                    Size = new Vector2(circleSize),
+                    Children = new Drawable[]
                     {
-                        RelativeSizeAxes = Axes.Both,
-                        Colour = colours.Gray1,
-                        Alpha = 0.9f,
-                    },
-                    bgProgress = new CircularProgress
-                    {
-                        RelativeSizeAxes = Axes.Both,
-                        InnerRadius = 0.05f,
-                        Rotation = 180,
-                        Anchor = Anchor.Centre,
-                        Origin = Anchor.Centre,
-                        Colour = colours.Gray2,
-                        Size = new Vector2(0.8f)
-                    },
-                    new Container
-                    {
-                        Anchor = Anchor.Centre,
-                        Origin = Anchor.Centre,
-                        RelativeSizeAxes = Axes.Both,
-                        Size = new Vector2(0.8f),
-                        Padding = new MarginPadding(-Blur.KernelSize(5)),
-                        Rotation = 180,
-                        Child = (volumeCircle = new CircularProgress
+                        new BufferedContainer
                         {
+                            Alpha = 0.9f,
                             RelativeSizeAxes = Axes.Both,
-                            InnerRadius = 0.05f,
+                            Children = new Drawable[]
+                            {
+                                new Circle
+                                {
+                                    RelativeSizeAxes = Axes.Both,
+                                    Colour = backgroundColour,
+                                },
+                                new CircularContainer
+                                {
+                                    Masking = true,
+                                    Anchor = Anchor.Centre,
+                                    Origin = Anchor.Centre,
+                                    RelativeSizeAxes = Axes.Both,
+                                    Size = new Vector2(progress_end_radius),
+                                    Children = new Drawable[]
+                                    {
+                                        bgProgress = new CircularProgress
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            RelativeSizeAxes = Axes.Both,
+                                            Rotation = 180,
+                                            Colour = backgroundColour,
+                                        },
+                                        new Container
+                                        {
+                                            Anchor = Anchor.Centre,
+                                            Origin = Anchor.Centre,
+                                            Name = "Progress under covers for smoothing",
+                                            RelativeSizeAxes = Axes.Both,
+                                            Rotation = 180,
+                                            Child = volumeCircle = new CircularProgress
+                                            {
+                                                RelativeSizeAxes = Axes.Both,
+                                            }
+                                        },
+                                    }
+                                },
+                                new Circle
+                                {
+                                    Name = "Inner Cover",
+                                    Anchor = Anchor.Centre,
+                                    Origin = Anchor.Centre,
+                                    RelativeSizeAxes = Axes.Both,
+                                    Colour = backgroundColour,
+                                    Size = new Vector2(progress_start_radius),
+                                },
+                                new Container
+                                {
+                                    Name = "Progress overlay for glow",
+                                    Anchor = Anchor.Centre,
+                                    Origin = Anchor.Centre,
+                                    RelativeSizeAxes = Axes.Both,
+                                    Size = new Vector2(progress_start_radius + progress_size / 1.5f),
+                                    Rotation = 180,
+                                    Padding = new MarginPadding(-Blur.KernelSize(blur_amount)),
+                                    Child = (volumeCircleGlow = new CircularProgress
+                                    {
+                                        RelativeSizeAxes = Axes.Both,
+                                        InnerRadius = progress_size * 0.8f,
+                                    }).WithEffect(new GlowEffect
+                                    {
+                                        Colour = meterColour,
+                                        BlurSigma = new Vector2(blur_amount),
+                                        Strength = 5,
+                                        PadExtent = true
+                                    }),
+                                },
+                            },
+                        },
+                        maxGlow = (text = new OsuSpriteText
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            Font = "Venera",
+                            TextSize = 0.16f * circleSize
                         }).WithEffect(new GlowEffect
                         {
-                            Colour = meterColour,
-                            Strength = 2,
-                            PadExtent = true
-                        }),
-                    },
-                    maxGlow = (text = new OsuSpriteText
+                            Colour = Color4.Transparent,
+                            PadExtent = true,
+                        })
+                    }
+                },
+                new Container
+                {
+                    Size = new Vector2(120, 20),
+                    CornerRadius = 10,
+                    Masking = true,
+                    Margin = new MarginPadding { Left = circleSize + 10 },
+                    Origin = Anchor.CentreLeft,
+                    Anchor = Anchor.CentreLeft,
+                    Children = new Drawable[]
                     {
-                        Anchor = Anchor.Centre,
-                        Origin = Anchor.Centre,
-                        Font = "Venera",
-                        TextSize = 0.16f * circleSize
-                    }).WithEffect(new GlowEffect
-                    {
-                        Colour = Color4.Transparent,
-                        PadExtent = true,
-                    })
+                        new Box
+                        {
+                            Alpha = 0.9f,
+                            RelativeSizeAxes = Axes.Both,
+                            Colour = backgroundColour,
+                        },
+                        new OsuSpriteText
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            Font = "Exo2.0-Bold",
+                            Text = name
+                        }
+                    }
                 }
-            });
-
-            Bindable.ValueChanged += newVolume => { this.TransformTo("DisplayVolume", newVolume, 400, Easing.OutQuint); };
+            };
+            Bindable.ValueChanged += newVolume =>
+            {
+                this.TransformTo("DisplayVolume",
+                    newVolume,
+                    400,
+                    Easing.OutQuint);
+            };
             bgProgress.Current.Value = 0.75f;
         }
 
@@ -158,6 +213,7 @@ namespace osu.Game.Overlays.Volume
                 }
 
                 volumeCircle.Current.Value = displayVolume * 0.75f;
+                volumeCircleGlow.Current.Value = displayVolume * 0.75f;
             }
         }
 

From c41a50bf24635c94c719eefcde8272d4147eda4e Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 14 Jun 2018 16:39:31 +0900
Subject: [PATCH 02/14] Fix combos ending with JuiceStreams never leaving the
 catcher's plate

Closes #2634.
---
 osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index ae799875a9..b2d8e3f8a5 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -124,6 +124,9 @@ namespace osu.Game.Rulesets.Catch.Objects
                     X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
                 });
             }
+
+            if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
+                lastNested.LastInCombo = LastInCombo;
         }
 
         public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;

From b25a6a33ccdde95af2a05739661b211cf0d7a3fd Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 19:37:21 +0900
Subject: [PATCH 03/14] Fix 1K breaking hitcircle / slider conversions

---
 .../Patterns/Legacy/DistanceObjectPatternGenerator.cs      | 7 +++++++
 .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs  | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index afa9bdbbd7..f60958d581 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -58,6 +58,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
         public override Pattern Generate()
         {
+            if (TotalColumns == 1)
+            {
+                var pattern = new Pattern();
+                addToPattern(pattern, 0, HitObject.StartTime, endTime);
+                return pattern;
+            }
+
             if (spanCount > 1)
             {
                 if (segmentDuration <= 90)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index cec3e18ad6..c72005e3ad 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -81,6 +81,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
         public override Pattern Generate()
         {
+            if (TotalColumns == 1)
+            {
+                var pattern = new Pattern();
+                addToPattern(pattern, 0);
+                return pattern;
+            }
+
             int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
 
             if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())

From a3bf16e481bc5489ba809ef97e6326c565b0fc97 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 19:37:32 +0900
Subject: [PATCH 04/14] Fix missing convert type

---
 .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index c72005e3ad..339a1b4e77 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -77,6 +77,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             }
             else
                 convertType |= PatternType.LowProbability;
+
+            if ((convertType & PatternType.KeepSingle) == 0)
+            {
+                if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
+                    convertType |= PatternType.Mirror;
+                else
+                    convertType |= PatternType.Gathered;
+            }
         }
 
         public override Pattern Generate()

From fd84afb89bfa052f9a13139721d74b8932fe1c0e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 19:38:48 +0900
Subject: [PATCH 05/14] Fix non-inverted calculation

---
 .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 339a1b4e77..b4160dc98b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -361,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
             addToCentre = false;
 
             if ((convertType & PatternType.ForceNotStack) > 0)
-                return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
+                return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
 
             switch (TotalColumns)
             {

From 5e66b021085c1214da2c350ebe96688c5aabf0f2 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 13 Jun 2018 18:38:27 +0900
Subject: [PATCH 06/14] Process beatmap before generating mappings

---
 .../Tests/Beatmaps/BeatmapConversionTest.cs   | 21 ++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 7470f6ebed..c5f74af770 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -84,19 +84,26 @@ namespace osu.Game.Tests.Beatmaps
             beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
 
             var result = new ConvertResult();
-
             var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
+
+            List<KeyValuePair<HitObject, IEnumerable<HitObject>>> conversions = new List<KeyValuePair<HitObject, IEnumerable<HitObject>>>();
+
             converter.ObjectConverted += (orig, converted) =>
             {
+                conversions.Add(new KeyValuePair<HitObject, IEnumerable<HitObject>>(orig, converted));
                 converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
-
-                var mapping = new ConvertMapping { StartTime = orig.StartTime };
-                foreach (var obj in converted)
-                    mapping.Objects.AddRange(CreateConvertValue(obj));
-                result.Mappings.Add(mapping);
             };
 
-            converter.Convert();
+            IBeatmap convertedBeatmap = converter.Convert();
+            rulesetInstance.CreateBeatmapProcessor(convertedBeatmap).PostProcess();
+
+            foreach (var pair in conversions)
+            {
+                var mapping = new ConvertMapping { StartTime = pair.Key.StartTime };
+                foreach (var obj in pair.Value)
+                    mapping.Objects.AddRange(CreateConvertValue(obj));
+                result.Mappings.Add(mapping);
+            }
 
             return result;
         }

From 8d0e7abdd62c977ffc53d408430faf967604ced3 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 13 Jun 2018 21:17:27 +0900
Subject: [PATCH 07/14] Some rulesets don't have a beatmap processor

---
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index c5f74af770..4b0da3a14b 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps
             var result = new ConvertResult();
             var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
 
-            List<KeyValuePair<HitObject, IEnumerable<HitObject>>> conversions = new List<KeyValuePair<HitObject, IEnumerable<HitObject>>>();
+            var conversions = new List<KeyValuePair<HitObject, IEnumerable<HitObject>>>();
 
             converter.ObjectConverted += (orig, converted) =>
             {
@@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps
             };
 
             IBeatmap convertedBeatmap = converter.Convert();
-            rulesetInstance.CreateBeatmapProcessor(convertedBeatmap).PostProcess();
+            rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess();
 
             foreach (var pair in conversions)
             {

From 024d2abfe09ea3c1200a587ebcef17311d738238 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 20:26:55 +0900
Subject: [PATCH 08/14] Always generate mappings/convert values as soon as
 objects are converted

# Conflicts:
#	osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
---
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 4b0da3a14b..424ebe0cf3 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -86,25 +86,19 @@ namespace osu.Game.Tests.Beatmaps
             var result = new ConvertResult();
             var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
 
-            var conversions = new List<KeyValuePair<HitObject, IEnumerable<HitObject>>>();
-
             converter.ObjectConverted += (orig, converted) =>
             {
-                conversions.Add(new KeyValuePair<HitObject, IEnumerable<HitObject>>(orig, converted));
                 converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
+
+                var mapping = new ConvertMapping { StartTime = orig.StartTime };
+                foreach (var obj in converted)
+                    mapping.Objects.AddRange(CreateConvertValue(obj));
+                result.Mappings.Add(mapping);
             };
 
             IBeatmap convertedBeatmap = converter.Convert();
             rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess();
 
-            foreach (var pair in conversions)
-            {
-                var mapping = new ConvertMapping { StartTime = pair.Key.StartTime };
-                foreach (var obj in pair.Value)
-                    mapping.Objects.AddRange(CreateConvertValue(obj));
-                result.Mappings.Add(mapping);
-            }
-
             return result;
         }
 

From b99b520656dc8ae9cda7fc5ef1da13801e2ae858 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 20:28:29 +0900
Subject: [PATCH 09/14] Allow convertmapping to be extended

---
 .../Tests/Beatmaps/BeatmapConversionTest.cs   | 68 +++++++++++++++----
 1 file changed, 54 insertions(+), 14 deletions(-)

diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 424ebe0cf3..9f15949f7b 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -16,7 +16,8 @@ using osu.Game.Rulesets.Objects;
 namespace osu.Game.Tests.Beatmaps
 {
     [TestFixture]
-    public abstract class BeatmapConversionTest<TConvertValue>
+    public abstract class BeatmapConversionTest<TConvertMapping, TConvertValue>
+        where TConvertMapping : ConvertMapping<TConvertValue>, IEquatable<TConvertMapping>, new()
         where TConvertValue : IEquatable<TConvertValue>
     {
         private const string resource_namespace = "Testing.Beatmaps";
@@ -59,9 +60,13 @@ namespace osu.Game.Tests.Beatmaps
                                 else if (objectCounter >= expectedMapping.Objects.Count)
                                     Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
                                                 + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
-                                else if (!EqualityComparer<TConvertValue>.Default.Equals(expectedMapping.Objects[objectCounter], ourMapping.Objects[objectCounter]))
+                                else if (!expectedMapping.Equals(ourMapping))
+                                    Assert.Fail($"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
+                                                + $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
+                                                + $"Received: {JsonConvert.SerializeObject(ourMapping)}");
+                                else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter]))
                                 {
-                                    Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}\n"
+                                    Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n"
                                                 + $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
                                                 + $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
                                 }
@@ -90,7 +95,9 @@ namespace osu.Game.Tests.Beatmaps
             {
                 converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
 
-                var mapping = new ConvertMapping { StartTime = orig.StartTime };
+                var mapping = CreateConvertMapping();
+                mapping.StartTime = orig.StartTime;
+
                 foreach (var obj in converted)
                     mapping.Objects.AddRange(CreateConvertValue(obj));
                 result.Mappings.Add(mapping);
@@ -129,21 +136,54 @@ namespace osu.Game.Tests.Beatmaps
             return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
         }
 
-        protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
-        protected abstract Ruleset CreateRuleset();
+        /// <summary>
+        /// Creates the conversion mapping for a <see cref="HitObject"/>. A conversion mapping stores important information about the conversion process.
+        /// This is generated _after_ the <see cref="HitObject"/> has been converted.
+        /// <para>
+        /// This should be used to validate the integrity of the conversion process after a conversion has occurred.
+        /// </para>
+        /// </summary>
+        protected virtual TConvertMapping CreateConvertMapping() => new TConvertMapping();
 
-        private class ConvertMapping
-        {
-            [JsonProperty]
-            public double StartTime;
-            [JsonProperty]
-            public List<TConvertValue> Objects = new List<TConvertValue>();
-        }
+        /// <summary>
+        /// Creates the conversion value for a <see cref="HitObject"/>. A conversion value stores information about the converted <see cref="HitObject"/>.
+        /// <para>
+        /// This should be used to validate the integrity of the converted <see cref="HitObject"/>.
+        /// </para>
+        /// </summary>
+        /// <param name="hitObject">The converted <see cref="HitObject"/>.</param>
+        protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
+
+        /// <summary>
+        /// Creates the <see cref="Ruleset"/> applicable to this <see cref="BeatmapConversionTest{TConvertMapping,TConvertValue}"/>.
+        /// </summary>
+        /// <returns></returns>
+        protected abstract Ruleset CreateRuleset();
 
         private class ConvertResult
         {
             [JsonProperty]
-            public List<ConvertMapping> Mappings = new List<ConvertMapping>();
+            public List<TConvertMapping> Mappings = new List<TConvertMapping>();
         }
     }
+
+    public abstract class BeatmapConversionTest<TConvertValue> : BeatmapConversionTest<ConvertMapping<TConvertValue>, TConvertValue>
+        where TConvertValue : IEquatable<TConvertValue>
+    {
+    }
+
+    public class ConvertMapping<TConvertValue> : IEquatable<ConvertMapping<TConvertValue>>
+        where TConvertValue : IEquatable<TConvertValue>
+    {
+        [JsonProperty]
+        public double StartTime;
+
+        [JsonIgnore]
+        public List<TConvertValue> Objects = new List<TConvertValue>();
+
+        [JsonProperty("Objects")]
+        private List<TConvertValue> setObjects { set => Objects = value; }
+
+        public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime.Equals(other?.StartTime);
+    }
 }

From a4d236408378ea34fd93af06e6c77d775072c59e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 14 Jun 2018 21:29:08 +0900
Subject: [PATCH 10/14] Add one more newline

---
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 9f15949f7b..cf4dda52a8 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Tests.Beatmaps
                                 else if (!expectedMapping.Equals(ourMapping))
                                     Assert.Fail($"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
                                                 + $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
-                                                + $"Received: {JsonConvert.SerializeObject(ourMapping)}");
+                                                + $"Received: {JsonConvert.SerializeObject(ourMapping)}\n");
                                 else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter]))
                                 {
                                     Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n"

From 069d939e292bc072423fe8852d2ae003e80ebfe9 Mon Sep 17 00:00:00 2001
From: Joehu <jmadamba07@hotmail.com>
Date: Thu, 14 Jun 2018 21:53:01 -0700
Subject: [PATCH 11/14] Remove "from" prefix from sources on direct panels

---
 osu.Game/Overlays/Direct/DirectGridPanel.cs | 2 +-
 osu.Game/Overlays/Direct/DirectListPanel.cs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs
index 723e9e8b35..e286837746 100644
--- a/osu.Game/Overlays/Direct/DirectGridPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs
@@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Direct
                                             {
                                                 new OsuSpriteText
                                                 {
-                                                    Text = $"{SetInfo.Metadata.Source}",
+                                                    Text = SetInfo.Metadata.Source,
                                                     TextSize = 14,
                                                     Shadow = false,
                                                     Colour = colours.Gray5,
diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs
index 6e3483604b..812a0e2073 100644
--- a/osu.Game/Overlays/Direct/DirectListPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectListPanel.cs
@@ -160,7 +160,7 @@ namespace osu.Game.Overlays.Direct
                                         },
                                         new OsuSpriteText
                                         {
-                                            Text = $"from {SetInfo.Metadata.Source}",
+                                            Text = SetInfo.Metadata.Source,
                                             Anchor = Anchor.TopRight,
                                             Origin = Anchor.TopRight,
                                             TextSize = 14,

From 1c27ef644a53340f8d3883128789ae71bbc6cb8a Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Jun 2018 16:12:11 +0900
Subject: [PATCH 12/14] Fix music controller drag activating from anywhere on
 the screen

---
 osu.Game/Overlays/MusicController.cs | 61 +++++++++++++++-------------
 1 file changed, 32 insertions(+), 29 deletions(-)

diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index d96bb40165..a57d5fd183 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -66,34 +66,6 @@ namespace osu.Game.Overlays
             AlwaysPresent = true;
         }
 
-        private Vector2 dragStart;
-
-        protected override bool OnDragStart(InputState state)
-        {
-            base.OnDragStart(state);
-            dragStart = state.Mouse.Position;
-            return true;
-        }
-
-        protected override bool OnDrag(InputState state)
-        {
-            if (base.OnDrag(state)) return true;
-
-            Vector2 change = state.Mouse.Position - dragStart;
-
-            // Diminish the drag distance as we go further to simulate "rubber band" feeling.
-            change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
-
-            dragContainer.MoveTo(change);
-            return true;
-        }
-
-        protected override bool OnDragEnd(InputState state)
-        {
-            dragContainer.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
-            return base.OnDragEnd(state);
-        }
-
         [BackgroundDependencyLoader]
         private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
         {
@@ -103,7 +75,7 @@ namespace osu.Game.Overlays
 
             Children = new Drawable[]
             {
-                dragContainer = new Container
+                dragContainer = new DragContainer
                 {
                     Anchor = Anchor.Centre,
                     Origin = Anchor.Centre,
@@ -470,5 +442,36 @@ namespace osu.Game.Overlays
                 sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
             }
         }
+
+        private class DragContainer : Container
+        {
+            private Vector2 dragStart;
+
+            protected override bool OnDragStart(InputState state)
+            {
+                base.OnDragStart(state);
+                dragStart = state.Mouse.Position;
+                return true;
+            }
+
+            protected override bool OnDrag(InputState state)
+            {
+                if (base.OnDrag(state)) return true;
+
+                Vector2 change = state.Mouse.Position - dragStart;
+
+                // Diminish the drag distance as we go further to simulate "rubber band" feeling.
+                change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
+
+                this.MoveTo(change);
+                return true;
+            }
+
+            protected override bool OnDragEnd(InputState state)
+            {
+                this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
+                return base.OnDragEnd(state);
+            }
+        }
     }
 }

From a2950b1d80d6efeece366c6be4b807a88d434dc6 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 15 Jun 2018 16:12:18 +0900
Subject: [PATCH 13/14] Fix incorrect comment

---
 osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 0186a170c9..062f8d27aa 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Graphics.Containers
 
         /// <summary>
         /// Whether mouse input should be blocked screen-wide while this overlay is visible.
-        /// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through.
+        /// Performing mouse actions outside of the valid extents will hide the overlay and block the input.
         /// </summary>
         public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;
 

From aaf3ef240e4e40a49768bf051ed8157ad1e5894e Mon Sep 17 00:00:00 2001
From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com>
Date: Fri, 15 Jun 2018 17:07:07 +0900
Subject: [PATCH 14/14] Remove easily-confused comment

---
 osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 062f8d27aa..0528f7b3ae 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Graphics.Containers
 
         /// <summary>
         /// Whether mouse input should be blocked screen-wide while this overlay is visible.
-        /// Performing mouse actions outside of the valid extents will hide the overlay and block the input.
+        /// Performing mouse actions outside of the valid extents will hide the overlay.
         /// </summary>
         public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;