From e9ec544bf65b929450c4bcd18a4ad63fe14207cb Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 3 Apr 2018 02:04:40 +0900
Subject: [PATCH 001/177] Implement joystick keybindings

---
 osu-framework                                 |  2 +-
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 20 +++++++++++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 85b3494117..3388ae24ba 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 85b3494117ccef1b396b70957e1cffaba06e2b54
+Subproject commit 3388ae24ba5cc75fc966ab1531f44701e74588a9
diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 379d25313e..81f24d2661 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -223,6 +223,26 @@ namespace osu.Game.Overlays.KeyBinding
             return true;
         }
 
+        protected override bool OnJoystickPress(InputState state, JoystickPressEventArgs args)
+        {
+            if (!HasFocus)
+                return false;
+
+            bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
+            finalise();
+
+            return true;
+        }
+
+        protected override bool OnJoystickRelease(InputState state, JoystickReleaseEventArgs args)
+        {
+            if (!HasFocus)
+                return base.OnJoystickRelease(state, args);
+
+            finalise();
+            return true;
+        }
+
         private void finalise()
         {
             if (bindTarget != null)

From c39fb9a200712423e0656ba2ac2170748819ec4b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 3 Apr 2018 20:10:26 +0900
Subject: [PATCH 002/177] Update with framework changes

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 81f24d2661..9cf43c2bde 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -223,7 +223,7 @@ namespace osu.Game.Overlays.KeyBinding
             return true;
         }
 
-        protected override bool OnJoystickPress(InputState state, JoystickPressEventArgs args)
+        protected override bool OnJoystickPress(InputState state, Framework.Input.JoystickEventArgs args)
         {
             if (!HasFocus)
                 return false;
@@ -234,7 +234,7 @@ namespace osu.Game.Overlays.KeyBinding
             return true;
         }
 
-        protected override bool OnJoystickRelease(InputState state, JoystickReleaseEventArgs args)
+        protected override bool OnJoystickRelease(InputState state, Framework.Input.JoystickEventArgs args)
         {
             if (!HasFocus)
                 return base.OnJoystickRelease(state, args);

From dbee936748439445d049ed3ddf6a9d164d2d7429 Mon Sep 17 00:00:00 2001
From: aQaTL <mmsoltys@outlook.com>
Date: Mon, 9 Apr 2018 18:48:47 +0200
Subject: [PATCH 003/177] Allow mapping delete key via alt+delete key
 combination

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 71c346d404..88005795ff 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -198,18 +198,29 @@ namespace osu.Game.Overlays.KeyBinding
             if (!HasFocus)
                 return false;
 
+            KeyCombination keyCombination = KeyCombination.FromInputState(state);
+
             switch (args.Key)
             {
                 case Key.Escape:
                     finalise();
                     return true;
                 case Key.Delete:
-                    bindTarget.UpdateKeyCombination(InputKey.None);
-                    finalise();
-                    return true;
+                    if (keyCombination.Equals(InputKey.Delete))
+                    {
+                        bindTarget.UpdateKeyCombination(InputKey.None);
+                        finalise();
+                        return true;
+                    }
+                    else if (keyCombination.Equals(new[] { InputKey.Alt, InputKey.Delete }))
+                    {
+                        keyCombination = InputKey.Delete;
+                    }
+
+                    break;
             }
 
-            bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
+            bindTarget.UpdateKeyCombination(keyCombination);
             if (!isModifier(args.Key)) finalise();
 
             return true;

From 38277bff35f5eb945958ecd485be489374dad3fc Mon Sep 17 00:00:00 2001
From: aQaTL <mmsoltys@outlook.com>
Date: Tue, 10 Apr 2018 18:00:22 +0200
Subject: [PATCH 004/177] Changed mapping of deleting key binding to
 shift+delete

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 25 +++++++------------
 1 file changed, 9 insertions(+), 16 deletions(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 88005795ff..63b5e8a7c1 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -200,24 +200,17 @@ namespace osu.Game.Overlays.KeyBinding
 
             KeyCombination keyCombination = KeyCombination.FromInputState(state);
 
-            switch (args.Key)
+            if (keyCombination.Equals(InputKey.Escape))
             {
-                case Key.Escape:
-                    finalise();
-                    return true;
-                case Key.Delete:
-                    if (keyCombination.Equals(InputKey.Delete))
-                    {
-                        bindTarget.UpdateKeyCombination(InputKey.None);
-                        finalise();
-                        return true;
-                    }
-                    else if (keyCombination.Equals(new[] { InputKey.Alt, InputKey.Delete }))
-                    {
-                        keyCombination = InputKey.Delete;
-                    }
+                finalise();
+                return true;
+            }
 
-                    break;
+            if (keyCombination.Equals(new[] { InputKey.Shift, InputKey.Delete }))
+            {
+                bindTarget.UpdateKeyCombination(InputKey.None);
+                finalise();
+                return true;
             }
 
             bindTarget.UpdateKeyCombination(keyCombination);

From 90beff83f6cabb47a26b4fb031322f9a3ccc2b0d Mon Sep 17 00:00:00 2001
From: aQaTL <mmsoltys@outlook.com>
Date: Wed, 11 Apr 2018 08:07:26 +0200
Subject: [PATCH 005/177] Updated KeyBindingRow sprite text, adjusted
 KeyBindingOverlay width

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 2 +-
 osu.Game/Overlays/KeyBindingOverlay.cs        | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 63b5e8a7c1..6926e3d966 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Overlays.KeyBinding
                 },
                 pressAKey = new OsuSpriteText
                 {
-                    Text = "Press a key to change binding, DEL to delete, ESC to cancel.",
+                    Text = "Press a key to change binding, SHIFT+DEL to delete, ESC to cancel.",
                     Y = height,
                     Margin = new MarginPadding(padding),
                     Alpha = 0,
diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs
index b311ee68c0..3f393851c2 100644
--- a/osu.Game/Overlays/KeyBindingOverlay.cs
+++ b/osu.Game/Overlays/KeyBindingOverlay.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Overlays
 {
     public class KeyBindingOverlay : SettingsOverlay
     {
+        protected const float WIDTH = 430;
+
         protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
 
         [BackgroundDependencyLoader(permitNulls: true)]
@@ -21,6 +23,8 @@ namespace osu.Game.Overlays
 
             foreach (var ruleset in rulesets.AvailableRulesets)
                 AddSection(new RulesetBindingsSection(ruleset));
+
+            ContentContainer.Width = WIDTH;
         }
 
         public KeyBindingOverlay()

From 1dc8986c22cec2011005cc77f5f6a0d5fa691f55 Mon Sep 17 00:00:00 2001
From: aQaTL <mmsoltys@outlook.com>
Date: Wed, 18 Apr 2018 15:06:03 +0200
Subject: [PATCH 006/177] Switched back to switch

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 29 ++++++++++---------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 6926e3d966..4e06ce8211 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -198,22 +198,25 @@ namespace osu.Game.Overlays.KeyBinding
             if (!HasFocus)
                 return false;
 
-            KeyCombination keyCombination = KeyCombination.FromInputState(state);
-
-            if (keyCombination.Equals(InputKey.Escape))
+            switch (args.Key)
             {
-                finalise();
-                return true;
+                case Key.Escape:
+                    finalise();
+                    return true;
+                case Key.Delete:
+                {
+                    if (state.Keyboard.ShiftPressed)
+                    {
+                        bindTarget.UpdateKeyCombination(InputKey.None);
+                        finalise();
+                        return true;
+                    }
+
+                    break;
+                }
             }
 
-            if (keyCombination.Equals(new[] { InputKey.Shift, InputKey.Delete }))
-            {
-                bindTarget.UpdateKeyCombination(InputKey.None);
-                finalise();
-                return true;
-            }
-
-            bindTarget.UpdateKeyCombination(keyCombination);
+            bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
             if (!isModifier(args.Key)) finalise();
 
             return true;

From b5a55a0dcea31751a0fc7ceebc96f53bad87e426 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 18:14:21 +0900
Subject: [PATCH 007/177] Make an interface for beatmaps

---
 osu.Game/Beatmaps/Beatmap.cs              | 118 +++++++++++++---------
 osu.Game/Beatmaps/BeatmapConverter.cs     |   2 +-
 osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs |   9 --
 osu.Game/Screens/Play/BreakOverlay.cs     |   4 +-
 osu.Game/Tests/Beatmaps/TestBeatmap.cs    |   8 +-
 5 files changed, 79 insertions(+), 62 deletions(-)

diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 12a017f68c..541f7f85b6 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -12,24 +12,69 @@ using osu.Game.IO.Serialization.Converters;
 
 namespace osu.Game.Beatmaps
 {
+    public interface IBeatmap : IJsonSerializable
+    {
+        /// <summary>
+        /// This beatmap's info.
+        /// </summary>
+        BeatmapInfo BeatmapInfo { get; }
+
+        /// <summary>
+        /// This beatmap's metadata.
+        /// </summary>
+        BeatmapMetadata Metadata { get; }
+
+        /// <summary>
+        /// The control points in this beatmap.
+        /// </summary>
+        ControlPointInfo ControlPointInfo { get; }
+
+        /// <summary>
+        /// The breaks in this beatmap.
+        /// </summary>
+        List<BreakPeriod> Breaks { get; }
+
+        /// <summary>
+        /// Total amount of break time in the beatmap.
+        /// </summary>
+        double TotalBreakTime { get; }
+
+        /// <summary>
+        /// The hitobjects contained by this beatmap.
+        /// </summary>
+        IEnumerable<HitObject> HitObjects { get; }
+
+        /// <summary>
+        /// Creates a shallow-clone of this beatmap and returns it.
+        /// </summary>
+        /// <returns>The shallow-cloned beatmap.</returns>
+        IBeatmap Clone();
+    }
+
     /// <summary>
     /// A Beatmap containing converted HitObjects.
     /// </summary>
-    public class Beatmap<T> : IJsonSerializable
+    public class Beatmap<T> : IBeatmap
         where T : HitObject
     {
-        public BeatmapInfo BeatmapInfo = new BeatmapInfo();
-        public ControlPointInfo ControlPointInfo = new ControlPointInfo();
-        public List<BreakPeriod> Breaks = new List<BreakPeriod>();
+        public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo
+        {
+            Metadata = new BeatmapMetadata
+            {
+                Artist = @"Unknown",
+                Title = @"Unknown",
+                AuthorString = @"Unknown Creator",
+            },
+            Version = @"Normal",
+            BaseDifficulty = new BeatmapDifficulty()
+        };
 
         [JsonIgnore]
         public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
 
-        /// <summary>
-        /// The HitObjects this Beatmap contains.
-        /// </summary>
-        [JsonConverter(typeof(TypedListConverter<HitObject>))]
-        public List<T> HitObjects = new List<T>();
+        public ControlPointInfo ControlPointInfo { get; set; } = new ControlPointInfo();
+
+        public List<BreakPeriod> Breaks { get; set; } = new List<BreakPeriod>();
 
         /// <summary>
         /// Total amount of break time in the beatmap.
@@ -38,51 +83,26 @@ namespace osu.Game.Beatmaps
         public double TotalBreakTime => Breaks.Sum(b => b.Duration);
 
         /// <summary>
-        /// Constructs a new beatmap.
+        /// The HitObjects this Beatmap contains.
         /// </summary>
-        /// <param name="original">The original beatmap to use the parameters of.</param>
-        public Beatmap(Beatmap<T> original = null)
-        {
-            BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
-            ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
-            Breaks = original?.Breaks ?? Breaks;
-            HitObjects = original?.HitObjects ?? HitObjects;
+        [JsonConverter(typeof(TypedListConverter<HitObject>))]
+        public List<T> HitObjects = new List<T>();
 
-            if (original == null && Metadata == null)
-            {
-                // we may have no metadata in cases we weren't sourced from the database.
-                // let's fill it (and other related fields) so we don't need to null-check it in future usages.
-                BeatmapInfo = new BeatmapInfo
-                {
-                    Metadata = new BeatmapMetadata
-                    {
-                        Artist = @"Unknown",
-                        Title = @"Unknown",
-                        AuthorString = @"Unknown Creator",
-                    },
-                    Version = @"Normal",
-                    BaseDifficulty = new BeatmapDifficulty()
-                };
-            }
-        }
+        IEnumerable<HitObject> IBeatmap.HitObjects => HitObjects;
+
+        public Beatmap<T> Clone() => new Beatmap<T>
+        {
+            BeatmapInfo = BeatmapInfo.DeepClone(),
+            ControlPointInfo = ControlPointInfo,
+            Breaks = Breaks,
+            HitObjects = HitObjects
+        };
+
+        IBeatmap IBeatmap.Clone() => Clone();
     }
 
-    /// <summary>
-    /// A Beatmap containing un-converted HitObjects.
-    /// </summary>
     public class Beatmap : Beatmap<HitObject>
     {
-        /// <summary>
-        /// Constructs a new beatmap.
-        /// </summary>
-        /// <param name="original">The original beatmap to use the parameters of.</param>
-        public Beatmap(Beatmap original)
-            : base(original)
-        {
-        }
-
-        public Beatmap()
-        {
-        }
+        public new Beatmap Clone() => (Beatmap)base.Clone();
     }
 }
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 153cace187..f46e6abc87 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps
         public Beatmap<T> Convert(Beatmap original)
         {
             // We always operate on a clone of the original beatmap, to not modify it game-wide
-            return ConvertBeatmap(new Beatmap(original));
+            return ConvertBeatmap(original.Clone());
         }
 
         void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
diff --git a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
index eea82dac6d..4daa014804 100644
--- a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
+++ b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
@@ -8,14 +8,5 @@ namespace osu.Game.Beatmaps.Legacy
     /// </summary>
     public class LegacyBeatmap : Beatmap
     {
-        /// <summary>
-        /// Constructs a new beatmap.
-        /// </summary>
-        /// <param name="original">The original beatmap to use the parameters of.</param>
-        internal LegacyBeatmap(Beatmap original = null)
-            : base(original)
-        {
-            HitObjects = original?.HitObjects;
-        }
     }
 }
diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index b2df996d35..ca252dd6fd 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -18,11 +18,11 @@ namespace osu.Game.Screens.Play
         private const float remaining_time_container_max_size = 0.3f;
         private const int vertical_margin = 25;
 
-        private List<BreakPeriod> breaks;
+        private IReadOnlyList<BreakPeriod> breaks;
 
         private readonly Container fadeContainer;
 
-        public List<BreakPeriod> Breaks
+        public IReadOnlyList<BreakPeriod> Breaks
         {
             get => breaks;
             set
diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
index 09a3a7af8c..6bad08baaa 100644
--- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs
@@ -12,8 +12,14 @@ namespace osu.Game.Tests.Beatmaps
     public class TestBeatmap : Beatmap
     {
         public TestBeatmap(RulesetInfo ruleset)
-        : base(createTestBeatmap())
         {
+            var baseBeatmap = createTestBeatmap();
+
+            BeatmapInfo = baseBeatmap.BeatmapInfo;
+            ControlPointInfo = baseBeatmap.ControlPointInfo;
+            Breaks = baseBeatmap.Breaks;
+            HitObjects = baseBeatmap.HitObjects;
+
             BeatmapInfo.Ruleset = ruleset;
         }
 

From ac64f9d958be5c2630dbf799b704df1f3d5e56eb Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 18:40:01 +0900
Subject: [PATCH 008/177] Remove LegacyBeatmap

---
 osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs | 12 ------------
 1 file changed, 12 deletions(-)
 delete mode 100644 osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs

diff --git a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs b/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
deleted file mode 100644
index 4daa014804..0000000000
--- a/osu.Game/Beatmaps/Legacy/LegacyBeatmap.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-namespace osu.Game.Beatmaps.Legacy
-{
-    /// <summary>
-    /// A type of Beatmap loaded from a legacy .osu beatmap file (version &lt;=15).
-    /// </summary>
-    public class LegacyBeatmap : Beatmap
-    {
-    }
-}

From e666a82e1f01a6b6fc891b7c8b52660752d67cf8 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 18:50:21 +0900
Subject: [PATCH 009/177] Fix cloning

---
 osu.Game/Beatmaps/Beatmap.cs | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 541f7f85b6..7066432c5c 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -90,19 +90,19 @@ namespace osu.Game.Beatmaps
 
         IEnumerable<HitObject> IBeatmap.HitObjects => HitObjects;
 
-        public Beatmap<T> Clone() => new Beatmap<T>
-        {
-            BeatmapInfo = BeatmapInfo.DeepClone(),
-            ControlPointInfo = ControlPointInfo,
-            Breaks = Breaks,
-            HitObjects = HitObjects
-        };
-
         IBeatmap IBeatmap.Clone() => Clone();
+
+        public Beatmap<T> Clone()
+        {
+            var newInstance = (Beatmap<T>)MemberwiseClone();
+            newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
+
+            return newInstance;
+        }
     }
 
     public class Beatmap : Beatmap<HitObject>
     {
-        public new Beatmap Clone() => (Beatmap)base.Clone();
+        public Beatmap Clone() => (Beatmap)base.Clone();
     }
 }

From 7a550e3f071f40ceded70e0dcebad8d99670ddac Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 20:20:56 +0900
Subject: [PATCH 010/177] Revert unnecessary change for now

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

diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs
index ca252dd6fd..b2df996d35 100644
--- a/osu.Game/Screens/Play/BreakOverlay.cs
+++ b/osu.Game/Screens/Play/BreakOverlay.cs
@@ -18,11 +18,11 @@ namespace osu.Game.Screens.Play
         private const float remaining_time_container_max_size = 0.3f;
         private const int vertical_margin = 25;
 
-        private IReadOnlyList<BreakPeriod> breaks;
+        private List<BreakPeriod> breaks;
 
         private readonly Container fadeContainer;
 
-        public IReadOnlyList<BreakPeriod> Breaks
+        public List<BreakPeriod> Breaks
         {
             get => breaks;
             set

From 66b3b295e74860fe60a105decfc742732bf87256 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 20:44:38 +0900
Subject: [PATCH 011/177] Use IBeatmap wherever possible

---
 .../CatchBeatmapConversionTest.cs                  |  2 +-
 .../TestCaseAutoJuiceStream.cs                     |  2 +-
 .../TestCaseBananaShower.cs                        |  2 +-
 .../TestCaseCatchStacker.cs                        |  2 +-
 osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs |  2 +-
 .../Beatmaps/CatchBeatmapConverter.cs              |  2 +-
 .../CatchDifficultyCalculator.cs                   |  4 ++--
 osu.Game.Rulesets.Catch/CatchRuleset.cs            |  2 +-
 .../Replays/CatchReplayFrame.cs                    |  2 +-
 .../ManiaBeatmapConversionTest.cs                  |  2 +-
 .../Beatmaps/ManiaBeatmapConverter.cs              | 14 +++++++-------
 .../Legacy/DistanceObjectPatternGenerator.cs       |  2 +-
 .../Legacy/EndTimeObjectPatternGenerator.cs        |  2 +-
 .../Patterns/Legacy/HitObjectPatternGenerator.cs   |  2 +-
 .../Beatmaps/Patterns/Legacy/PatternGenerator.cs   |  6 +++---
 .../ManiaDifficultyCalculator.cs                   |  6 +++---
 osu.Game.Rulesets.Mania/ManiaRuleset.cs            |  2 +-
 .../Replays/ManiaReplayFrame.cs                    |  2 +-
 .../OsuBeatmapConversionTest.cs                    |  2 +-
 .../Beatmaps/OsuBeatmapConverter.cs                |  2 +-
 .../OsuDifficulty/OsuDifficultyCalculator.cs       |  6 +++---
 osu.Game.Rulesets.Osu/OsuRuleset.cs                |  4 ++--
 osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs    |  2 +-
 .../Scoring/OsuPerformanceCalculator.cs            |  2 +-
 .../TaikoBeatmapConversionTest.cs                  |  2 +-
 .../Beatmaps/TaikoBeatmapConverter.cs              |  4 ++--
 .../Replays/TaikoReplayFrame.cs                    |  2 +-
 .../TaikoDifficultyCalculator.cs                   |  4 ++--
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs            |  2 +-
 osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs    |  8 ++++----
 osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs  |  4 ++--
 .../Visual/TestCaseEditorSeekSnapping.cs           |  2 +-
 osu.Game/Beatmaps/Beatmap.cs                       |  2 +-
 osu.Game/Beatmaps/BeatmapConverter.cs              | 12 ++++++------
 osu.Game/Beatmaps/BeatmapManager.cs                |  2 +-
 osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs |  2 +-
 osu.Game/Beatmaps/DifficultyCalculator.cs          |  4 ++--
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs           |  4 ++--
 osu.Game/Beatmaps/IBeatmapConverter.cs             |  2 +-
 osu.Game/Beatmaps/WorkingBeatmap.cs                | 12 ++++++------
 .../Replays/Types/IConvertibleReplayFrame.cs       |  2 +-
 osu.Game/Rulesets/Ruleset.cs                       |  4 ++--
 .../Rulesets/Scoring/Legacy/LegacyScoreParser.cs   |  2 +-
 osu.Game/Rulesets/Scoring/PerformanceCalculator.cs |  2 +-
 osu.Game/Screens/Play/Player.cs                    |  2 +-
 osu.Game/Screens/Select/BeatmapInfoWedge.cs        |  6 +++---
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs   |  4 ++--
 osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs      |  6 +++---
 osu.Game/Tests/Visual/TestCasePerformancePoints.cs |  4 ++--
 osu.Game/Tests/Visual/TestCasePlayer.cs            |  2 +-
 50 files changed, 89 insertions(+), 89 deletions(-)

diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index bd0cc209b6..bf373867e8 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.Tests
             }
         }
 
-        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter();
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
index bce20520d3..097750d7e0 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Tests
         {
         }
 
-        protected override Beatmap CreateBeatmap(Ruleset ruleset)
+        protected override IBeatmap CreateBeatmap(Ruleset ruleset)
         {
             var beatmap = new Beatmap
             {
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
index d13a6bb860..b5cf0e3d1d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
         {
         }
 
-        protected override Beatmap CreateBeatmap(Ruleset ruleset)
+        protected override IBeatmap CreateBeatmap(Ruleset ruleset)
         {
             var beatmap = new Beatmap
             {
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
index 2b58fcc93c..8a90b48180 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests
         {
         }
 
-        protected override Beatmap CreateBeatmap(Ruleset ruleset)
+        protected override IBeatmap CreateBeatmap(Ruleset ruleset)
         {
             var beatmap = new Beatmap
             {
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
index e7f936ca2a..896582bf0a 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperdash.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Tests
         {
         }
 
-        protected override Beatmap CreateBeatmap(Ruleset ruleset)
+        protected override IBeatmap CreateBeatmap(Ruleset ruleset)
         {
             var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
 
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 34e5f425fd..b1b101e797 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
     {
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
 
-        protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap)
+        protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
         {
             var curveData = obj as IHasCurve;
             var positionData = obj as IHasXPosition;
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
index 876b394da0..626a3d186a 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
@@ -10,12 +10,12 @@ namespace osu.Game.Rulesets.Catch
 {
     public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject>
     {
-        public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
+        public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
         {
         }
 
         public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
 
-        protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+        protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter();
     }
 }
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index cfe0fc5cec..e2cb7b1d9b 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch
 
         public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
 
-        public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
+        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
 
         public override int? LegacyID => 2;
 
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index d63d1bd331..d5c5eb844a 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Replays
             Dashing = dashing;
         }
 
-        public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+        public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
         {
             Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
             Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 81c537e53c..fb1fce4279 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 60b92cb7b3..a142db8fed 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         private ManiaBeatmap beatmap;
 
-        public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original)
+        public ManiaBeatmapConverter(bool isForCurrentRuleset, IBeatmap original)
         {
             IsForCurrentRuleset = isForCurrentRuleset;
 
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 TargetColumns = (int)Math.Max(1, roundedCircleSize);
             else
             {
-                float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count;
+                float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count();
                 if (percentSliderOrSpinner < 0.2)
                     TargetColumns = 7;
                 else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             }
         }
 
-        protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original)
+        protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
         {
             BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
 
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         protected override Beatmap<ManiaHitObject> CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
 
-        protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
+        protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
         {
             var maniaOriginal = original as ManiaHitObject;
             if (maniaOriginal != null)
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
         /// <param name="original">The original hit object.</param>
         /// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
         /// <returns>The hit objects generated.</returns>
-        private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, Beatmap originalBeatmap)
+        private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, IBeatmap originalBeatmap)
         {
             var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
 
@@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
         /// <param name="original">The original hit object.</param>
         /// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
         /// <returns>The hit objects generated.</returns>
-        private IEnumerable<ManiaHitObject> generateConverted(HitObject original, Beatmap originalBeatmap)
+        private IEnumerable<ManiaHitObject> generateConverted(HitObject original, IBeatmap originalBeatmap)
         {
             var endTimeData = original as IHasEndTime;
             var distanceData = original as IHasDistance;
@@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
         /// </summary>
         private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
         {
-            public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+            public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
                 : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
             {
             }
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 3b5c028bfd..afa9bdbbd7 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
         private PatternType convertType;
 
-        public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+        public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
             : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
         {
             convertType = PatternType.None;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 743e230cb2..3f34afee85 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
     {
         private readonly double endTime;
 
-        public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap)
+        public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap)
             : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
         {
             var endtimeData = HitObject as IHasEndTime;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index 652c92dd78..cec3e18ad6 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
 
         private readonly PatternType convertType;
 
-        public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap)
+        public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, IBeatmap originalBeatmap)
             : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
         {
             if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 02306846a3..930597c1ad 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
         /// <summary>
         /// The beatmap which <see cref="HitObject"/> is being converted from.
         /// </summary>
-        protected readonly Beatmap OriginalBeatmap;
+        protected readonly IBeatmap OriginalBeatmap;
 
-        protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+        protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap)
             : base(hitObject, beatmap, previousPattern)
         {
             if (random == null) throw new ArgumentNullException(nameof(random));
@@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
                 drainTime /= 1000;
 
                 BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
-                conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
+                conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count() / drainTime * 9f) / 38f * 5f / 1.15;
                 conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
 
                 return conversionDifficulty.Value;
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 5eea346836..24fe72faec 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -31,12 +31,12 @@ namespace osu.Game.Rulesets.Mania
         /// </summary>
         private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
 
-        public ManiaDifficultyCalculator(Beatmap beatmap)
+        public ManiaDifficultyCalculator(IBeatmap beatmap)
             : base(beatmap)
         {
         }
 
-        public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods)
+        public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
             : base(beatmap, mods)
         {
         }
@@ -141,6 +141,6 @@ namespace osu.Game.Rulesets.Mania
             return difficulty;
         }
 
-        protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
+        protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
     }
 }
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 0546cbc174..d5bf1b30fc 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Mania
 
         public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
 
-        public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
+        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
 
         public override int? LegacyID => 3;
 
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 8d86325dd9..926a3a8923 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Replays
             Actions.AddRange(actions);
         }
 
-        public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+        public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
         {
             // We don't need to fully convert, just create the converter
             var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap);
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 6ac3c016a0..a77b20e83b 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter();
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter();
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 1236076f48..b1a52c5469 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
     {
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
 
-        protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
+        protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
         {
             var curveData = original as IHasCurve;
             var endTimeData = original as IHasEndTime;
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
index 926a7975f3..197bc16fc1 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
@@ -17,12 +17,12 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
         private const int section_length = 400;
         private const double difficulty_multiplier = 0.0675;
 
-        public OsuDifficultyCalculator(Beatmap beatmap)
+        public OsuDifficultyCalculator(IBeatmap beatmap)
             : base(beatmap)
         {
         }
 
-        public OsuDifficultyCalculator(Beatmap beatmap, Mod[] mods)
+        public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
             : base(beatmap, mods)
         {
         }
@@ -73,6 +73,6 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
             return starRating;
         }
 
-        protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter(Beatmap beatmap) => new OsuBeatmapConverter();
+        protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter();
     }
 }
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index e0ecee97a3..ed750882f4 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -181,9 +181,9 @@ namespace osu.Game.Rulesets.Osu
 
         public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
 
-        public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
+        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
 
-        public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
+        public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
 
         public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
 
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
index 6f2512cc33..4412b6efab 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Replays
             Actions.AddRange(actions);
         }
 
-        public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+        public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
         {
             Position = legacyFrame.Position;
             if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 8f0feca207..c8806cbdb1 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
         private int count50;
         private int countMiss;
 
-        public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+        public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
             : base(ruleset, beatmap, score)
         {
             countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index aa61f2d60b..3d236af8ad 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 2f175a9922..eabe7eb91a 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
             this.isForCurrentRuleset = isForCurrentRuleset;
         }
 
-        protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original)
+        protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
         {
             // Rewrite the beatmap info to add the slider velocity multiplier
             BeatmapInfo info = original.BeatmapInfo.DeepClone();
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
             return converted;
         }
 
-        protected override IEnumerable<TaikoHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap)
+        protected override IEnumerable<TaikoHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
         {
             var distanceData = obj as IHasDistance;
             var repeatsData = obj as IHasRepeats;
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
index e510b34ad7..2177a3cbdc 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
             Actions.AddRange(actions);
         }
 
-        public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+        public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
         {
             if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
             if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
index 58661d7881..66b8459d1f 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko
         /// </summary>
         private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
 
-        public TaikoDifficultyCalculator(Beatmap beatmap)
+        public TaikoDifficultyCalculator(IBeatmap beatmap)
             : base(beatmap)
         {
         }
@@ -133,6 +133,6 @@ namespace osu.Game.Rulesets.Taiko
             return difficulty;
         }
 
-        protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter(Beatmap beatmap) => new TaikoBeatmapConverter(true);
+        protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(true);
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 06a8e44a14..e5f3b33355 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Taiko
 
         public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
 
-        public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
+        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
 
         public override int? LegacyID => 1;
 
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 6453cdbd3e..f60caf2397 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -275,13 +275,13 @@ namespace osu.Game.Tests.Beatmaps.IO
                 Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
             Assert.IsTrue(set.Beatmaps.Count > 0);
             var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
-            Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+            Assert.IsTrue(beatmap?.HitObjects.Any() == true);
             beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
-            Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+            Assert.IsTrue(beatmap?.HitObjects.Any() == true);
             beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
-            Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+            Assert.IsTrue(beatmap?.HitObjects.Any() == true);
             beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
-            Assert.IsTrue(beatmap?.HitObjects.Count > 0);
+            Assert.IsTrue(beatmap?.HitObjects.Any() == true);
         }
 
         private void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index 790c4cedc3..0c5c5dba22 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual
     {
         private RulesetStore rulesets;
         private TestBeatmapInfoWedge infoWedge;
-        private readonly List<Beatmap> beatmaps = new List<Beatmap>();
+        private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
         private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
 
         [BackgroundDependencyLoader]
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual
             });
         }
 
-        private Beatmap createTestBeatmap(RulesetInfo ruleset)
+        private IBeatmap createTestBeatmap(RulesetInfo ruleset)
         {
             List<HitObject> objects = new List<HitObject>();
             for (double i = 0; i < 50000; i += 1000)
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
index 582ab5ecc9..f037d70493 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
@@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual
 
             private readonly Drawable tracker;
 
-            public TimingPointVisualiser(Beatmap beatmap, double length)
+            public TimingPointVisualiser(IBeatmap beatmap, double length)
             {
                 this.length = length;
 
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 7066432c5c..6a7d2690ff 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps
         /// <summary>
         /// This beatmap's info.
         /// </summary>
-        BeatmapInfo BeatmapInfo { get; }
+        BeatmapInfo BeatmapInfo { get; set; }
 
         /// <summary>
         /// This beatmap's metadata.
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index f46e6abc87..fab7860077 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -27,27 +27,27 @@ namespace osu.Game.Beatmaps
         /// </summary>
         /// <param name="beatmap">The Beatmap to check.</param>
         /// <returns>Whether the Beatmap can be converted using this Beatmap Converter.</returns>
-        public bool CanConvert(Beatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType));
+        public bool CanConvert(IBeatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType));
 
         /// <summary>
         /// Converts a Beatmap using this Beatmap Converter.
         /// </summary>
         /// <param name="original">The un-converted Beatmap.</param>
         /// <returns>The converted Beatmap.</returns>
-        public Beatmap<T> Convert(Beatmap original)
+        public Beatmap<T> Convert(IBeatmap original)
         {
             // We always operate on a clone of the original beatmap, to not modify it game-wide
             return ConvertBeatmap(original.Clone());
         }
 
-        void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
+        void IBeatmapConverter.Convert(IBeatmap original) => Convert(original);
 
         /// <summary>
         /// Performs the conversion of a Beatmap using this Beatmap Converter.
         /// </summary>
         /// <param name="original">The un-converted Beatmap.</param>
         /// <returns>The converted Beatmap.</returns>
-        protected virtual Beatmap<T> ConvertBeatmap(Beatmap original)
+        protected virtual Beatmap<T> ConvertBeatmap(IBeatmap original)
         {
             var beatmap = CreateBeatmap();
 
@@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps
         /// <param name="original">The hit object to convert.</param>
         /// <param name="beatmap">The un-converted Beatmap.</param>
         /// <returns>The converted hit object.</returns>
-        private IEnumerable<T> convert(HitObject original, Beatmap beatmap)
+        private IEnumerable<T> convert(HitObject original, IBeatmap beatmap)
         {
             // Check if the hitobject is already the converted type
             T tObject = original as T;
@@ -107,6 +107,6 @@ namespace osu.Game.Beatmaps
         /// <param name="original">The hit object to convert.</param>
         /// <param name="beatmap">The un-converted Beatmap.</param>
         /// <returns>The converted hit object.</returns>
-        protected abstract IEnumerable<T> ConvertHitObject(HitObject original, Beatmap beatmap);
+        protected abstract IEnumerable<T> ConvertHitObject(HitObject original, IBeatmap beatmap);
     }
 }
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 645e52a6c6..14436fce13 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -333,7 +333,7 @@ namespace osu.Game.Beatmaps
                     ms.Position = 0;
 
                     var decoder = Decoder.GetDecoder<Beatmap>(sr);
-                    Beatmap beatmap = decoder.Decode(sr);
+                    IBeatmap beatmap = decoder.Decode(sr);
 
                     beatmap.BeatmapInfo.Path = name;
                     beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 8e09d66c42..71406c6034 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
                 this.audioManager = audioManager;
             }
 
-            protected override Beatmap GetBeatmap()
+            protected override IBeatmap GetBeatmap()
             {
                 try
                 {
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index 5e2d9afd23..bf252ff51f 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Beatmaps
         protected readonly Beatmap<T> Beatmap;
         protected readonly Mod[] Mods;
 
-        protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null)
+        protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
         {
             Mods = mods ?? new Mod[0];
 
@@ -59,6 +59,6 @@ namespace osu.Game.Beatmaps
         {
         }
 
-        protected abstract BeatmapConverter<T> CreateBeatmapConverter(Beatmap beatmap);
+        protected abstract BeatmapConverter<T> CreateBeatmapConverter(IBeatmap beatmap);
     }
 }
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 0424ff84f1..0d325284e1 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Beatmaps
             this.game = game;
         }
 
-        protected override Beatmap GetBeatmap() => new Beatmap();
+        protected override IBeatmap GetBeatmap() => new Beatmap();
 
         protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
 
@@ -58,7 +58,7 @@ namespace osu.Game.Beatmaps
                     throw new NotImplementedException();
                 }
 
-                public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => null;
+                public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null;
 
                 public override string Description => "dummy";
 
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index 6c25395a56..4df250ad17 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -20,6 +20,6 @@ namespace osu.Game.Beatmaps
         /// Converts a Beatmap using this Beatmap Converter.
         /// </summary>
         /// <param name="beatmap">The un-converted Beatmap.</param>
-        void Convert(Beatmap beatmap);
+        void Convert(IBeatmap beatmap);
     }
 }
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 4080e34e81..fc67d2e508 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps
 
             Mods.ValueChanged += mods => applyRateAdjustments();
 
-            beatmap = new AsyncLazy<Beatmap>(populateBeatmap);
+            beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
             background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
             track = new AsyncLazy<Track>(populateTrack);
             waveform = new AsyncLazy<Waveform>(populateWaveform);
@@ -55,7 +55,7 @@ namespace osu.Game.Beatmaps
             Process.Start(path);
         }
 
-        protected abstract Beatmap GetBeatmap();
+        protected abstract IBeatmap GetBeatmap();
         protected abstract Texture GetBackground();
         protected abstract Track GetTrack();
         protected virtual Skin GetSkin() => new DefaultSkin();
@@ -63,12 +63,12 @@ namespace osu.Game.Beatmaps
         protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
 
         public bool BeatmapLoaded => beatmap.IsResultAvailable;
-        public Beatmap Beatmap => beatmap.Value.Result;
-        public async Task<Beatmap> GetBeatmapAsync() => await beatmap.Value;
+        public IBeatmap Beatmap => beatmap.Value.Result;
+        public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
 
-        private readonly AsyncLazy<Beatmap> beatmap;
+        private readonly AsyncLazy<IBeatmap> beatmap;
 
-        private Beatmap populateBeatmap()
+        private IBeatmap populateBeatmap()
         {
             var b = GetBeatmap() ?? new Beatmap();
 
diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
index adf35ce078..fdd528f296 100644
--- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
+++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Replays.Types
         /// </summary>
         /// <param name="legacyFrame">The <see cref="LegacyReplayFrame"/> to extract values from.</param>
         /// <param name="beatmap">The beatmap.</param>
-        void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap);
+        void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap);
     }
 }
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index cd1d030afe..3f8512eb90 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -57,9 +57,9 @@ namespace osu.Game.Rulesets
         /// <returns></returns>
         public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
 
-        public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
+        public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null);
 
-        public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
+        public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null;
 
         public virtual HitObjectComposer CreateHitObjectComposer() => null;
 
diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index 239f200e29..d5ab856697 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy
             this.beatmaps = beatmaps;
         }
 
-        private Beatmap currentBeatmap;
+        private IBeatmap currentBeatmap;
         private Ruleset currentRuleset;
 
         public Score Parse(Stream stream)
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
index 0f115fff6b..6392d2c0ae 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Scoring
         protected readonly Beatmap<TObject> Beatmap;
         protected readonly Score Score;
 
-        protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
+        protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
         {
             Score = score;
 
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index ec7c1a1009..32685935a1 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Screens.Play
             mouseWheelDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableWheel);
             userAudioOffset = config.GetBindable<double>(OsuSetting.AudioOffset);
 
-            Beatmap beatmap;
+            IBeatmap beatmap;
 
             try
             {
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index f005261ffa..da82a49f51 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -215,7 +215,7 @@ namespace osu.Game.Screens.Select
 
                 List<InfoLabel> labels = new List<InfoLabel>();
 
-                if (beatmap?.HitObjects?.Count > 0)
+                if (beatmap?.HitObjects?.Any() == true)
                 {
                     HitObject lastObject = beatmap.HitObjects.LastOrDefault();
                     double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
@@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select
                     {
                         Name = "Length",
                         Icon = FontAwesome.fa_clock_o,
-                        Content = beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
+                        Content = TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
                     }));
 
                     labels.Add(new InfoLabel(new BeatmapStatistic
@@ -241,7 +241,7 @@ namespace osu.Game.Screens.Select
                 return labels.ToArray();
             }
 
-            private string getBPMRange(Beatmap beatmap)
+            private string getBPMRange(IBeatmap beatmap)
             {
                 double bpmMax = beatmap.ControlPointInfo.BPMMaximum;
                 double bpmMin = beatmap.ControlPointInfo.BPMMinimum;
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 2850de8ba5..d02ccaff16 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Tests.Beatmaps
             }
         }
 
-        private Beatmap getBeatmap(string name)
+        private IBeatmap getBeatmap(string name)
         {
             using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
             using (var stream = new StreamReader(resStream))
@@ -125,7 +125,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
-        protected abstract IBeatmapConverter CreateConverter(Beatmap beatmap);
+        protected abstract IBeatmapConverter CreateConverter(IBeatmap beatmap);
 
         private class ConvertMapping
         {
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index e24fbab3ac..37693c99e8 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -17,14 +17,14 @@ namespace osu.Game.Tests.Beatmaps
         {
         }
 
-        public TestWorkingBeatmap(Beatmap beatmap)
+        public TestWorkingBeatmap(IBeatmap beatmap)
             : base(beatmap.BeatmapInfo)
         {
             this.beatmap = beatmap;
         }
 
-        private readonly Beatmap beatmap;
-        protected override Beatmap GetBeatmap() => beatmap;
+        private readonly IBeatmap beatmap;
+        protected override IBeatmap GetBeatmap() => beatmap;
         protected override Texture GetBackground() => null;
 
         protected override Track GetTrack()
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
index 29132258c2..51460ecb6d 100644
--- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -259,9 +259,9 @@ namespace osu.Game.Tests.Visual
                 private readonly OsuSpriteText text;
 
                 private readonly Score score;
-                private readonly Beatmap beatmap;
+                private readonly IBeatmap beatmap;
 
-                public PerformanceDisplay(Score score, Beatmap beatmap)
+                public PerformanceDisplay(Score score, IBeatmap beatmap)
                 {
                     this.score = score;
                     this.beatmap = beatmap;
diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs
index 5ed43b2814..bda438d906 100644
--- a/osu.Game/Tests/Visual/TestCasePlayer.cs
+++ b/osu.Game/Tests/Visual/TestCasePlayer.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual
             }
         }
 
-        protected virtual Beatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
+        protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
 
         private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
 

From 03a5df84c6860738ec0723bc83df634d02666085 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 22:04:12 +0900
Subject: [PATCH 012/177] Initial rework of beatmap conversion process

---
 .../CatchBeatmapConversionTest.cs             |  2 +-
 .../Beatmaps/CatchBeatmapConverter.cs         |  5 ++
 .../Beatmaps/CatchBeatmapProcessor.cs         | 15 +++--
 .../CatchDifficultyCalculator.cs              |  6 +-
 osu.Game.Rulesets.Catch/CatchRuleset.cs       |  3 +
 .../Mods/CatchModHardRock.cs                  | 13 +++--
 .../UI/CatchRulesetContainer.cs               |  5 --
 .../ManiaBeatmapConversionTest.cs             |  2 +-
 .../Beatmaps/ManiaBeatmapConverter.cs         | 16 +++---
 .../ManiaDifficultyCalculator.cs              |  6 +-
 osu.Game.Rulesets.Mania/ManiaRuleset.cs       |  2 +
 osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs   |  5 +-
 .../Mods/ManiaModDualStages.cs                |  4 +-
 .../Replays/ManiaReplayFrame.cs               |  2 +-
 .../UI/ManiaRulesetContainer.cs               |  2 -
 .../OsuBeatmapConversionTest.cs               |  2 +-
 .../Beatmaps/OsuBeatmapConverter.cs           |  5 ++
 .../Beatmaps/OsuBeatmapProcessor.cs           | 13 +++--
 osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs  |  9 ++-
 .../OsuDifficulty/OsuDifficultyCalculator.cs  | 12 +---
 osu.Game.Rulesets.Osu/OsuRuleset.cs           |  5 +-
 .../Scoring/OsuPerformanceCalculator.cs       |  7 +--
 .../UI/OsuRulesetContainer.cs                 |  5 --
 .../TaikoBeatmapConversionTest.cs             |  7 +--
 .../Beatmaps/TaikoBeatmapConverter.cs         |  5 +-
 .../TaikoDifficultyCalculator.cs              |  7 +--
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs       |  2 +
 .../UI/TaikoRulesetContainer.cs               |  3 -
 .../Beatmaps/IO/ImportBeatmapTest.cs          |  8 +--
 .../Visual/TestCaseBeatSyncedContainer.cs     |  2 +-
 osu.Game/Beatmaps/BeatmapConverter.cs         | 32 ++++++-----
 .../Beatmaps/BeatmapManager_WorkingBeatmap.cs |  2 +-
 osu.Game/Beatmaps/BeatmapProcessor.cs         | 29 ++++++++--
 osu.Game/Beatmaps/DifficultyCalculator.cs     | 34 ++---------
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs      | 13 ++++-
 osu.Game/Beatmaps/IBeatmapConverter.cs        | 12 +++-
 osu.Game/Beatmaps/WorkingBeatmap.cs           | 56 +++++++++++++++----
 .../Containers/BeatSyncedContainer.cs         |  2 +-
 osu.Game/Overlays/MusicController.cs          |  2 +-
 .../Mods/IApplicableToBeatmapConverter.cs     |  5 +-
 .../Rulesets/Mods/IApplicableToHitObject.cs   |  5 +-
 osu.Game/Rulesets/Ruleset.cs                  |  4 ++
 .../Scoring/Legacy/LegacyScoreParser.cs       |  2 +-
 .../Rulesets/Scoring/PerformanceCalculator.cs | 20 +------
 osu.Game/Rulesets/UI/RulesetContainer.cs      | 42 +-------------
 .../Timelines/Summary/Parts/BreakPart.cs      |  2 +-
 .../Summary/Parts/ControlPointPart.cs         |  2 +-
 osu.Game/Screens/Edit/Editor.cs               |  2 +-
 osu.Game/Screens/Menu/LogoVisualisation.cs    |  2 +-
 osu.Game/Screens/Play/Player.cs               |  2 +-
 osu.Game/Screens/Ranking/ResultsPageScore.cs  |  2 +-
 osu.Game/Screens/Select/BeatmapInfoWedge.cs   |  2 +-
 .../Tests/Beatmaps/BeatmapConversionTest.cs   |  2 +-
 osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs |  2 +-
 osu.Game/Tests/Visual/EditorClockTestCase.cs  |  2 +-
 .../Tests/Visual/TestCasePerformancePoints.cs |  4 +-
 56 files changed, 230 insertions(+), 234 deletions(-)

diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index bf373867e8..a94d29ca2c 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.Tests
             }
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter();
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index b1b101e797..3b22c47ac8 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -13,6 +13,11 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
 {
     public class CatchBeatmapConverter : BeatmapConverter<CatchHitObject>
     {
+        public CatchBeatmapConverter(IBeatmap beatmap)
+            : base(beatmap)
+        {
+        }
+        
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
 
         protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index dfd10e0df7..e16f5fcb60 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -12,16 +12,21 @@ using OpenTK;
 
 namespace osu.Game.Rulesets.Catch.Beatmaps
 {
-    public class CatchBeatmapProcessor : BeatmapProcessor<CatchHitObject>
+    public class CatchBeatmapProcessor : BeatmapProcessor
     {
-        public override void PostProcess(Beatmap<CatchHitObject> beatmap)
+        public CatchBeatmapProcessor(IBeatmap beatmap)
+            : base(beatmap)
         {
-            initialiseHyperDash(beatmap.HitObjects);
+        }
 
-            base.PostProcess(beatmap);
+        public override void PostProcess()
+        {
+            initialiseHyperDash((List<CatchHitObject>)Beatmap.HitObjects);
+
+            base.PostProcess();
 
             int index = 0;
-            foreach (var obj in beatmap.HitObjects)
+            foreach (var obj in Beatmap.HitObjects.OfType<CatchHitObject>())
                 obj.IndexInBeatmap = index++;
         }
 
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
index 626a3d186a..f47d09fe20 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
@@ -2,20 +2,16 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
 using System.Collections.Generic;
 
 namespace osu.Game.Rulesets.Catch
 {
-    public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject>
+    public class CatchDifficultyCalculator : DifficultyCalculator
     {
         public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
         {
         }
 
         public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
-
-        protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter();
     }
 }
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index e2cb7b1d9b..f091fbbee4 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -13,12 +13,15 @@ using osu.Framework.Input.Bindings;
 using osu.Game.Rulesets.Catch.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Catch.Beatmaps;
 
 namespace osu.Game.Rulesets.Catch
 {
     public class CatchRuleset : Ruleset
     {
         public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+        public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
 
         public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
         {
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index df7578799f..8e19c0614a 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -6,10 +6,11 @@ using osu.Game.Rulesets.Catch.Objects;
 using osu.Game.Rulesets.Catch.UI;
 using osu.Game.Rulesets.Mods;
 using System;
+using osu.Game.Rulesets.Objects;
 
 namespace osu.Game.Rulesets.Catch.Mods
 {
-    public class CatchModHardRock : ModHardRock, IApplicableToHitObject<CatchHitObject>
+    public class CatchModHardRock : ModHardRock, IApplicableToHitObject
     {
         public override double ScoreMultiplier => 1.12;
         public override bool Ranked => true;
@@ -17,9 +18,11 @@ namespace osu.Game.Rulesets.Catch.Mods
         private float lastStartX;
         private int lastStartTime;
 
-        public void ApplyToHitObject(CatchHitObject hitObject)
+        public void ApplyToHitObject(HitObject hitObject)
         {
-            float position = hitObject.X;
+            var catchObject = (CatchHitObject)hitObject;
+
+            float position = catchObject.X;
             int startTime = (int)hitObject.StartTime;
 
             if (lastStartX == 0)
@@ -60,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Mods
                         position += rand;
                 }
 
-                hitObject.X = position;
+                catchObject.X = position;
 
                 return;
             }
@@ -79,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Mods
                 }
             }
 
-            hitObject.X = position;
+            catchObject.X = position;
 
             lastStartX = position;
             lastStartTime = startTime;
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 022a8a8b43..6e8a2c1660 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -4,7 +4,6 @@
 using osu.Framework.Input;
 using osu.Game.Beatmaps;
 using osu.Game.Input.Handlers;
-using osu.Game.Rulesets.Catch.Beatmaps;
 using osu.Game.Rulesets.Catch.Objects;
 using osu.Game.Rulesets.Catch.Objects.Drawable;
 using osu.Game.Rulesets.Catch.Replays;
@@ -29,10 +28,6 @@ namespace osu.Game.Rulesets.Catch.UI
 
         protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
 
-        protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
-
-        protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter() => new CatchBeatmapConverter();
-
         protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation);
 
         public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index fb1fce4279..e52fc11518 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index a142db8fed..93feddc143 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -33,18 +33,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         private ManiaBeatmap beatmap;
 
-        public ManiaBeatmapConverter(bool isForCurrentRuleset, IBeatmap original)
+        public ManiaBeatmapConverter(IBeatmap beatmap)
+            : base(beatmap)
         {
-            IsForCurrentRuleset = isForCurrentRuleset;
+            IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
+            
+            var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+            var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
 
-            var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize);
-            var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty);
-
-            if (isForCurrentRuleset)
+            if (beatmap.BeatmapInfo.Ruleset == new ManiaRuleset().RulesetInfo)
                 TargetColumns = (int)Math.Max(1, roundedCircleSize);
             else
             {
-                float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count();
+                float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count();
                 if (percentSliderOrSpinner < 0.2)
                     TargetColumns = 7;
                 else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
@@ -58,6 +59,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
         {
+
             BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
 
             int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 24fe72faec..822ba53eeb 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -10,7 +10,7 @@ using System.Collections.Generic;
 
 namespace osu.Game.Rulesets.Mania
 {
-    internal class ManiaDifficultyCalculator : DifficultyCalculator<ManiaHitObject>
+    internal class ManiaDifficultyCalculator : DifficultyCalculator
     {
         private const double star_scaling_factor = 0.018;
 
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
             int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
 
             foreach (var hitObject in Beatmap.HitObjects)
-                difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount));
+                difficultyHitObjects.Add(new ManiaHitObjectDifficulty((ManiaHitObject)hitObject, columnCount));
 
             // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
             difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
@@ -140,7 +140,5 @@ namespace osu.Game.Rulesets.Mania
 
             return difficulty;
         }
-
-        protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
     }
 }
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d5bf1b30fc..75b1e38a2f 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -15,12 +15,14 @@ using osu.Game.Graphics;
 using osu.Game.Rulesets.Mania.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Mania.Beatmaps;
 
 namespace osu.Game.Rulesets.Mania
 {
     public class ManiaRuleset : Ruleset
     {
         public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
 
         public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
         {
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
index dbd30121a8..e02db68a28 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs
@@ -3,19 +3,18 @@
 
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mods;
 
 namespace osu.Game.Rulesets.Mania.Mods
 {
-    public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter<ManiaHitObject>
+    public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter
     {
         public override string ShortenedName => Name;
         public abstract int KeyCount { get; }
         public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
         public override bool Ranked => true;
 
-        public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter)
+        public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
         {
             var mbc = (ManiaBeatmapConverter)beatmapConverter;
 
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index 197b37b3f5..dde8acf428 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -11,14 +11,14 @@ using osu.Game.Rulesets.UI;
 
 namespace osu.Game.Rulesets.Mania.Mods
 {
-    public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter<ManiaHitObject>, IApplicableToRulesetContainer<ManiaHitObject>
+    public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToRulesetContainer<ManiaHitObject>
     {
         public override string Name => "Dual Stages";
         public override string ShortenedName => "DS";
         public override string Description => @"Double the stages, double the fun!";
         public override double ScoreMultiplier => 0;
 
-        public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter)
+        public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
         {
             var mbc = (ManiaBeatmapConverter)beatmapConverter;
 
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 926a3a8923..bc9fd6e06f 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
         public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
         {
             // We don't need to fully convert, just create the converter
-            var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap);
+            var converter = new ManiaBeatmapConverter(beatmap);
 
             // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
             // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 76afaf270f..059eaaadcd 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -85,8 +85,6 @@ namespace osu.Game.Rulesets.Mania.UI
 
         public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
 
-        protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap);
-
         protected override DrawableHitObject<ManiaHitObject> GetVisualRepresentation(ManiaHitObject h)
         {
             ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index a77b20e83b..a8f82a112b 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter();
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index b1a52c5469..1cd4ec5668 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
 {
     internal class OsuBeatmapConverter : BeatmapConverter<OsuHitObject>
     {
+        public OsuBeatmapConverter(IBeatmap beatmap)
+            : base(beatmap)
+        {
+        }
+
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
 
         protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index afa2437bf6..c7c9f4a01a 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -8,12 +8,17 @@ using osu.Game.Rulesets.Osu.Objects;
 
 namespace osu.Game.Rulesets.Osu.Beatmaps
 {
-    internal class OsuBeatmapProcessor : BeatmapProcessor<OsuHitObject>
+    internal class OsuBeatmapProcessor : BeatmapProcessor
     {
-        public override void PostProcess(Beatmap<OsuHitObject> beatmap)
+        public OsuBeatmapProcessor(IBeatmap beatmap)
+            : base(beatmap)
         {
-            applyStacking(beatmap);
-            base.PostProcess(beatmap);
+        }
+
+        public override void PostProcess()
+        {
+            applyStacking((Beatmap<OsuHitObject>)Beatmap);
+            base.PostProcess();
         }
 
         private void applyStacking(Beatmap<OsuHitObject> beatmap)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index cf71116d47..7a30e6b134 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -5,20 +5,23 @@ using System.Collections.Generic;
 using System.Linq;
 using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Osu.UI;
 using OpenTK;
 
 namespace osu.Game.Rulesets.Osu.Mods
 {
-    public class OsuModHardRock : ModHardRock, IApplicableToHitObject<OsuHitObject>
+    public class OsuModHardRock : ModHardRock, IApplicableToHitObject
     {
         public override double ScoreMultiplier => 1.06;
         public override bool Ranked => true;
 
-        public void ApplyToHitObject(OsuHitObject hitObject)
+        public void ApplyToHitObject(HitObject hitObject)
         {
-            hitObject.Position = new Vector2(hitObject.Position.X, OsuPlayfield.BASE_SIZE.Y - hitObject.Y);
+            var osuObject = (OsuHitObject)hitObject;
+
+            osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
 
             var slider = hitObject as Slider;
             if (slider == null)
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
index 197bc16fc1..4853cd66cd 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
@@ -5,14 +5,13 @@ using System;
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Beatmaps;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
 using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
 
 namespace osu.Game.Rulesets.Osu.OsuDifficulty
 {
-    public class OsuDifficultyCalculator : DifficultyCalculator<OsuHitObject>
+    public class OsuDifficultyCalculator : DifficultyCalculator
     {
         private const int section_length = 400;
         private const double difficulty_multiplier = 0.0675;
@@ -27,14 +26,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
         {
         }
 
-        protected override void PreprocessHitObjects()
-        {
-            new OsuBeatmapProcessor().PostProcess(Beatmap);
-        }
-
         public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
         {
-            OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
+            OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
             Skill[] skills =
             {
                 new Aim(),
@@ -72,7 +66,5 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
 
             return starRating;
         }
-
-        protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter();
     }
 }
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index ed750882f4..02b887f1b9 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -22,12 +22,15 @@ using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Osu.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Osu.Beatmaps;
 
 namespace osu.Game.Rulesets.Osu
 {
     public class OsuRuleset : Ruleset
     {
         public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+        public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
 
         public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
         {
@@ -39,7 +42,7 @@ namespace osu.Game.Rulesets.Osu
 
         public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap)
         {
-            IEnumerable<HitObject> hitObjects = beatmap.Beatmap.HitObjects;
+            IEnumerable<HitObject> hitObjects = beatmap.OriginalBeatmap.HitObjects;
             IEnumerable<HitObject> circles = hitObjects.Where(c => !(c is IHasEndTime));
             IEnumerable<HitObject> sliders = hitObjects.Where(s => s is IHasCurve);
             IEnumerable<HitObject> spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index c8806cbdb1..6b9214d9dc 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -6,14 +6,13 @@ using System.Collections.Generic;
 using System.Linq;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu.Beatmaps;
 using osu.Game.Rulesets.Osu.Mods;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Scoring;
 
 namespace osu.Game.Rulesets.Osu.Scoring
 {
-    public class OsuPerformanceCalculator : PerformanceCalculator<OsuHitObject>
+    public class OsuPerformanceCalculator : PerformanceCalculator
     {
         private readonly int countHitCircles;
         private readonly int beatmapMaxCombo;
@@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
         {
             countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
 
-            beatmapMaxCombo = Beatmap.HitObjects.Count;
+            beatmapMaxCombo = Beatmap.HitObjects.Count();
             beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count) + 1;
         }
 
@@ -193,7 +192,5 @@ namespace osu.Game.Rulesets.Osu.Scoring
 
         private double totalHits => count300 + count100 + count50 + countMiss;
         private double totalSuccessfulHits => count300 + count100 + count50;
-
-        protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
     }
 }
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 22c7b719cd..603f95dc47 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -7,7 +7,6 @@ using OpenTK;
 using osu.Game.Beatmaps;
 using osu.Game.Input.Handlers;
 using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Beatmaps;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Osu.Objects.Drawables;
 using osu.Game.Rulesets.Osu.Replays;
@@ -28,10 +27,6 @@ namespace osu.Game.Rulesets.Osu.UI
 
         public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
 
-        protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
-
-        protected override BeatmapProcessor<OsuHitObject> CreateBeatmapProcessor() => new OsuBeatmapProcessor();
-
         protected override Playfield CreatePlayfield() => new OsuPlayfield();
 
         public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 3d236af8ad..cbab53fa75 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -18,14 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
 
-        private bool isForCurrentRuleset;
-
         [NonParallelizable]
         [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
         [TestCase("slider-generating-drumroll", false)]
-        public void Test(string name, bool isForCurrentRuleset)
+        public new void Test(string name)
         {
-            this.isForCurrentRuleset = isForCurrentRuleset;
             base.Test(name);
         }
 
@@ -43,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
+        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
     }
 
     public struct ConvertValue : IEquatable<ConvertValue>
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index eabe7eb91a..eeb0fa1871 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -42,9 +42,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
 
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) };
 
-        public TaikoBeatmapConverter(bool isForCurrentRuleset)
+        public TaikoBeatmapConverter(IBeatmap beatmap)
+            : base(beatmap)
         {
-            this.isForCurrentRuleset = isForCurrentRuleset;
+            isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo);
         }
 
         protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
index 66b8459d1f..f14c53f7ae 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
@@ -2,14 +2,13 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Taiko.Beatmaps;
 using osu.Game.Rulesets.Taiko.Objects;
 using System.Collections.Generic;
 using System;
 
 namespace osu.Game.Rulesets.Taiko
 {
-    internal class TaikoDifficultyCalculator : DifficultyCalculator<TaikoHitObject>
+    internal class TaikoDifficultyCalculator : DifficultyCalculator
     {
         private const double star_scaling_factor = 0.04125;
 
@@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko
             difficultyHitObjects.Clear();
 
             foreach (var hitObject in Beatmap.HitObjects)
-                difficultyHitObjects.Add(new TaikoHitObjectDifficulty(hitObject));
+                difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
 
             // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
             difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
@@ -132,7 +131,5 @@ namespace osu.Game.Rulesets.Taiko
 
             return difficulty;
         }
-
-        protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(true);
     }
 }
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index e5f3b33355..2e27b34e69 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -13,12 +13,14 @@ using osu.Framework.Input.Bindings;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Rulesets.Taiko.Replays;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Taiko.Beatmaps;
 
 namespace osu.Game.Rulesets.Taiko
 {
     public class TaikoRuleset : Ruleset
     {
         public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
 
         public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
         {
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 3d3c6ab2f3..0a84d74c27 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Objects.Drawables;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Replays;
 using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Beatmaps;
 using osu.Game.Rulesets.Taiko.Objects;
 using osu.Game.Rulesets.Taiko.Objects.Drawables;
 using osu.Game.Rulesets.Taiko.Scoring;
@@ -93,8 +92,6 @@ namespace osu.Game.Rulesets.Taiko.UI
 
         public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
 
-        protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
-
         public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
 
         protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo)
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index f60caf2397..3530ae928d 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -274,13 +274,13 @@ namespace osu.Game.Tests.Beatmaps.IO
             foreach (BeatmapInfo b in set.Beatmaps)
                 Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
             Assert.IsTrue(set.Beatmaps.Count > 0);
-            var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
+            var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.OriginalBeatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.OriginalBeatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.OriginalBeatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.OriginalBeatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
         }
 
diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
index 596b7839e0..4e3462b5ea 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
@@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual
                 };
             }
 
-            private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints;
+            private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.OriginalBeatmap.ControlPointInfo.TimingPoints;
             private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
             {
                 if (timingPoints[timingPoints.Count - 1] == current)
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index fab7860077..263a4c36ee 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -22,25 +22,27 @@ namespace osu.Game.Beatmaps
             remove => ObjectConverted -= value;
         }
 
-        /// <summary>
-        /// Checks if a Beatmap can be converted using this Beatmap Converter.
-        /// </summary>
-        /// <param name="beatmap">The Beatmap to check.</param>
-        /// <returns>Whether the Beatmap can be converted using this Beatmap Converter.</returns>
-        public bool CanConvert(IBeatmap beatmap) => ValidConversionTypes.All(t => beatmap.HitObjects.Any(t.IsInstanceOfType));
+        public IBeatmap Beatmap { get; }
 
-        /// <summary>
-        /// Converts a Beatmap using this Beatmap Converter.
-        /// </summary>
-        /// <param name="original">The un-converted Beatmap.</param>
-        /// <returns>The converted Beatmap.</returns>
-        public Beatmap<T> Convert(IBeatmap original)
+        protected BeatmapConverter(IBeatmap beatmap)
         {
-            // We always operate on a clone of the original beatmap, to not modify it game-wide
-            return ConvertBeatmap(original.Clone());
+            Beatmap = beatmap;
         }
 
-        void IBeatmapConverter.Convert(IBeatmap original) => Convert(original);
+        /// <summary>
+        /// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>.
+        /// </summary>
+        public bool CanConvert => ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
+
+        /// <summary>
+        /// Converts <see cref="Beatmap"/>.
+        /// </summary>
+        /// <returns>The converted Beatmap.</returns>
+        public IBeatmap Convert()
+        {
+            // We always operate on a clone of the original beatmap, to not modify it game-wide
+            return ConvertBeatmap(Beatmap.Clone());
+        }
 
         /// <summary>
         /// Performs the conversion of a Beatmap using this Beatmap Converter.
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 71406c6034..cff500fabf 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
                 this.audioManager = audioManager;
             }
 
-            protected override IBeatmap GetBeatmap()
+            protected override IBeatmap GetOriginalBeatmap()
             {
                 try
                 {
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index 8f5a2a4cab..bf1cd7d4ee 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -2,30 +2,47 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Linq;
-using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 
 namespace osu.Game.Beatmaps
 {
+    public interface IBeatmapProcessor
+    {
+        IBeatmap Beatmap { get; }
+
+        /// <summary>
+        /// Post-processes <see cref="Beatmap"/> to add mode-specific components that aren't added during conversion.
+        /// <para>
+        /// An example of such a usage is for combo colours.
+        /// </para>
+        /// </summary>
+        void PostProcess();
+    }
+
     /// <summary>
     /// Processes a post-converted Beatmap.
     /// </summary>
     /// <typeparam name="TObject">The type of HitObject contained in the Beatmap.</typeparam>
-    public class BeatmapProcessor<TObject>
-        where TObject : HitObject
+    public class BeatmapProcessor : IBeatmapProcessor
     {
+        public IBeatmap Beatmap { get; }
+
+        public BeatmapProcessor(IBeatmap beatmap)
+        {
+            Beatmap = beatmap;
+        }
+
         /// <summary>
         /// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
         /// <para>
         /// An example of such a usage is for combo colours.
         /// </para>
         /// </summary>
-        /// <param name="beatmap">The Beatmap to process.</param>
-        public virtual void PostProcess(Beatmap<TObject> beatmap)
+        public virtual void PostProcess()
         {
             IHasComboInformation lastObj = null;
 
-            foreach (var obj in beatmap.HitObjects.OfType<IHasComboInformation>())
+            foreach (var obj in Beatmap.HitObjects.OfType<IHasComboInformation>())
             {
                 if (obj.NewCombo)
                 {
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index bf252ff51f..37155c09cd 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Game.Rulesets.Objects;
 using System.Collections.Generic;
 using osu.Game.Rulesets.Mods;
 using osu.Framework.Timing;
@@ -12,30 +11,17 @@ namespace osu.Game.Beatmaps
 {
     public abstract class DifficultyCalculator
     {
-        protected double TimeRate = 1;
-
-        public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
-    }
-
-    public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
-    {
-        protected readonly Beatmap<T> Beatmap;
+        protected readonly IBeatmap Beatmap;
         protected readonly Mod[] Mods;
 
+        protected double TimeRate = 1;
+
         protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
         {
+            Beatmap = beatmap;
             Mods = mods ?? new Mod[0];
 
-            var converter = CreateBeatmapConverter(beatmap);
-
-            foreach (var mod in Mods.OfType<IApplicableToBeatmapConverter<T>>())
-                mod.ApplyToBeatmapConverter(converter);
-
-            Beatmap = converter.Convert(beatmap);
-
             ApplyMods(Mods);
-
-            PreprocessHitObjects();
         }
 
         protected virtual void ApplyMods(Mod[] mods)
@@ -43,22 +29,12 @@ namespace osu.Game.Beatmaps
             var clock = new StopwatchClock();
             mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
             TimeRate = clock.Rate;
-
-            foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
-                mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
-
-            foreach (var h in Beatmap.HitObjects)
-                h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
-
-            foreach (var mod in mods.OfType<IApplicableToHitObject<T>>())
-                foreach (var obj in Beatmap.HitObjects)
-                    mod.ApplyToHitObject(obj);
         }
 
         protected virtual void PreprocessHitObjects()
         {
         }
 
-        protected abstract BeatmapConverter<T> CreateBeatmapConverter(IBeatmap beatmap);
+        public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
     }
 }
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 0d325284e1..23ddc5a976 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio.Track;
 using osu.Framework.Graphics.Textures;
 using osu.Game.Rulesets;
 using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.UI;
 
 namespace osu.Game.Beatmaps
@@ -39,7 +40,7 @@ namespace osu.Game.Beatmaps
             this.game = game;
         }
 
-        protected override IBeatmap GetBeatmap() => new Beatmap();
+        protected override IBeatmap GetOriginalBeatmap() => new Beatmap();
 
         protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
 
@@ -58,6 +59,8 @@ namespace osu.Game.Beatmaps
                     throw new NotImplementedException();
                 }
 
+                public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
+
                 public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null;
 
                 public override string Description => "dummy";
@@ -68,6 +71,14 @@ namespace osu.Game.Beatmaps
                     : base(rulesetInfo)
                 {
                 }
+
+                private class DummyBeatmapConverter : IBeatmapConverter
+                {
+                    public event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
+                    public IBeatmap Beatmap { get; set; }
+                    public bool CanConvert => true;
+                    public IBeatmap Convert() => Beatmap;
+                }
             }
         }
     }
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
index 4df250ad17..00566093b8 100644
--- a/osu.Game/Beatmaps/IBeatmapConverter.cs
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -16,10 +16,16 @@ namespace osu.Game.Beatmaps
         /// </summary>
         event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
 
+        IBeatmap Beatmap { get; }
+
         /// <summary>
-        /// Converts a Beatmap using this Beatmap Converter.
+        /// Whether <see cref="Beatmap"/> can be converted by this <see cref="IBeatmapConverter"/>.
         /// </summary>
-        /// <param name="beatmap">The un-converted Beatmap.</param>
-        void Convert(IBeatmap beatmap);
+        bool CanConvert { get; }
+
+        /// <summary>
+        /// Converts <see cref="Beatmap"/>.
+        /// </summary>
+        IBeatmap Convert();
     }
 }
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index fc67d2e508..7d6c23dad0 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -14,6 +14,8 @@ using osu.Framework.IO.File;
 using System.IO;
 using osu.Game.IO.Serialization;
 using System.Diagnostics;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.UI;
 using osu.Game.Skinning;
 
 namespace osu.Game.Beatmaps
@@ -36,7 +38,7 @@ namespace osu.Game.Beatmaps
 
             Mods.ValueChanged += mods => applyRateAdjustments();
 
-            beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
+            originalBeatmap = new AsyncLazy<IBeatmap>(populateOriginalBeatmap);
             background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
             track = new AsyncLazy<Track>(populateTrack);
             waveform = new AsyncLazy<Waveform>(populateWaveform);
@@ -51,26 +53,25 @@ namespace osu.Game.Beatmaps
         {
             var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
             using (var sw = new StreamWriter(path))
-                sw.WriteLine(Beatmap.Serialize());
+                sw.WriteLine(OriginalBeatmap.Serialize());
             Process.Start(path);
         }
 
-        protected abstract IBeatmap GetBeatmap();
+        protected abstract IBeatmap GetOriginalBeatmap();
         protected abstract Texture GetBackground();
         protected abstract Track GetTrack();
         protected virtual Skin GetSkin() => new DefaultSkin();
         protected virtual Waveform GetWaveform() => new Waveform();
         protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
 
-        public bool BeatmapLoaded => beatmap.IsResultAvailable;
-        public IBeatmap Beatmap => beatmap.Value.Result;
-        public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
+        public bool BeatmapLoaded => originalBeatmap.IsResultAvailable;
+        public IBeatmap OriginalBeatmap => originalBeatmap.Value.Result;
+        public async Task<IBeatmap> GetOriginalBeatmapAsync() => await originalBeatmap.Value;
+        private readonly AsyncLazy<IBeatmap> originalBeatmap;
 
-        private readonly AsyncLazy<IBeatmap> beatmap;
-
-        private IBeatmap populateBeatmap()
+        private IBeatmap populateOriginalBeatmap()
         {
-            var b = GetBeatmap() ?? new Beatmap();
+            var b = GetOriginalBeatmap() ?? new Beatmap();
 
             // use the database-backed info.
             b.BeatmapInfo = BeatmapInfo;
@@ -78,6 +79,41 @@ namespace osu.Game.Beatmaps
             return b;
         }
 
+        public IBeatmap GetBeatmap(RulesetInfo ruleset)
+        {
+            var rulesetInstance = ruleset.CreateInstance();
+
+            IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(OriginalBeatmap);
+
+            // Check if the beatmap can be converted
+            if (!converter.CanConvert)
+                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
+
+            // Apply conversion mods
+            foreach (var mod in Mods.Value.OfType<IApplicableToBeatmapConverter>())
+                mod.ApplyToBeatmapConverter(converter);
+
+            // Convert
+            IBeatmap converted = converter.Convert();
+
+            // Apply difficulty mods
+            foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
+                mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
+
+            // Post-process
+            rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();
+
+            // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed
+            foreach (var obj in converted.HitObjects)
+                obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
+
+            foreach (var mod in Mods.Value.OfType<IApplicableToHitObject>())
+            foreach (var obj in converted.HitObjects)
+                mod.ApplyToHitObject(obj);
+
+            return converted;
+        }
+
         public bool BackgroundLoaded => background.IsResultAvailable;
         public Texture Background => background.Value.Result;
         public async Task<Texture> GetBackgroundAsync() => await background.Value;
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index bf16af4706..8d8717a612 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Graphics.Containers
             if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return;
 
             var track = Beatmap.Value.Track;
-            var beatmap = Beatmap.Value.Beatmap;
+            var beatmap = Beatmap.Value.OriginalBeatmap;
 
             if (track == null || beatmap == null)
                 return;
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index b4021f2808..eb88fe0eb4 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -343,7 +343,7 @@ namespace osu.Game.Overlays
                 // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
                 Task.Run(() =>
                 {
-                    if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
+                    if (beatmap?.OriginalBeatmap == null) //this is not needed if a placeholder exists
                     {
                         title.Current = null;
                         title.Text = @"Nothing to play";
diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
index a03a003810..1b8e62b53c 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs
@@ -10,13 +10,12 @@ namespace osu.Game.Rulesets.Mods
     /// Interface for a <see cref="Mod"/> that applies changes to a <see cref="BeatmapConverter{TObject}"/>.
     /// </summary>
     /// <typeparam name="TObject">The type of converted <see cref="HitObject"/>.</typeparam>
-    public interface IApplicableToBeatmapConverter<TObject> : IApplicableMod
-        where TObject : HitObject
+    public interface IApplicableToBeatmapConverter : IApplicableMod
     {
         /// <summary>
         /// Applies this <see cref="Mod"/> to a <see cref="BeatmapConverter{TObject}"/>.
         /// </summary>
         /// <param name="beatmapConverter">The <see cref="BeatmapConverter{TObject}"/> to apply to.</param>
-        void ApplyToBeatmapConverter(BeatmapConverter<TObject> beatmapConverter);
+        void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter);
     }
 }
diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
index 0fd2e398c8..d6f330d9df 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs
@@ -8,13 +8,12 @@ namespace osu.Game.Rulesets.Mods
     /// <summary>
     /// An interface for <see cref="Mod"/>s that can be applied to <see cref="HitObject"/>s.
     /// </summary>
-    public interface IApplicableToHitObject<in TObject> : IApplicableMod
-        where TObject : HitObject
+    public interface IApplicableToHitObject : IApplicableMod
     {
         /// <summary>
         /// Applies this <see cref="IApplicableToHitObject{TObject}"/> to a <see cref="HitObject"/>.
         /// </summary>
         /// <param name="hitObject">The <see cref="HitObject"/> to apply to.</param>
-        void ApplyToHitObject(TObject hitObject);
+        void ApplyToHitObject(HitObject hitObject);
     }
 }
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 3f8512eb90..6f117332b2 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -57,6 +57,10 @@ namespace osu.Game.Rulesets
         /// <returns></returns>
         public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
 
+        public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
+
+        public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
+
         public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null);
 
         public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null;
diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index d5ab856697..3145561001 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy
                 /* score.FileChecksum = */
                 var beatmapHash = sr.ReadString();
                 score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
-                currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
+                currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).OriginalBeatmap;
 
                 /* score.PlayerName = */
                 score.User = new User { Username = sr.ReadString() };
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
index 6392d2c0ae..5b8f5f0d0f 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -2,42 +2,28 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
-using System.Linq;
 using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects;
 
 namespace osu.Game.Rulesets.Scoring
 {
     public abstract class PerformanceCalculator
-    {
-        public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
-    }
-
-    public abstract class PerformanceCalculator<TObject> : PerformanceCalculator
-        where TObject : HitObject
     {
         private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
         protected IDictionary<string, double> Attributes => attributes;
 
-        protected readonly Beatmap<TObject> Beatmap;
+        protected readonly IBeatmap Beatmap;
         protected readonly Score Score;
 
         protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
         {
             Score = score;
 
-            var converter = CreateBeatmapConverter();
-
-            foreach (var mod in score.Mods.OfType<IApplicableToBeatmapConverter<TObject>>())
-                mod.ApplyToBeatmapConverter(converter);
-
-            Beatmap = converter.Convert(beatmap);
+            Beatmap = beatmap;
 
             var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
             diffCalc.Calculate(attributes);
         }
 
-        protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
+        public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
     }
 }
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index d1f1807937..074380da56 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -219,30 +219,7 @@ namespace osu.Game.Rulesets.UI
 
             RelativeSizeAxes = Axes.Both;
 
-            BeatmapConverter<TObject> converter = CreateBeatmapConverter();
-            BeatmapProcessor<TObject> processor = CreateBeatmapProcessor();
-
-            // Check if the beatmap can be converted
-            if (!converter.CanConvert(workingBeatmap.Beatmap))
-                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
-
-            // Apply conversion adjustments before converting
-            foreach (var mod in Mods.OfType<IApplicableToBeatmapConverter<TObject>>())
-                mod.ApplyToBeatmapConverter(converter);
-
-            // Convert the beatmap
-            Beatmap = converter.Convert(workingBeatmap.Beatmap);
-
-            // Apply difficulty adjustments from mods before using Difficulty.
-            foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
-                mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
-
-            // Post-process the beatmap
-            processor.PostProcess(Beatmap);
-
-            // Apply defaults
-            foreach (var h in Beatmap.HitObjects)
-                h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
+            Beatmap = (Beatmap<TObject>)workingBeatmap.GetBeatmap(ruleset.RulesetInfo);
 
             KeyBindingInputManager = CreateInputManager();
             KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
@@ -277,10 +254,6 @@ namespace osu.Game.Rulesets.UI
             if (mods == null)
                 return;
 
-            foreach (var mod in mods.OfType<IApplicableToHitObject<TObject>>())
-                foreach (var obj in Beatmap.HitObjects)
-                    mod.ApplyToHitObject(obj);
-
             foreach (var mod in mods.OfType<IApplicableToRulesetContainer<TObject>>())
                 mod.ApplyToRulesetContainer(this);
         }
@@ -324,13 +297,6 @@ namespace osu.Game.Rulesets.UI
             Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea;
         }
 
-        /// <summary>
-        /// Creates a processor to perform post-processing operations
-        /// on HitObjects in converted Beatmaps.
-        /// </summary>
-        /// <returns>The Beatmap processor.</returns>
-        protected virtual BeatmapProcessor<TObject> CreateBeatmapProcessor() => new BeatmapProcessor<TObject>();
-
         /// <summary>
         /// Computes the size of the <see cref="Playfield"/> in relative coordinate space after aspect adjustments.
         /// </summary>
@@ -344,12 +310,6 @@ namespace osu.Game.Rulesets.UI
         /// </summary>
         protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default
 
-        /// <summary>
-        /// Creates a converter to convert Beatmap to a specific mode.
-        /// </summary>
-        /// <returns>The Beatmap converter.</returns>
-        protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
-
         /// <summary>
         /// Creates a DrawableHitObject from a HitObject.
         /// </summary>
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
index 1146037004..e081897339 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
         protected override void LoadBeatmap(WorkingBeatmap beatmap)
         {
             base.LoadBeatmap(beatmap);
-            foreach (var breakPeriod in beatmap.Beatmap.Breaks)
+            foreach (var breakPeriod in beatmap.OriginalBeatmap.Breaks)
                 Add(new BreakVisualisation(breakPeriod));
         }
 
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
index 4bef22463e..faf090bfd9 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
         {
             base.LoadBeatmap(beatmap);
 
-            ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo;
+            ControlPointInfo cpi = beatmap.OriginalBeatmap.ControlPointInfo;
 
             cpi.TimingPoints.ForEach(addTimingPoint);
 
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index ea1d85bb5b..4ad11e6a91 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit
         {
             // TODO: should probably be done at a RulesetContainer level to share logic with Player.
             var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
-            clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false };
+            clock = new EditorClock(Beatmap.Value.OriginalBeatmap.ControlPointInfo, beatDivisor) { IsCoupled = false };
             clock.ChangeSource(sourceClock);
 
             dependencies.CacheAs<IFrameBasedClock>(clock);
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index 1f2cb915b3..bbd50d8f1b 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Screens.Menu
         private void updateAmplitudes()
         {
             var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
-            var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
+            var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.OriginalBeatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
 
             float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes ?? new float[256];
 
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 32685935a1..bfc0cec18f 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Screens.Play
 
             try
             {
-                beatmap = working.Beatmap;
+                beatmap = working.OriginalBeatmap;
 
                 if (beatmap == null)
                     throw new InvalidOperationException("Beatmap was not loaded");
diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs
index 9d92439a4b..6d3812ac4b 100644
--- a/osu.Game/Screens/Ranking/ResultsPageScore.cs
+++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Screens.Ranking
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                     Alpha = 0.5f,
-                                    Objects = Beatmap.Beatmap.HitObjects,
+                                    Objects = Beatmap.OriginalBeatmap.HitObjects,
                                 },
                                 scoreCounter = new SlowScoreCounter(6)
                                 {
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index da82a49f51..2dad5a9008 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -210,7 +210,7 @@ namespace osu.Game.Screens.Select
 
             private InfoLabel[] getInfoLabels()
             {
-                var beatmap = working.Beatmap;
+                var beatmap = working.OriginalBeatmap;
                 var info = working.BeatmapInfo;
 
                 List<InfoLabel> labels = new List<InfoLabel>();
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index d02ccaff16..735c0ef76c 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
                 result.Mappings.Add(mapping);
             };
 
-            converter.Convert(beatmap);
+            converter.Convert();
 
             return result;
         }
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 37693c99e8..71893cfe37 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         private readonly IBeatmap beatmap;
-        protected override IBeatmap GetBeatmap() => beatmap;
+        protected override IBeatmap GetOriginalBeatmap() => beatmap;
         protected override Texture GetBackground() => null;
 
         protected override Track GetTrack()
diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs
index 43b20f7021..c6a98a4f43 100644
--- a/osu.Game/Tests/Visual/EditorClockTestCase.cs
+++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
 
         private void beatmapChanged(WorkingBeatmap working)
         {
-            Clock.ControlPointInfo = working.Beatmap.ControlPointInfo;
+            Clock.ControlPointInfo = working.OriginalBeatmap.ControlPointInfo;
             Clock.ChangeSource((IAdjustableClock)working.Track ?? new StopwatchClock());
             Clock.ProcessFrame();
         }
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
index 51460ecb6d..5d78b95c04 100644
--- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -250,7 +250,7 @@ namespace osu.Game.Tests.Visual
                     return;
 
                 lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset);
-                lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
+                lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.OriginalBeatmap)));
                 api.Queue(lastRequest);
             }
 
@@ -381,7 +381,7 @@ namespace osu.Game.Tests.Visual
                     var allMods = ruleset.GetAllMods().ToList();
                     Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
 
-                    var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
+                    var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.OriginalBeatmap, activeMods);
                     if (diffCalc != null)
                     {
                         var categories = new Dictionary<string, double>();

From 42aa02579bb909918ecc0cd50a6a163366c372aa Mon Sep 17 00:00:00 2001
From: TocoToucan <TocoToucanMS@gmail.com>
Date: Sun, 29 Apr 2018 19:52:33 +0300
Subject: [PATCH 013/177] Add 'Back' global key binding

---
 osu.Game/Input/Bindings/GlobalActionContainer.cs | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index dd8f00f6cd..565d530395 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Input.Bindings
         {
             new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
             new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
-            new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
+            new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
 
             new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
             new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
@@ -36,6 +36,9 @@ namespace osu.Game.Input.Bindings
             new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
+
+            new KeyBinding(InputKey.Escape, GlobalAction.Back),
+            new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
         };
 
         public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@@ -76,6 +79,9 @@ namespace osu.Game.Input.Bindings
         QuickRetry,
 
         [Description("Take screenshot")]
-        TakeScreenshot
+        TakeScreenshot,
+
+        [Description("Go back")]
+        Back
     }
 }

From 804b59ee8073cf54fa475e8dc9780edab842a18d Mon Sep 17 00:00:00 2001
From: TocoToucan <TocoToucanMS@gmail.com>
Date: Sun, 29 Apr 2018 20:15:09 +0300
Subject: [PATCH 014/177] Handle GlobalAction.Back

---
 osu.Game/Screens/Menu/ButtonSystem.cs | 42 +++++++++++++++++++++------
 osu.Game/Screens/OsuScreen.cs         | 35 +++++++++++++---------
 2 files changed, 55 insertions(+), 22 deletions(-)

diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index f48e1925c5..8cf0d24f7d 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -6,22 +6,24 @@ using System.Collections.Generic;
 using System.Linq;
 using osu.Framework;
 using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
 using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Threading;
 using osu.Game.Graphics;
+using osu.Game.Input.Bindings;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Audio;
-using osu.Framework.Configuration;
-using osu.Framework.Threading;
 
 namespace osu.Game.Screens.Menu
 {
-    public class ButtonSystem : Container, IStateful<MenuState>
+    public class ButtonSystem : Container, IStateful<MenuState>, IKeyBindingHandler<GlobalAction>
     {
         public event Action<MenuState> StateChanged;
 
@@ -146,7 +148,16 @@ namespace osu.Game.Screens.Menu
                 case Key.Space:
                     logo?.TriggerOnClick(state);
                     return true;
-                case Key.Escape:
+            }
+
+            return false;
+        }
+
+        public bool OnPressed(GlobalAction action)
+        {
+            switch (action)
+            {
+                case GlobalAction.Back:
                     switch (State)
                     {
                         case MenuState.TopLevel:
@@ -155,14 +166,26 @@ namespace osu.Game.Screens.Menu
                         case MenuState.Play:
                             backButton.TriggerOnClick();
                             return true;
+                        default:
+                            return false;
                     }
-
+                default:
                     return false;
             }
-
-            return false;
         }
 
+        public bool OnReleased(GlobalAction action)
+        {
+            switch (action)
+            {
+                case GlobalAction.Back:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+
         private void onPlay()
         {
             State = MenuState.Play;
@@ -337,6 +360,7 @@ namespace osu.Game.Screens.Menu
                             logo.ScaleTo(0.5f, 200, Easing.OutQuint);
                             break;
                     }
+
                     break;
                 case MenuState.EnteringMode:
                     logoTracking = true;
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 7a910574e0..5d2c46f438 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -3,22 +3,22 @@
 
 using System;
 using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
 using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.Input.Bindings;
 using osu.Framework.Screens;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics.Containers;
-using OpenTK;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Audio;
-using osu.Framework.Graphics;
+using osu.Game.Input.Bindings;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Menu;
-using osu.Framework.Input;
-using OpenTK.Input;
+using OpenTK;
 
 namespace osu.Game.Screens
 {
-    public abstract class OsuScreen : Screen
+    public abstract class OsuScreen : Screen, IKeyBindingHandler<GlobalAction>
     {
         public BackgroundScreen Background { get; private set; }
 
@@ -90,18 +90,27 @@ namespace osu.Game.Screens
             sampleExit = audio.Sample.Get(@"UI/screen-back");
         }
 
-        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+        public bool OnPressed(GlobalAction action)
         {
-            if (args.Repeat || !IsCurrentScreen) return false;
-
-            switch (args.Key)
+            switch (action)
             {
-                case Key.Escape:
+                case GlobalAction.Back:
                     Exit();
                     return true;
+                default:
+                    return false;
             }
+        }
 
-            return base.OnKeyDown(state, args);
+        public bool OnReleased(GlobalAction action)
+        {
+            switch (action)
+            {
+                case GlobalAction.Back:
+                    return true;
+                default:
+                    return false;
+            }
         }
 
         protected override void OnResuming(Screen last)

From 2d6b2d10f7e83376a37433b84d30254c4575d838 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 2 May 2018 18:26:23 +0900
Subject: [PATCH 015/177] Use bindable transforms

---
 osu-framework                                 |  2 +-
 osu.Game/Overlays/ChatOverlay.cs              | 25 +----------------
 .../UI/Scrolling/ScrollingPlayfield.cs        | 27 ++-----------------
 3 files changed, 4 insertions(+), 50 deletions(-)

diff --git a/osu-framework b/osu-framework
index 0773d895d9..96ef8c43b5 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed
+Subproject commit 96ef8c43b5e6b6ae14b01c3550c480c8d9a78518
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 331dcec4c0..a2542c537f 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -12,10 +12,8 @@ using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Transforms;
 using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Input;
-using osu.Framework.MathUtils;
 using osu.Framework.Threading;
 using osu.Game.Configuration;
 using osu.Game.Graphics;
@@ -181,7 +179,7 @@ namespace osu.Game.Overlays
                 {
                     textbox.HoldFocus = false;
                     if (1f - ChatHeight.Value < channel_selection_min_height)
-                        transformChatHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint);
+                        this.TransformBindableTo(ChatHeight, 1f - channel_selection_min_height, 800, Easing.OutQuint);
                 }
                 else
                     textbox.HoldFocus = true;
@@ -533,26 +531,5 @@ namespace osu.Game.Overlays
 
             api.Queue(req);
         }
-
-        private void transformChatHeightTo(double newChatHeight, double duration = 0, Easing easing = Easing.None)
-        {
-            this.TransformTo(this.PopulateTransform(new TransformChatHeight(), newChatHeight, duration, easing));
-        }
-
-        private class TransformChatHeight : Transform<double, ChatOverlay>
-        {
-            private double valueAt(double time)
-            {
-                if (time < StartTime) return StartValue;
-                if (time >= EndTime) return EndValue;
-
-                return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
-            }
-
-            public override string TargetMember => "ChatHeight.Value";
-
-            protected override void Apply(ChatOverlay d, double time) => d.ChatHeight.Value = valueAt(time);
-            protected override void ReadIntoStartValue(ChatOverlay d) => StartValue = d.ChatHeight.Value;
-        }
     }
 }
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
index 1e7f561aba..6f86d20295 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs
@@ -4,9 +4,7 @@
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
-using osu.Framework.Graphics.Transforms;
 using osu.Framework.Input;
-using osu.Framework.MathUtils;
 using osu.Game.Rulesets.Objects.Drawables;
 using OpenTK.Input;
 
@@ -90,10 +88,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
                 switch (args.Key)
                 {
                     case Key.Minus:
-                        transformVisibleTimeRangeTo(VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
+                        this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint);
                         break;
                     case Key.Plus:
-                        transformVisibleTimeRangeTo(VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
+                        this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint);
                         break;
                 }
             }
@@ -101,27 +99,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
             return false;
         }
 
-        private void transformVisibleTimeRangeTo(double newTimeRange, double duration = 0, Easing easing = Easing.None)
-        {
-            this.TransformTo(this.PopulateTransform(new TransformVisibleTimeRange(), newTimeRange, duration, easing));
-        }
-
         protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction);
-
-        private class TransformVisibleTimeRange : Transform<double, ScrollingPlayfield>
-        {
-            private double valueAt(double time)
-            {
-                if (time < StartTime) return StartValue;
-                if (time >= EndTime) return EndValue;
-
-                return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
-            }
-
-            public override string TargetMember => "VisibleTimeRange.Value";
-
-            protected override void Apply(ScrollingPlayfield d, double time) => d.VisibleTimeRange.Value = valueAt(time);
-            protected override void ReadIntoStartValue(ScrollingPlayfield d) => StartValue = d.VisibleTimeRange.Value;
-        }
     }
 }

From d4ada3000cb059b1a55ea43c3f348d6a731ed4d9 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 2 May 2018 19:37:47 +0900
Subject: [PATCH 016/177] Add keybind to disable mouse buttons

---
 osu.Game/Configuration/OsuConfigManager.cs       | 6 ++++++
 osu.Game/Input/Bindings/GlobalActionContainer.cs | 5 ++++-
 osu.Game/OsuGame.cs                              | 3 +++
 osu.Game/Overlays/OnScreenDisplay.cs             | 4 +++-
 4 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 509622c2fe..b3082e49de 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using osu.Framework.Configuration;
+using osu.Framework.Configuration.Tracking;
 using osu.Framework.Platform;
 using osu.Game.Overlays;
 using osu.Game.Screens.Select;
@@ -95,6 +96,11 @@ namespace osu.Game.Configuration
         public OsuConfigManager(Storage storage) : base(storage)
         {
         }
+
+        public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
+        {
+            new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled"))
+        };
     }
 
     public enum OsuSetting
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index dd8f00f6cd..44e12be1dd 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,8 @@ namespace osu.Game.Input.Bindings
         {
             new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
             new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
-            new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
+            new KeyBinding(InputKey.F10, GlobalAction.ToggleMouseButtons),
+            new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
 
             new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
             new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
@@ -75,6 +76,8 @@ namespace osu.Game.Input.Bindings
         [Description("Quick Retry (Hold)")]
         QuickRetry,
 
+        [Description("Toggle gameplay mouse buttons")]
+        ToggleMouseButtons,
         [Description("Take screenshot")]
         TakeScreenshot
     }
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index d443ed36ae..d2d47a78a6 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -466,6 +466,9 @@ namespace osu.Game
                 case GlobalAction.ToggleDirect:
                     direct.ToggleVisibility();
                     return true;
+                case GlobalAction.ToggleMouseButtons:
+                    LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get<bool>(OsuSetting.MouseDisableButtons));
+                    return true;
             }
 
             return false;
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 3dd088891d..9882ea01f0 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -14,6 +14,7 @@ using osu.Game.Graphics;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Extensions.Color4Extensions;
+using osu.Game.Configuration;
 using osu.Game.Graphics.Sprites;
 
 namespace osu.Game.Overlays
@@ -116,9 +117,10 @@ namespace osu.Game.Overlays
         }
 
         [BackgroundDependencyLoader]
-        private void load(FrameworkConfigManager frameworkConfig)
+        private void load(FrameworkConfigManager frameworkConfig, OsuConfigManager osuConfig)
         {
             BeginTracking(this, frameworkConfig);
+            BeginTracking(this, osuConfig);
         }
 
         private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>();

From 482ae2db87e8cebec289a4b5090282b8a99c4c43 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 2 May 2018 19:42:03 +0900
Subject: [PATCH 017/177] Unify naming

---
 osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++--
 osu.Game/OsuGame.cs                              | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 44e12be1dd..2ae895785c 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Input.Bindings
         {
             new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
             new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
-            new KeyBinding(InputKey.F10, GlobalAction.ToggleMouseButtons),
+            new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons),
             new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
 
             new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
@@ -77,7 +77,7 @@ namespace osu.Game.Input.Bindings
         QuickRetry,
 
         [Description("Toggle gameplay mouse buttons")]
-        ToggleMouseButtons,
+        ToggleGameplayMouseButtons,
         [Description("Take screenshot")]
         TakeScreenshot
     }
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index d2d47a78a6..fe5ca4f278 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -466,7 +466,7 @@ namespace osu.Game
                 case GlobalAction.ToggleDirect:
                     direct.ToggleVisibility();
                     return true;
-                case GlobalAction.ToggleMouseButtons:
+                case GlobalAction.ToggleGameplayMouseButtons:
                     LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get<bool>(OsuSetting.MouseDisableButtons));
                     return true;
             }

From 89db7f81cbd9d1d4b7d452cfc584b259f98751c7 Mon Sep 17 00:00:00 2001
From: Santeri Nogelainen <santeri.nogelainen@gmail.com>
Date: Wed, 2 May 2018 17:11:55 +0300
Subject: [PATCH 018/177] Selecting a mod now triggers on mouseup

---
 osu.Game/Overlays/Mods/ModButton.cs | 21 ++++++++++++---------
 1 file changed, 12 insertions(+), 9 deletions(-)

diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 2a4f243606..3f1541aee3 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -147,18 +147,21 @@ namespace osu.Game.Overlays.Mods
 
         public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex);
 
-        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
         {
-            switch (args.Button)
+            // only trigger the event if we are inside the area of the button
+            if (Contains(ToScreenSpace(state.Mouse.Position - Position)))
             {
-                case MouseButton.Left:
-                    SelectNext(1);
-                    break;
-                case MouseButton.Right:
-                    SelectNext(-1);
-                    break;
+                switch (args.Button)
+                {
+                    case MouseButton.Left:
+                        SelectNext(1);
+                        break;
+                    case MouseButton.Right:
+                        SelectNext(-1);
+                        break;
+                }
             }
-
             return true;
         }
 

From b08b24b6da17260a2a028b6a070e36093429f6a4 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Fri, 4 May 2018 21:18:48 +0300
Subject: [PATCH 019/177] Introduce OsuScreen.AllowBackButton property

---
 osu.Game/Screens/OsuScreen.cs         | 24 ++++++++----------------
 osu.Game/Screens/Play/PlayerLoader.cs |  1 +
 2 files changed, 9 insertions(+), 16 deletions(-)

diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 5d2c46f438..fb5d3d12e6 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Screens
     {
         public BackgroundScreen Background { get; private set; }
 
+        protected virtual bool AllowBackButton => true;
+
         /// <summary>
         /// Override to create a BackgroundMode for the current screen.
         /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause.
@@ -92,26 +94,16 @@ namespace osu.Game.Screens
 
         public bool OnPressed(GlobalAction action)
         {
-            switch (action)
+            if (action == GlobalAction.Back && AllowBackButton)
             {
-                case GlobalAction.Back:
-                    Exit();
-                    return true;
-                default:
-                    return false;
+                Exit();
+                return true;
             }
+
+            return false;
         }
 
-        public bool OnReleased(GlobalAction action)
-        {
-            switch (action)
-            {
-                case GlobalAction.Back:
-                    return true;
-                default:
-                    return false;
-            }
-        }
+        public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
 
         protected override void OnResuming(Screen last)
         {
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 56fbd7b6e7..6eb156914e 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Screens.Play
 
         private bool showOverlays = true;
         public override bool ShowOverlaysOnEnter => showOverlays;
+        protected override bool AllowBackButton => false;
 
         private Task loadTask;
 

From f3aa9269ff3e9b2cdcde32c4c552794af0d41148 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 09:17:12 +0900
Subject: [PATCH 020/177] Fix mania-specific beatmap conversion

---
 osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +-
 osu.Game/Rulesets/Ruleset.cs                              | 3 ++-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 93feddc143..acd289a9e8 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
             var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
 
-            if (beatmap.BeatmapInfo.Ruleset == new ManiaRuleset().RulesetInfo)
+            if (IsForCurrentRuleset)
                 TargetColumns = (int)Math.Max(1, roundedCircleSize);
             else
             {
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 6f117332b2..f4c78fd195 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -118,7 +118,8 @@ namespace osu.Game.Rulesets
             Name = Description,
             ShortName = ShortName,
             InstantiationInfo = GetType().AssemblyQualifiedName,
-            ID = LegacyID
+            ID = LegacyID,
+            Available = true
         };
     }
 }

From 20509b1cc2813a98829a31fa7e3663dc7131e3b4 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 09:59:45 +0900
Subject: [PATCH 021/177] Fix non-IBeatmap usage

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

diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index f6b0bb56c9..efd6c36ce2 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual
             AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any());
         }
 
-        private void selectBeatmap(Beatmap b)
+        private void selectBeatmap(IBeatmap b)
         {
             BeatmapInfoWedge.BufferedWedgeInfo infoBefore = null;
 

From 27c8591d48d911e3884f755daabcf035e29b7e96 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:07:50 +0900
Subject: [PATCH 022/177] Fix reference to old name

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

diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index efd6c36ce2..22945e726c 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual
             });
 
             // select part is redundant, but wait for load isn't
-            selectBeatmap(beatmap.Value.Beatmap);
+            selectBeatmap(beatmap.Value.OriginalBeatmap);
 
             AddWaitStep(3);
 

From 4ecdea8b2eb12282a9360b8048b3fb4cd033275b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:17:54 +0900
Subject: [PATCH 023/177] Remove now-unnecessary IsForCurrentRuleset property
 of RulesetContainer

---
 osu.Game.Rulesets.Catch/CatchRuleset.cs            |  2 +-
 .../UI/CatchRulesetContainer.cs                    |  4 ++--
 osu.Game.Rulesets.Mania/ManiaRuleset.cs            |  2 +-
 osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs |  8 ++++++--
 .../UI/ManiaRulesetContainer.cs                    |  4 ++--
 .../Edit/OsuEditRulesetContainer.cs                |  2 +-
 osu.Game.Rulesets.Osu/OsuRuleset.cs                |  2 +-
 osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs    |  4 ++--
 .../TestCaseTaikoPlayfield.cs                      |  2 +-
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs            |  2 +-
 .../UI/TaikoRulesetContainer.cs                    |  4 ++--
 osu.Game.Tests/Visual/TestCaseReplay.cs            |  2 +-
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs           |  2 +-
 osu.Game/Rulesets/Edit/HitObjectComposer.cs        |  2 +-
 osu.Game/Rulesets/Ruleset.cs                       |  3 +--
 osu.Game/Rulesets/UI/RulesetContainer.cs           | 14 +++-----------
 .../UI/Scrolling/ScrollingRulesetContainer.cs      |  4 ++--
 osu.Game/Screens/Play/Player.cs                    |  4 ++--
 18 files changed, 31 insertions(+), 36 deletions(-)

diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index f091fbbee4..15e51fa126 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch
 {
     public class CatchRuleset : Ruleset
     {
-        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap);
         public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
         public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
 
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 6e8a2c1660..070dc19a6f 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Catch.UI
 {
     public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject>
     {
-        public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
         }
 
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 75b1e38a2f..f1d65f855b 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania
 {
     public class ManiaRuleset : Ruleset
     {
-        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
         public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
 
         public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index dde8acf428..7f3985b26d 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -18,12 +18,16 @@ namespace osu.Game.Rulesets.Mania.Mods
         public override string Description => @"Double the stages, double the fun!";
         public override double ScoreMultiplier => 0;
 
+        private bool isForCurrentRuleset;
+
         public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
         {
             var mbc = (ManiaBeatmapConverter)beatmapConverter;
 
+            isForCurrentRuleset = mbc.IsForCurrentRuleset;
+
             // Although this can work, for now let's not allow keymods for mania-specific beatmaps
-            if (mbc.IsForCurrentRuleset)
+            if (isForCurrentRuleset)
                 return;
 
             mbc.TargetColumns *= 2;
@@ -34,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Mods
             var mrc = (ManiaRulesetContainer)rulesetContainer;
 
             // Although this can work, for now let's not allow keymods for mania-specific beatmaps
-            if (mrc.IsForCurrentRuleset)
+            if (isForCurrentRuleset)
                 return;
 
             var newDefinitions = new List<StageDefinition>();
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 059eaaadcd..7123aab901 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -36,8 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI
 
         public IEnumerable<BarLine> BarLines;
 
-        public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
             // Generate the bar lines
             double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
index 8d4c342740..dce43f5bbb 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit
     public class OsuEditRulesetContainer : OsuRulesetContainer
     {
         public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+            : base(ruleset, beatmap)
         {
         }
 
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 02b887f1b9..ffc421217c 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu
 {
     public class OsuRuleset : Ruleset
     {
-        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new OsuRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap);
         public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
         public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
 
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 603f95dc47..ad1052f86a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.UI
 {
     public class OsuRulesetContainer : RulesetContainer<OsuHitObject>
     {
-        public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
         }
 
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
index aa7318b863..f67726c022 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
                 RelativeSizeAxes = Axes.X,
                 Height = 768,
                 Clock = new FramedClock(rateAdjustClock),
-                Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) }
+                Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap) }
             });
         }
 
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 2e27b34e69..102de5717f 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko
 {
     public class TaikoRuleset : Ruleset
     {
-        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset);
+        public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap);
         public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
 
         public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 0a84d74c27..313c205981 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI
 {
     public class TaikoRulesetContainer : ScrollingRulesetContainer<TaikoPlayfield, TaikoHitObject>
     {
-        public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
         }
 
diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs
index 6ba671c7fc..5bc16fe420 100644
--- a/osu.Game.Tests/Visual/TestCaseReplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplay.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual
             // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here
             // to simulate setting a replay rather than having the replay already set for us
             beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
-            var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo));
+            var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap);
 
             // We have the replay
             var replay = dummyRulesetContainer.Replay;
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 23ddc5a976..956034a279 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
             {
                 public override IEnumerable<Mod> GetModsFor(ModType type) => new Mod[] { };
 
-                public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset)
+                public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap)
                 {
                     throw new NotImplementedException();
                 }
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 1820053d3d..5f1b9a6bad 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Edit
 
         private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool;
 
-        protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true);
+        protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap);
 
         protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; }
 
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index f4c78fd195..d6d1d19628 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -52,10 +52,9 @@ namespace osu.Game.Rulesets
         /// Attempt to create a hit renderer for a beatmap
         /// </summary>
         /// <param name="beatmap">The beatmap to create the hit renderer for.</param>
-        /// <param name="isForCurrentRuleset">Whether the hit renderer should assume the beatmap is for the current ruleset.</param>
         /// <exception cref="BeatmapInvalidForRulesetException">Unable to successfully load the beatmap to be usable with this ruleset.</exception>
         /// <returns></returns>
-        public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset);
+        public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap);
 
         public abstract IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap);
 
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index 074380da56..3a2e66505a 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -190,11 +190,6 @@ namespace osu.Game.Rulesets.UI
         /// </summary>
         protected readonly WorkingBeatmap WorkingBeatmap;
 
-        /// <summary>
-        /// Whether the specified beatmap is assumed to be specific to the current ruleset.
-        /// </summary>
-        public readonly bool IsForCurrentRuleset;
-
         public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor<TObject>(this);
 
         protected override Container<Drawable> Content => content;
@@ -206,14 +201,12 @@ namespace osu.Game.Rulesets.UI
         /// </summary>
         /// <param name="ruleset">The ruleset being repesented.</param>
         /// <param name="workingBeatmap">The beatmap to create the hit renderer for.</param>
-        /// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
-        protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset)
+        protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap)
             : base(ruleset)
         {
             Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap.");
 
             WorkingBeatmap = workingBeatmap;
-            IsForCurrentRuleset = isForCurrentRuleset;
             // ReSharper disable once PossibleNullReferenceException
             Mods = workingBeatmap.Mods.Value;
 
@@ -337,9 +330,8 @@ namespace osu.Game.Rulesets.UI
         /// </summary>
         /// <param name="ruleset">The ruleset being repesented.</param>
         /// <param name="beatmap">The beatmap to create the hit renderer for.</param>
-        /// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
-        protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
         }
     }
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
index 79f9eaa9e9..efd901240a 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
@@ -30,8 +30,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
         /// <returns></returns>
         protected readonly SortedList<MultiplierControlPoint> DefaultControlPoints = new SortedList<MultiplierControlPoint>(Comparer<MultiplierControlPoint>.Default);
 
-        protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
-            : base(ruleset, beatmap, isForCurrentRuleset)
+        protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
+            : base(ruleset, beatmap)
         {
         }
 
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index d5edff3a0c..d39b28bf25 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Screens.Play
 
                 try
                 {
-                    RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID);
+                    RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working);
                 }
                 catch (BeatmapInvalidForRulesetException)
                 {
@@ -115,7 +115,7 @@ namespace osu.Game.Screens.Play
                     // let's try again forcing the beatmap's ruleset.
                     ruleset = beatmap.BeatmapInfo.Ruleset;
                     rulesetInstance = ruleset.CreateInstance();
-                    RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true);
+                    RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap);
                 }
 
                 if (!RulesetContainer.Objects.Any())

From b68ee39136bc5ac8359a96db294b509066a4ad71 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:18:47 +0900
Subject: [PATCH 024/177] Reword beatmap conversion error

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

diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 7d6c23dad0..2afaaf13f9 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps
 
             // Check if the beatmap can be converted
             if (!converter.CanConvert)
-                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
+                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the ruleset (ruleset: {ruleset}, converter: {converter}).");
 
             // Apply conversion mods
             foreach (var mod in Mods.Value.OfType<IApplicableToBeatmapConverter>())

From 7e83c75888a0b7f0ef04cc062f5f2174512cabcc Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:23:32 +0900
Subject: [PATCH 025/177] Add xmldoc

---
 osu.Game/Beatmaps/WorkingBeatmap.cs | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 2afaaf13f9..ae83a14d61 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -79,6 +79,13 @@ namespace osu.Game.Beatmaps
             return b;
         }
 
+        /// <summary>
+        /// Retrieves the resulting <see cref="IBeatmap"/> from the conversion of <see cref="OriginalBeatmap"/> to a specific <see cref="RulesetInfo"/>.
+        /// All mods have been applied to the returned <see cref="IBeatmap"/>.
+        /// </summary>
+        /// <param name="ruleset">The <see cref="RulesetInfo"/> to convert <see cref="OriginalBeatmap"/> to.</param>
+        /// <returns>The converted <see cref="IBeatmap"/>.</returns>
+        /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="OriginalBeatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
         public IBeatmap GetBeatmap(RulesetInfo ruleset)
         {
             var rulesetInstance = ruleset.CreateInstance();

From 9128e64c9a907e5cf2990115e305c0966a3ac28e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:29:38 +0900
Subject: [PATCH 026/177] Rename OriginalBeatmap back to Beatmap

---
 osu.Game.Rulesets.Osu/OsuRuleset.cs           |  2 +-
 .../Beatmaps/IO/ImportBeatmapTest.cs          |  8 ++---
 .../Visual/TestCaseBeatSyncedContainer.cs     |  2 +-
 .../Visual/TestCaseBeatmapInfoWedge.cs        |  2 +-
 .../Beatmaps/BeatmapManager_WorkingBeatmap.cs |  2 +-
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs      |  2 +-
 osu.Game/Beatmaps/WorkingBeatmap.cs           | 30 +++++++++----------
 .../Containers/BeatSyncedContainer.cs         |  2 +-
 osu.Game/Overlays/MusicController.cs          |  2 +-
 .../Scoring/Legacy/LegacyScoreParser.cs       |  2 +-
 .../Timelines/Summary/Parts/BreakPart.cs      |  2 +-
 .../Summary/Parts/ControlPointPart.cs         |  2 +-
 osu.Game/Screens/Edit/Editor.cs               |  2 +-
 osu.Game/Screens/Menu/LogoVisualisation.cs    |  2 +-
 osu.Game/Screens/Play/Player.cs               |  2 +-
 osu.Game/Screens/Ranking/ResultsPageScore.cs  |  2 +-
 osu.Game/Screens/Select/BeatmapInfoWedge.cs   |  2 +-
 osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs |  2 +-
 osu.Game/Tests/Visual/EditorClockTestCase.cs  |  2 +-
 .../Tests/Visual/TestCasePerformancePoints.cs |  4 +--
 20 files changed, 38 insertions(+), 38 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index ffc421217c..927ffa2146 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu
 
         public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap)
         {
-            IEnumerable<HitObject> hitObjects = beatmap.OriginalBeatmap.HitObjects;
+            IEnumerable<HitObject> hitObjects = beatmap.Beatmap.HitObjects;
             IEnumerable<HitObject> circles = hitObjects.Where(c => !(c is IHasEndTime));
             IEnumerable<HitObject> sliders = hitObjects.Where(s => s is IHasCurve);
             IEnumerable<HitObject> spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 3530ae928d..f60caf2397 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -274,13 +274,13 @@ namespace osu.Game.Tests.Beatmaps.IO
             foreach (BeatmapInfo b in set.Beatmaps)
                 Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
             Assert.IsTrue(set.Beatmaps.Count > 0);
-            var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.OriginalBeatmap;
+            var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.OriginalBeatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.OriginalBeatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
-            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.OriginalBeatmap;
+            beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
             Assert.IsTrue(beatmap?.HitObjects.Any() == true);
         }
 
diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
index 4e3462b5ea..596b7839e0 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
@@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual
                 };
             }
 
-            private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.OriginalBeatmap.ControlPointInfo.TimingPoints;
+            private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints;
             private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
             {
                 if (timingPoints[timingPoints.Count - 1] == current)
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index 22945e726c..efd6c36ce2 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual
             });
 
             // select part is redundant, but wait for load isn't
-            selectBeatmap(beatmap.Value.OriginalBeatmap);
+            selectBeatmap(beatmap.Value.Beatmap);
 
             AddWaitStep(3);
 
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index cff500fabf..71406c6034 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
                 this.audioManager = audioManager;
             }
 
-            protected override IBeatmap GetOriginalBeatmap()
+            protected override IBeatmap GetBeatmap()
             {
                 try
                 {
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 956034a279..da52dc7284 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps
             this.game = game;
         }
 
-        protected override IBeatmap GetOriginalBeatmap() => new Beatmap();
+        protected override IBeatmap GetBeatmap() => new Beatmap();
 
         protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
 
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index ae83a14d61..e90df687e4 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Beatmaps
 
             Mods.ValueChanged += mods => applyRateAdjustments();
 
-            originalBeatmap = new AsyncLazy<IBeatmap>(populateOriginalBeatmap);
+            beatmap = new AsyncLazy<IBeatmap>(populateBeatmap);
             background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
             track = new AsyncLazy<Track>(populateTrack);
             waveform = new AsyncLazy<Waveform>(populateWaveform);
@@ -47,31 +47,31 @@ namespace osu.Game.Beatmaps
         }
 
         /// <summary>
-        /// Saves the <see cref="Beatmap"/>.
+        /// Saves the <see cref="Beatmaps.Beatmap"/>.
         /// </summary>
         public void Save()
         {
             var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
             using (var sw = new StreamWriter(path))
-                sw.WriteLine(OriginalBeatmap.Serialize());
+                sw.WriteLine(Beatmap.Serialize());
             Process.Start(path);
         }
 
-        protected abstract IBeatmap GetOriginalBeatmap();
+        protected abstract IBeatmap GetBeatmap();
         protected abstract Texture GetBackground();
         protected abstract Track GetTrack();
         protected virtual Skin GetSkin() => new DefaultSkin();
         protected virtual Waveform GetWaveform() => new Waveform();
         protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
 
-        public bool BeatmapLoaded => originalBeatmap.IsResultAvailable;
-        public IBeatmap OriginalBeatmap => originalBeatmap.Value.Result;
-        public async Task<IBeatmap> GetOriginalBeatmapAsync() => await originalBeatmap.Value;
-        private readonly AsyncLazy<IBeatmap> originalBeatmap;
+        public bool BeatmapLoaded => beatmap.IsResultAvailable;
+        public IBeatmap Beatmap => beatmap.Value.Result;
+        public async Task<IBeatmap> GetBeatmapAsync() => await beatmap.Value;
+        private readonly AsyncLazy<IBeatmap> beatmap;
 
-        private IBeatmap populateOriginalBeatmap()
+        private IBeatmap populateBeatmap()
         {
-            var b = GetOriginalBeatmap() ?? new Beatmap();
+            var b = GetBeatmap() ?? new Beatmap();
 
             // use the database-backed info.
             b.BeatmapInfo = BeatmapInfo;
@@ -80,21 +80,21 @@ namespace osu.Game.Beatmaps
         }
 
         /// <summary>
-        /// Retrieves the resulting <see cref="IBeatmap"/> from the conversion of <see cref="OriginalBeatmap"/> to a specific <see cref="RulesetInfo"/>.
+        /// Retrieves the resulting <see cref="IBeatmap"/> from the conversion of <see cref="Beatmap"/> to a specific <see cref="RulesetInfo"/>.
         /// All mods have been applied to the returned <see cref="IBeatmap"/>.
         /// </summary>
-        /// <param name="ruleset">The <see cref="RulesetInfo"/> to convert <see cref="OriginalBeatmap"/> to.</param>
+        /// <param name="ruleset">The <see cref="RulesetInfo"/> to convert <see cref="Beatmap"/> to.</param>
         /// <returns>The converted <see cref="IBeatmap"/>.</returns>
-        /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="OriginalBeatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
+        /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
         public IBeatmap GetBeatmap(RulesetInfo ruleset)
         {
             var rulesetInstance = ruleset.CreateInstance();
 
-            IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(OriginalBeatmap);
+            IBeatmapConverter converter = rulesetInstance.CreateBeatmapConverter(Beatmap);
 
             // Check if the beatmap can be converted
             if (!converter.CanConvert)
-                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the ruleset (ruleset: {ruleset}, converter: {converter}).");
+                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset}, converter: {converter}).");
 
             // Apply conversion mods
             foreach (var mod in Mods.Value.OfType<IApplicableToBeatmapConverter>())
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 8d8717a612..bf16af4706 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Graphics.Containers
             if (!Beatmap.Value.TrackLoaded || !Beatmap.Value.BeatmapLoaded) return;
 
             var track = Beatmap.Value.Track;
-            var beatmap = Beatmap.Value.OriginalBeatmap;
+            var beatmap = Beatmap.Value.Beatmap;
 
             if (track == null || beatmap == null)
                 return;
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index eb88fe0eb4..b4021f2808 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -343,7 +343,7 @@ namespace osu.Game.Overlays
                 // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
                 Task.Run(() =>
                 {
-                    if (beatmap?.OriginalBeatmap == null) //this is not needed if a placeholder exists
+                    if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
                     {
                         title.Current = null;
                         title.Text = @"Nothing to play";
diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index 3145561001..d5ab856697 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Scoring.Legacy
                 /* score.FileChecksum = */
                 var beatmapHash = sr.ReadString();
                 score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
-                currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).OriginalBeatmap;
+                currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
 
                 /* score.PlayerName = */
                 score.User = new User { Username = sr.ReadString() };
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
index e081897339..1146037004 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
         protected override void LoadBeatmap(WorkingBeatmap beatmap)
         {
             base.LoadBeatmap(beatmap);
-            foreach (var breakPeriod in beatmap.OriginalBeatmap.Breaks)
+            foreach (var breakPeriod in beatmap.Beatmap.Breaks)
                 Add(new BreakVisualisation(breakPeriod));
         }
 
diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
index faf090bfd9..4bef22463e 100644
--- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
+++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
         {
             base.LoadBeatmap(beatmap);
 
-            ControlPointInfo cpi = beatmap.OriginalBeatmap.ControlPointInfo;
+            ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo;
 
             cpi.TimingPoints.ForEach(addTimingPoint);
 
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index a9324e3fb0..adb749b492 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit
         {
             // TODO: should probably be done at a RulesetContainer level to share logic with Player.
             var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
-            clock = new EditorClock(Beatmap.Value.OriginalBeatmap.ControlPointInfo, beatDivisor) { IsCoupled = false };
+            clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false };
             clock.ChangeSource(sourceClock);
 
             dependencies.CacheAs<IFrameBasedClock>(clock);
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index bbd50d8f1b..1f2cb915b3 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -87,7 +87,7 @@ namespace osu.Game.Screens.Menu
         private void updateAmplitudes()
         {
             var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
-            var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.OriginalBeatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
+            var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
 
             float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes ?? new float[256];
 
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index d39b28bf25..f397d0c3d4 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Screens.Play
 
             try
             {
-                beatmap = working.OriginalBeatmap;
+                beatmap = working.Beatmap;
 
                 if (beatmap == null)
                     throw new InvalidOperationException("Beatmap was not loaded");
diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs
index 862f77d219..42d8af07b9 100644
--- a/osu.Game/Screens/Ranking/ResultsPageScore.cs
+++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Screens.Ranking
                                 {
                                     RelativeSizeAxes = Axes.Both,
                                     Alpha = 0.5f,
-                                    Objects = Beatmap.OriginalBeatmap.HitObjects,
+                                    Objects = Beatmap.Beatmap.HitObjects,
                                 },
                                 scoreCounter = new SlowScoreCounter(6)
                                 {
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 2dad5a9008..da82a49f51 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -210,7 +210,7 @@ namespace osu.Game.Screens.Select
 
             private InfoLabel[] getInfoLabels()
             {
-                var beatmap = working.OriginalBeatmap;
+                var beatmap = working.Beatmap;
                 var info = working.BeatmapInfo;
 
                 List<InfoLabel> labels = new List<InfoLabel>();
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 71893cfe37..37693c99e8 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         private readonly IBeatmap beatmap;
-        protected override IBeatmap GetOriginalBeatmap() => beatmap;
+        protected override IBeatmap GetBeatmap() => beatmap;
         protected override Texture GetBackground() => null;
 
         protected override Track GetTrack()
diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs
index c6a98a4f43..43b20f7021 100644
--- a/osu.Game/Tests/Visual/EditorClockTestCase.cs
+++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs
@@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
 
         private void beatmapChanged(WorkingBeatmap working)
         {
-            Clock.ControlPointInfo = working.OriginalBeatmap.ControlPointInfo;
+            Clock.ControlPointInfo = working.Beatmap.ControlPointInfo;
             Clock.ChangeSource((IAdjustableClock)working.Track ?? new StopwatchClock());
             Clock.ProcessFrame();
         }
diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
index 5d78b95c04..51460ecb6d 100644
--- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
+++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs
@@ -250,7 +250,7 @@ namespace osu.Game.Tests.Visual
                     return;
 
                 lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset);
-                lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.OriginalBeatmap)));
+                lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
                 api.Queue(lastRequest);
             }
 
@@ -381,7 +381,7 @@ namespace osu.Game.Tests.Visual
                     var allMods = ruleset.GetAllMods().ToList();
                     Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
 
-                    var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.OriginalBeatmap, activeMods);
+                    var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
                     if (diffCalc != null)
                     {
                         var categories = new Dictionary<string, double>();

From db3e48c194ed7a0838b3390dab7152cf37ecde84 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:40:30 +0900
Subject: [PATCH 027/177] Rename GetBeatmap() to GetPlayableBeatmap()

---
 .../Beatmaps/BeatmapManager_WorkingBeatmap.cs     |  2 +-
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs          |  2 +-
 osu.Game/Beatmaps/WorkingBeatmap.cs               | 15 +++++++++------
 osu.Game/Rulesets/UI/RulesetContainer.cs          |  2 +-
 osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs     |  2 +-
 5 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 71406c6034..400c877d6a 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
                 this.audioManager = audioManager;
             }
 
-            protected override IBeatmap GetBeatmap()
+            protected override IBeatmap GetPlayableBeatmap()
             {
                 try
                 {
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index da52dc7284..2c9d83fe9e 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps
             this.game = game;
         }
 
-        protected override IBeatmap GetBeatmap() => new Beatmap();
+        protected override IBeatmap GetPlayableBeatmap() => new Beatmap();
 
         protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
 
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index e90df687e4..515b7c2051 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps
             Process.Start(path);
         }
 
-        protected abstract IBeatmap GetBeatmap();
+        protected abstract IBeatmap GetPlayableBeatmap();
         protected abstract Texture GetBackground();
         protected abstract Track GetTrack();
         protected virtual Skin GetSkin() => new DefaultSkin();
@@ -71,7 +71,7 @@ namespace osu.Game.Beatmaps
 
         private IBeatmap populateBeatmap()
         {
-            var b = GetBeatmap() ?? new Beatmap();
+            var b = GetPlayableBeatmap() ?? new Beatmap();
 
             // use the database-backed info.
             b.BeatmapInfo = BeatmapInfo;
@@ -80,13 +80,16 @@ namespace osu.Game.Beatmaps
         }
 
         /// <summary>
-        /// Retrieves the resulting <see cref="IBeatmap"/> from the conversion of <see cref="Beatmap"/> to a specific <see cref="RulesetInfo"/>.
-        /// All mods have been applied to the returned <see cref="IBeatmap"/>.
+        /// Constructs a playable <see cref="IBeatmap"/> from <see cref="Beatmap"/> using the applicable converters for a specific <see cref="RulesetInfo"/>.
+        /// <para>
+        /// The returned <see cref="IBeatmap"/> is in a playable state - all <see cref="HitObject"/> and <see cref="BeatmapDifficulty"/> <see cref="Mod"/>s
+        /// have been applied, and <see cref="HitObject"/>s have been fully constructed.
+        /// </para>
         /// </summary>
-        /// <param name="ruleset">The <see cref="RulesetInfo"/> to convert <see cref="Beatmap"/> to.</param>
+        /// <param name="ruleset">The <see cref="RulesetInfo"/> to create a playable <see cref="IBeatmap"/> for.</param>
         /// <returns>The converted <see cref="IBeatmap"/>.</returns>
         /// <exception cref="BeatmapInvalidForRulesetException">If <see cref="Beatmap"/> could not be converted to <paramref name="ruleset"/>.</exception>
-        public IBeatmap GetBeatmap(RulesetInfo ruleset)
+        public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset)
         {
             var rulesetInstance = ruleset.CreateInstance();
 
diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs
index 3a2e66505a..e42e74c245 100644
--- a/osu.Game/Rulesets/UI/RulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/RulesetContainer.cs
@@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.UI
 
             RelativeSizeAxes = Axes.Both;
 
-            Beatmap = (Beatmap<TObject>)workingBeatmap.GetBeatmap(ruleset.RulesetInfo);
+            Beatmap = (Beatmap<TObject>)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
 
             KeyBindingInputManager = CreateInputManager();
             KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index 37693c99e8..fbbe7d5284 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         private readonly IBeatmap beatmap;
-        protected override IBeatmap GetBeatmap() => beatmap;
+        protected override IBeatmap GetPlayableBeatmap() => beatmap;
         protected override Texture GetBackground() => null;
 
         protected override Track GetTrack()

From 794418432f53b9be47e3495307d0c6fd5a8f4345 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:22:25 +0900
Subject: [PATCH 028/177] Fix incorrect method name

---
 osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +-
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs           | 2 +-
 osu.Game/Beatmaps/WorkingBeatmap.cs                | 4 ++--
 osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs      | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 400c877d6a..71406c6034 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps
                 this.audioManager = audioManager;
             }
 
-            protected override IBeatmap GetPlayableBeatmap()
+            protected override IBeatmap GetBeatmap()
             {
                 try
                 {
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 2c9d83fe9e..da52dc7284 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps
             this.game = game;
         }
 
-        protected override IBeatmap GetPlayableBeatmap() => new Beatmap();
+        protected override IBeatmap GetBeatmap() => new Beatmap();
 
         protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4");
 
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 515b7c2051..2a3eb05544 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps
             Process.Start(path);
         }
 
-        protected abstract IBeatmap GetPlayableBeatmap();
+        protected abstract IBeatmap GetBeatmap();
         protected abstract Texture GetBackground();
         protected abstract Track GetTrack();
         protected virtual Skin GetSkin() => new DefaultSkin();
@@ -71,7 +71,7 @@ namespace osu.Game.Beatmaps
 
         private IBeatmap populateBeatmap()
         {
-            var b = GetPlayableBeatmap() ?? new Beatmap();
+            var b = GetBeatmap() ?? new Beatmap();
 
             // use the database-backed info.
             b.BeatmapInfo = BeatmapInfo;
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index fbbe7d5284..37693c99e8 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         private readonly IBeatmap beatmap;
-        protected override IBeatmap GetPlayableBeatmap() => beatmap;
+        protected override IBeatmap GetBeatmap() => beatmap;
         protected override Texture GetBackground() => null;
 
         protected override Track GetTrack()

From e20323b5f6408a3bc3e5ba230a7a6e14eec7b18d Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:23:29 +0900
Subject: [PATCH 029/177] Trim whitespace

---
 osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs | 2 +-
 osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 3b22c47ac8..4a0b120e47 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
             : base(beatmap)
         {
         }
-        
+
         protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
 
         protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index acd289a9e8..c8a7402904 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
             : base(beatmap)
         {
             IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
-            
+
             var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
             var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
 

From 268c14813b826f706de133e96ab6f4129030e01c Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:38:41 +0900
Subject: [PATCH 030/177] Fix resharper warnings

---
 osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs | 5 +----
 osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs       | 2 +-
 osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs          | 2 +-
 3 files changed, 3 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index e52fc11518..cf79feb16b 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -18,13 +18,10 @@ namespace osu.Game.Rulesets.Mania.Tests
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
 
-        private bool isForCurrentRuleset;
-
         [NonParallelizable]
         [TestCase("basic", false)]
-        public void Test(string name, bool isForCurrentRuleset)
+        public new void Test(string name)
         {
-            this.isForCurrentRuleset = isForCurrentRuleset;
             base.Test(name);
         }
 
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
index dce43f5bbb..ea33ec9ae0 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
 {
     public class OsuEditRulesetContainer : OsuRulesetContainer
     {
-        public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
+        public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
             : base(ruleset, beatmap)
         {
         }
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 7bf0651443..dce1fc2851 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit
         {
         }
 
-        protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap, true);
+        protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => new OsuEditRulesetContainer(ruleset, beatmap);
 
         protected override IReadOnlyList<ICompositionTool> CompositionTools => new ICompositionTool[]
         {

From ad2c2a5fe6a3dbc17e1d2267506796ecc2f2f714 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 12:25:21 +0900
Subject: [PATCH 031/177] Restore star difficulty calculation

---
 osu.Game/Beatmaps/BeatmapManager.cs | 31 +++++++++++++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 14436fce13..36fde8a319 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -9,7 +9,9 @@ using System.Linq.Expressions;
 using System.Threading.Tasks;
 using Microsoft.EntityFrameworkCore;
 using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
 using osu.Framework.Extensions;
+using osu.Framework.Graphics.Textures;
 using osu.Framework.Logging;
 using osu.Framework.Platform;
 using osu.Game.Beatmaps.Formats;
@@ -341,9 +343,16 @@ namespace osu.Game.Beatmaps
 
                     RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
 
-                    // TODO: this should be done in a better place once we actually need to dynamically update it.
                     beatmap.BeatmapInfo.Ruleset = ruleset;
-                    beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
+
+                    if (ruleset != null)
+                    {
+                        // TODO: this should be done in a better place once we actually need to dynamically update it.
+                        var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset);
+                        beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
+                    }
+                    else
+                        beatmap.BeatmapInfo.StarDifficulty = 0;
 
                     beatmapInfos.Add(beatmap.BeatmapInfo);
                 }
@@ -351,5 +360,23 @@ namespace osu.Game.Beatmaps
 
             return beatmapInfos;
         }
+
+        /// <summary>
+        /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
+        /// </summary>
+        private class DummyConversionBeatmap : WorkingBeatmap
+        {
+            private readonly IBeatmap beatmap;
+
+            public DummyConversionBeatmap(IBeatmap beatmap)
+                : base(beatmap.BeatmapInfo)
+            {
+                this.beatmap = beatmap;
+            }
+
+            protected override IBeatmap GetBeatmap() => beatmap;
+            protected override Texture GetBackground() => null;
+            protected override Track GetTrack() => null;
+        }
     }
 }

From 7ee8228a37d31b8b3803328a38a5601c5aae08ec Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 13:28:01 +0900
Subject: [PATCH 032/177] Fix testcases

---
 osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs | 2 +-
 osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs     | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index cf79feb16b..95a23f65c5 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Tests
         protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
 
         [NonParallelizable]
-        [TestCase("basic", false)]
+        [TestCase("basic")]
         public new void Test(string name)
         {
             base.Test(name);
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
index f67726c022..1bf24a46bc 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
         private Container playfieldContainer;
 
         [BackgroundDependencyLoader]
-        private void load(RulesetStore rulesets)
+        private void load()
         {
             AddStep("Hit!", () => addHitJudgement(false));
             AddStep("Kiai hit", () => addHitJudgement(true));
@@ -73,6 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
                         Title = @"Sample Beatmap",
                         AuthorString = @"peppy",
                     },
+                    Ruleset = new TaikoRuleset().RulesetInfo
                 },
                 ControlPointInfo = controlPointInfo
             });
@@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
                 RelativeSizeAxes = Axes.X,
                 Height = 768,
                 Clock = new FramedClock(rateAdjustClock),
-                Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap) }
+                Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) }
             });
         }
 

From 6867886f4c6bda3e43000c023b8fef7eccd80ec2 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 14:04:37 +0900
Subject: [PATCH 033/177] Give conversion-tested beatmaps a ruleset

---
 .../CatchBeatmapConversionTest.cs                          | 6 +++++-
 .../ManiaBeatmapConversionTest.cs                          | 6 +++++-
 osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs    | 6 +++++-
 .../TaikoBeatmapConversionTest.cs                          | 6 +++++-
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs           | 7 ++++++-
 5 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index a94d29ca2c..d0d623178e 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Catch.Tests
 {
-    public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    public class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
 
@@ -64,4 +64,8 @@ namespace osu.Game.Rulesets.Catch.Tests
             => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
                && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
     }
+
+    public class TestCatchRuleset : CatchRuleset
+    {
+    }
 }
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 95a23f65c5..f1ee874b88 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Mania.Tests
 {
-    public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    public class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
 
@@ -54,4 +54,8 @@ namespace osu.Game.Rulesets.Mania.Tests
                && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
                && Column == other.Column;
     }
+
+    public class TestManiaRuleset : ManiaRuleset
+    {
+    }
 }
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index a8f82a112b..aa7de4ed01 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -15,7 +15,7 @@ using OpenTK;
 
 namespace osu.Game.Rulesets.Osu.Tests
 {
-    public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    public class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
 
@@ -67,4 +67,8 @@ namespace osu.Game.Rulesets.Osu.Tests
                && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
                && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
     }
+
+    public class TestOsuRuleset : OsuRuleset
+    {
+    }
 }
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index cbab53fa75..33a5e1772e 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Taiko.Tests
 {
-    public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
+    public class TaikoBeatmapConversionTest : BeatmapConversionTest<TestTaikoRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
 
@@ -67,4 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
                && IsSwell == other.IsSwell
                && IsStrong == other.IsStrong;
     }
+
+    public class TestTaikoRuleset : TaikoRuleset
+    {
+    }
 }
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 735c0ef76c..2de38d49a9 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -10,12 +10,14 @@ using NUnit.Framework;
 using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Formats;
+using osu.Game.Rulesets;
 using osu.Game.Rulesets.Objects;
 
 namespace osu.Game.Tests.Beatmaps
 {
     [TestFixture]
-    public abstract class BeatmapConversionTest<TConvertValue>
+    public abstract class BeatmapConversionTest<TRuleset, TConvertValue>
+        where TRuleset : Ruleset, new()
         where TConvertValue : IEquatable<TConvertValue>
     {
         private const string resource_namespace = "Testing.Beatmaps";
@@ -79,6 +81,9 @@ namespace osu.Game.Tests.Beatmaps
         {
             var beatmap = getBeatmap(name);
 
+            var rulesetInstance = new TRuleset();
+            beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
+
             var result = new ConvertResult();
 
             var converter = CreateConverter(beatmap);

From 68441f1ef0b7770d3c3754f59024b184bd676a68 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 14:28:30 +0900
Subject: [PATCH 034/177] Output ruleset instantiation info

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

diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 2a3eb05544..9c389bbb8f 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Beatmaps
 
             // Check if the beatmap can be converted
             if (!converter.CanConvert)
-                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset}, converter: {converter}).");
+                throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
 
             // Apply conversion mods
             foreach (var mod in Mods.Value.OfType<IApplicableToBeatmapConverter>())

From 30a3f4f29f2ab4bbd6d6c260382cded231d8b490 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 15:27:45 +0900
Subject: [PATCH 035/177] Fix not being able to convert beatmaps with 0
 hitobjects

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

diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 263a4c36ee..b7a454460f 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps
         /// <summary>
         /// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>.
         /// </summary>
-        public bool CanConvert => ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
+        public bool CanConvert => !Beatmap.HitObjects.Any() || ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType));
 
         /// <summary>
         /// Converts <see cref="Beatmap"/>.

From 9f1efeb19df1d3a9b1c43771ec28c36f0c0ba473 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 15:41:14 +0900
Subject: [PATCH 036/177] Move keybind to last in enum

---
 osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 2ae895785c..1658bb4e79 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -76,9 +76,9 @@ namespace osu.Game.Input.Bindings
         [Description("Quick Retry (Hold)")]
         QuickRetry,
 
+        [Description("Take screenshot")]
+        TakeScreenshot,
         [Description("Toggle gameplay mouse buttons")]
         ToggleGameplayMouseButtons,
-        [Description("Take screenshot")]
-        TakeScreenshot
     }
 }

From 5489976c20cf5d7e15600c8d2f2a53e93f5c3af7 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:51:17 +0900
Subject: [PATCH 037/177] Implement ruleset-specific beatmap statistics

---
 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs  | 45 +++++++++++++++++++
 .../Beatmaps/OsuBeatmapConverter.cs           |  2 +
 osu.Game.Rulesets.Osu/OsuRuleset.cs           | 33 --------------
 osu.Game/Beatmaps/Beatmap.cs                  |  8 ++++
 osu.Game/Rulesets/Ruleset.cs                  |  2 -
 osu.Game/Screens/Select/BeatmapInfoWedge.cs   |  4 +-
 6 files changed, 57 insertions(+), 37 deletions(-)
 create mode 100644 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
new file mode 100644
index 0000000000..68002f73b5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
@@ -0,0 +1,45 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Beatmaps
+{
+    public class OsuBeatmap : Beatmap<OsuHitObject>
+    {
+        public override IEnumerable<BeatmapStatistic> GetStatistics()
+        {
+            IEnumerable<HitObject> circles = HitObjects.Where(c => !(c is IHasEndTime));
+            IEnumerable<HitObject> sliders = HitObjects.Where(s => s is IHasCurve);
+            IEnumerable<HitObject> spinners = HitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
+
+            return new[]
+            {
+                new BeatmapStatistic
+                {
+                    Name = @"Circle Count",
+                    Content = circles.Count().ToString(),
+                    Icon = FontAwesome.fa_circle_o
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Slider Count",
+                    Content = sliders.Count().ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Spinner Count",
+                    Content = spinners.Count().ToString(),
+                    Icon = FontAwesome.fa_circle
+                }
+            };
+        }
+    }
+}
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 1cd4ec5668..5f3a909488 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -65,5 +65,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                 };
             }
         }
+
+        protected override Beatmap<OsuHitObject> CreateBeatmap() => new OsuBeatmap();
     }
 }
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 927ffa2146..69a54fb533 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.OsuDifficulty;
 using osu.Game.Rulesets.Osu.UI;
 using osu.Game.Rulesets.UI;
 using System.Collections.Generic;
-using System.Linq;
 using osu.Framework.Graphics;
 using osu.Game.Overlays.Settings;
 using osu.Framework.Input.Bindings;
@@ -17,8 +16,6 @@ using osu.Game.Rulesets.Scoring;
 using osu.Game.Rulesets.Osu.Scoring;
 using osu.Game.Rulesets.Osu.Edit;
 using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Osu.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
@@ -40,36 +37,6 @@ namespace osu.Game.Rulesets.Osu
             new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
         };
 
-        public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap)
-        {
-            IEnumerable<HitObject> hitObjects = beatmap.Beatmap.HitObjects;
-            IEnumerable<HitObject> circles = hitObjects.Where(c => !(c is IHasEndTime));
-            IEnumerable<HitObject> sliders = hitObjects.Where(s => s is IHasCurve);
-            IEnumerable<HitObject> spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
-
-            return new[]
-            {
-                new BeatmapStatistic
-                {
-                    Name = @"Circle Count",
-                    Content = circles.Count().ToString(),
-                    Icon = FontAwesome.fa_circle_o
-                },
-                new BeatmapStatistic
-                {
-                    Name = @"Slider Count",
-                    Content = sliders.Count().ToString(),
-                    Icon = FontAwesome.fa_circle
-                },
-                new BeatmapStatistic
-                {
-                    Name = @"Spinner Count",
-                    Content = spinners.Count().ToString(),
-                    Icon = FontAwesome.fa_circle
-                }
-            };
-        }
-
         public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
         {
             if (mods.HasFlag(LegacyMods.Nightcore))
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 6a7d2690ff..e323dd0241 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -44,6 +44,12 @@ namespace osu.Game.Beatmaps
         /// </summary>
         IEnumerable<HitObject> HitObjects { get; }
 
+        /// <summary>
+        /// Returns statistics of the <see cref="HitObjects"/> contained in this beatmap.
+        /// </summary>
+        /// <returns></returns>
+        IEnumerable<BeatmapStatistic> GetStatistics();
+
         /// <summary>
         /// Creates a shallow-clone of this beatmap and returns it.
         /// </summary>
@@ -90,6 +96,8 @@ namespace osu.Game.Beatmaps
 
         IEnumerable<HitObject> IBeatmap.HitObjects => HitObjects;
 
+        public virtual IEnumerable<BeatmapStatistic> GetStatistics() => Enumerable.Empty<BeatmapStatistic>();
+
         IBeatmap IBeatmap.Clone() => Clone();
 
         public Beatmap<T> Clone()
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index d6d1d19628..6883d319f4 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -22,8 +22,6 @@ namespace osu.Game.Rulesets
     {
         public readonly RulesetInfo RulesetInfo;
 
-        public virtual IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { };
-
         public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
                                                     // Confine all mods of each mod type into a single IEnumerable<Mod>
                                                     .SelectMany(GetModsFor)
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index da82a49f51..c0370763c0 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -234,8 +234,8 @@ namespace osu.Game.Screens.Select
                         Content = getBPMRange(beatmap),
                     }));
 
-                    //get statistics from the current ruleset.
-                    labels.AddRange(info.Ruleset.CreateInstance().GetBeatmapStatistics(working).Select(s => new InfoLabel(s)));
+                    //get statistics for the current ruleset.
+                    labels.AddRange(working.GetPlayableBeatmap(info.Ruleset).GetStatistics().Select(s => new InfoLabel(s)));
                 }
 
                 return labels.ToArray();

From accffda5321fb6314d18665eccdf285e703c0972 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:51:30 +0900
Subject: [PATCH 038/177] Add taiko statistics

---
 .../Beatmaps/TaikoBeatmap.cs                  | 49 +++++++++++++++++++
 .../Beatmaps/TaikoBeatmapConverter.cs         |  2 +
 2 files changed, 51 insertions(+)
 create mode 100644 osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs

diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
new file mode 100644
index 0000000000..cbf2270c95
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
@@ -0,0 +1,49 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Beatmaps
+{
+    public class TaikoBeatmap : Beatmap<TaikoHitObject>
+    {
+        public override IEnumerable<BeatmapStatistic> GetStatistics()
+        {
+            int drumrolls = HitObjects.Count(s => s is DrumRoll);
+            int swells = HitObjects.Count(s => s is Swell);
+            int notes = HitObjects.Count - drumrolls - swells;
+
+            return new[]
+            {
+                new BeatmapStatistic
+                {
+                    Name = @"Object Count",
+                    Content = HitObjects.Count.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Note Count",
+                    Content = notes.ToString(),
+                    Icon = FontAwesome.fa_circle_o
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Drumroll Count",
+                    Content = drumrolls.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Swell Count",
+                    Content = swells.ToString(),
+                    Icon = FontAwesome.fa_circle
+                }
+            };
+        }
+    }
+}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index eeb0fa1871..d40e8dc643 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -197,5 +197,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                 }
             }
         }
+
+        protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
     }
 }

From 8a4717d2e9e84875f14f02f318020f8f6949d1cf Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:57:01 +0900
Subject: [PATCH 039/177] Add catch statistics

---
 .../Beatmaps/CatchBeatmap.cs                  | 49 +++++++++++++++++++
 1 file changed, 49 insertions(+)
 create mode 100644 osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs

diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
new file mode 100644
index 0000000000..1b65a70207
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -0,0 +1,49 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Catch.Objects;
+
+namespace osu.Game.Rulesets.Catch.Beatmaps
+{
+    public class CatchBeatmap : Beatmap<CatchHitObject>
+    {
+        public override IEnumerable<BeatmapStatistic> GetStatistics()
+        {
+            int juiceStreams = HitObjects.Count(s => s is JuiceStream);
+            int bananaShowers = HitObjects.Count(s => s is BananaShower);
+            int fruits = HitObjects.Count - juiceStreams - bananaShowers;
+
+            return new[]
+            {
+                new BeatmapStatistic
+                {
+                    Name = @"Object Count",
+                    Content = HitObjects.Count.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Fruit Count",
+                    Content = fruits.ToString(),
+                    Icon = FontAwesome.fa_circle_o
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Juice Stream Count",
+                    Content = juiceStreams.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Banana Shower Count",
+                    Content = bananaShowers.ToString(),
+                    Icon = FontAwesome.fa_circle
+                }
+            };
+        }
+    }
+}

From b737644208e0008eae06bdbe3a03b5ff9a527d1a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:58:56 +0900
Subject: [PATCH 040/177] Add mania statistics

---
 .../Beatmaps/ManiaBeatmap.cs                  | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index 6af3719f83..22cc57d924 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -4,6 +4,7 @@
 using System.Collections.Generic;
 using System.Linq;
 using osu.Game.Beatmaps;
+using osu.Game.Graphics;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.UI;
 
@@ -29,5 +30,33 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
         {
             Stages.Add(defaultStage);
         }
+
+        public override IEnumerable<BeatmapStatistic> GetStatistics()
+        {
+            int holdnotes = HitObjects.Count(s => s is HoldNote);
+            int notes = HitObjects.Count - holdnotes;
+
+            return new[]
+            {
+                new BeatmapStatistic
+                {
+                    Name = @"Object Count",
+                    Content = HitObjects.Count.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Note Count",
+                    Content = notes.ToString(),
+                    Icon = FontAwesome.fa_circle_o
+                },
+                new BeatmapStatistic
+                {
+                    Name = @"Hold Note Count",
+                    Content = holdnotes.ToString(),
+                    Icon = FontAwesome.fa_circle
+                },
+            };
+        }
     }
 }

From 5f74dc2c1746dec30ead8270bf58c1635146653d Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:59:30 +0900
Subject: [PATCH 041/177] Simplify osu-ruleset statistics

---
 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
index 68002f73b5..7b534deccf 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
@@ -6,7 +6,6 @@ using System.Linq;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics;
 using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Osu.Objects;
 
 namespace osu.Game.Rulesets.Osu.Beatmaps
@@ -15,9 +14,9 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
     {
         public override IEnumerable<BeatmapStatistic> GetStatistics()
         {
-            IEnumerable<HitObject> circles = HitObjects.Where(c => !(c is IHasEndTime));
-            IEnumerable<HitObject> sliders = HitObjects.Where(s => s is IHasCurve);
-            IEnumerable<HitObject> spinners = HitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
+            IEnumerable<HitObject> circles = HitObjects.Where(c => c is HitCircle);
+            IEnumerable<HitObject> sliders = HitObjects.Where(s => s is Slider);
+            IEnumerable<HitObject> spinners = HitObjects.Where(s => s is Spinner);
 
             return new[]
             {

From b9e4b59e46ff589a864de4da71b1fc504a1f95c0 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:01:09 +0900
Subject: [PATCH 042/177] Actually construct catch beatmaps

---
 osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 4a0b120e47..f40ef67dfb 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -69,5 +69,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
                 X = positionData.X / CatchPlayfield.BASE_WIDTH
             };
         }
+
+        protected override Beatmap<CatchHitObject> CreateBeatmap() => new CatchBeatmap();
     }
 }

From 7eb64ab590ccd828a04328b5b61818e544cb48be Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:33:05 +0900
Subject: [PATCH 043/177] Remove object counts from mania/taiko/catch

---
 osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs |  8 +-------
 osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs |  8 +-------
 osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs | 12 +++---------
 3 files changed, 5 insertions(+), 23 deletions(-)

diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
index 1b65a70207..5b4af6ea8a 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -13,18 +13,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
     {
         public override IEnumerable<BeatmapStatistic> GetStatistics()
         {
+            int fruits = HitObjects.Count(s => s is Fruit);
             int juiceStreams = HitObjects.Count(s => s is JuiceStream);
             int bananaShowers = HitObjects.Count(s => s is BananaShower);
-            int fruits = HitObjects.Count - juiceStreams - bananaShowers;
 
             return new[]
             {
-                new BeatmapStatistic
-                {
-                    Name = @"Object Count",
-                    Content = HitObjects.Count.ToString(),
-                    Icon = FontAwesome.fa_circle
-                },
                 new BeatmapStatistic
                 {
                     Name = @"Fruit Count",
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index 22cc57d924..ad5f8e447d 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -33,17 +33,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         public override IEnumerable<BeatmapStatistic> GetStatistics()
         {
+            int notes = HitObjects.Count(s => s is Note);
             int holdnotes = HitObjects.Count(s => s is HoldNote);
-            int notes = HitObjects.Count - holdnotes;
 
             return new[]
             {
-                new BeatmapStatistic
-                {
-                    Name = @"Object Count",
-                    Content = HitObjects.Count.ToString(),
-                    Icon = FontAwesome.fa_circle
-                },
                 new BeatmapStatistic
                 {
                     Name = @"Note Count",
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
index cbf2270c95..36f6df7869 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
@@ -13,22 +13,16 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
     {
         public override IEnumerable<BeatmapStatistic> GetStatistics()
         {
+            int hits = HitObjects.Count(s => s is Hit);
             int drumrolls = HitObjects.Count(s => s is DrumRoll);
             int swells = HitObjects.Count(s => s is Swell);
-            int notes = HitObjects.Count - drumrolls - swells;
 
             return new[]
             {
                 new BeatmapStatistic
                 {
-                    Name = @"Object Count",
-                    Content = HitObjects.Count.ToString(),
-                    Icon = FontAwesome.fa_circle
-                },
-                new BeatmapStatistic
-                {
-                    Name = @"Note Count",
-                    Content = notes.ToString(),
+                    Name = @"Hit Count",
+                    Content = hits.ToString(),
                     Icon = FontAwesome.fa_circle_o
                 },
                 new BeatmapStatistic

From 251bdfdee8c0232235b8de4f25620ca270946fd6 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:33:15 +0900
Subject: [PATCH 044/177] Simplify statistics in osu ruleset

---
 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
index 7b534deccf..6d90c2a875 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
@@ -5,7 +5,6 @@ using System.Collections.Generic;
 using System.Linq;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Osu.Objects;
 
 namespace osu.Game.Rulesets.Osu.Beatmaps
@@ -14,28 +13,28 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
     {
         public override IEnumerable<BeatmapStatistic> GetStatistics()
         {
-            IEnumerable<HitObject> circles = HitObjects.Where(c => c is HitCircle);
-            IEnumerable<HitObject> sliders = HitObjects.Where(s => s is Slider);
-            IEnumerable<HitObject> spinners = HitObjects.Where(s => s is Spinner);
+            int circles = HitObjects.Count(c => c is HitCircle);
+            int sliders = HitObjects.Count(s => s is Slider);
+            int spinners = HitObjects.Count(s => s is Spinner);
 
             return new[]
             {
                 new BeatmapStatistic
                 {
                     Name = @"Circle Count",
-                    Content = circles.Count().ToString(),
+                    Content = circles.ToString(),
                     Icon = FontAwesome.fa_circle_o
                 },
                 new BeatmapStatistic
                 {
                     Name = @"Slider Count",
-                    Content = sliders.Count().ToString(),
+                    Content = sliders.ToString(),
                     Icon = FontAwesome.fa_circle
                 },
                 new BeatmapStatistic
                 {
                     Name = @"Spinner Count",
-                    Content = spinners.Count().ToString(),
+                    Content = spinners.ToString(),
                     Icon = FontAwesome.fa_circle
                 }
             };

From 6a9f139d9b1fb1fb89a3ef93687e5ef8ae85507b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:33:40 +0900
Subject: [PATCH 045/177] Instantiate convertible hitobjects for beatmap info
 wedge

---
 osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index efd6c36ce2..a618e44301 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Rulesets;
 using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Osu;
 using osu.Game.Screens.Select;
 using osu.Game.Tests.Beatmaps;
@@ -138,7 +139,7 @@ namespace osu.Game.Tests.Visual
         {
             List<HitObject> objects = new List<HitObject>();
             for (double i = 0; i < 50000; i += 1000)
-                objects.Add(new HitObject { StartTime = i });
+                objects.Add(new TestHitObject { StartTime = i });
 
             return new Beatmap
             {
@@ -153,7 +154,8 @@ namespace osu.Game.Tests.Visual
                     },
                     Ruleset = ruleset,
                     StarDifficulty = 6,
-                    Version = $"{ruleset.ShortName}Version"
+                    Version = $"{ruleset.ShortName}Version",
+                    BaseDifficulty = new BeatmapDifficulty()
                 },
                 HitObjects = objects
             };
@@ -163,5 +165,12 @@ namespace osu.Game.Tests.Visual
         {
             public new BufferedWedgeInfo Info => base.Info;
         }
+
+        private class TestHitObject : HitObject, IHasPosition
+        {
+            public float X { get; } = 0;
+            public float Y { get; } = 0;
+            public Vector2 Position { get; } = Vector2.Zero;
+        }
     }
 }

From 09c70a93625d4bf3b2f6318b95014a55d0d27f49 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:36:51 +0900
Subject: [PATCH 046/177] Add info label tests to TestCaseBeatmapInfoWedge

---
 .../Visual/TestCaseBeatmapInfoWedge.cs        | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index a618e44301..0d3e08154f 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -12,9 +12,12 @@ using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Taiko;
 using osu.Game.Screens.Select;
 using osu.Game.Tests.Beatmaps;
 
@@ -73,13 +76,23 @@ namespace osu.Game.Tests.Visual
 
                 selectBeatmap(testBeatmap);
 
+                testBeatmapLabels(ruleset);
+
                 // TODO: adjust cases once more info is shown for other gamemodes
                 switch (ruleset)
                 {
-                    case OsuRuleset osu:
-                        testOsuBeatmap(osu);
+                    case OsuRuleset _:
                         testInfoLabels(5);
                         break;
+                    case TaikoRuleset _:
+                        testInfoLabels(5);
+                        break;
+                    case CatchRuleset _:
+                        testInfoLabels(5);
+                        break;
+                    case ManiaRuleset _:
+                        testInfoLabels(4);
+                        break;
                     default:
                         testInfoLabels(2);
                         break;
@@ -89,7 +102,7 @@ namespace osu.Game.Tests.Visual
             testNullBeatmap();
         }
 
-        private void testOsuBeatmap(OsuRuleset ruleset)
+        private void testBeatmapLabels(Ruleset ruleset)
         {
             AddAssert("check version", () => infoWedge.Info.VersionLabel.Text == $"{ruleset.ShortName}Version");
             AddAssert("check title", () => infoWedge.Info.TitleLabel.Text == $"{ruleset.ShortName}Source — {ruleset.ShortName}Title");

From fbd7ccc03fa732e3d6e4276f75c999538b14097f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 11:59:17 +0900
Subject: [PATCH 047/177] Make BeatmapInfoWedge display properly for converts

---
 osu.Game/Screens/Select/BeatmapInfoWedge.cs | 49 ++++++++++++++++++---
 1 file changed, 44 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index c0370763c0..7a8a04bd43 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -4,9 +4,11 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using JetBrains.Annotations;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
+using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
@@ -21,6 +23,8 @@ using osu.Game.Rulesets.Objects.Types;
 using osu.Framework.Graphics.Shapes;
 using osu.Framework.Graphics.Cursor;
 using osu.Framework.Localisation;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.UI;
 
 namespace osu.Game.Screens.Select
 {
@@ -28,6 +32,8 @@ namespace osu.Game.Screens.Select
     {
         private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0);
 
+        private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
+
         protected BufferedWedgeInfo Info;
 
         public BeatmapInfoWedge()
@@ -46,6 +52,14 @@ namespace osu.Game.Screens.Select
             };
         }
 
+        [BackgroundDependencyLoader(true)]
+        private void load([CanBeNull] OsuGame osuGame)
+        {
+            if (osuGame != null)
+                ruleset.BindTo(osuGame.Ruleset);
+            ruleset.ValueChanged += updateRuleset;
+        }
+
         protected override bool BlockPassThroughMouse => false;
 
         protected override void PopIn()
@@ -62,7 +76,17 @@ namespace osu.Game.Screens.Select
             this.FadeOut(500, Easing.In);
         }
 
+        private WorkingBeatmap beatmap;
+
         public void UpdateBeatmap(WorkingBeatmap beatmap)
+        {
+            this.beatmap = beatmap;
+            loadBeatmap();
+        }
+
+        private void updateRuleset(RulesetInfo ruleset) => loadBeatmap();
+
+        private void loadBeatmap()
         {
             LoadComponentAsync(new BufferedWedgeInfo(beatmap)
             {
@@ -90,14 +114,18 @@ namespace osu.Game.Screens.Select
             private UnicodeBindableString titleBinding;
             private UnicodeBindableString artistBinding;
 
+            private RulesetInfo ruleset;
+
             public BufferedWedgeInfo(WorkingBeatmap working)
             {
                 this.working = working;
             }
 
-            [BackgroundDependencyLoader]
-            private void load(LocalisationEngine localisation)
+            [BackgroundDependencyLoader(true)]
+            private void load([NotNull] LocalisationEngine localisation, [CanBeNull] OsuGame osuGame)
             {
+                ruleset = osuGame?.Ruleset.Value ?? working.BeatmapInfo.Ruleset;
+
                 var beatmapInfo = working.BeatmapInfo;
                 var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
 
@@ -211,7 +239,6 @@ namespace osu.Game.Screens.Select
             private InfoLabel[] getInfoLabels()
             {
                 var beatmap = working.Beatmap;
-                var info = working.BeatmapInfo;
 
                 List<InfoLabel> labels = new List<InfoLabel>();
 
@@ -234,8 +261,20 @@ namespace osu.Game.Screens.Select
                         Content = getBPMRange(beatmap),
                     }));
 
-                    //get statistics for the current ruleset.
-                    labels.AddRange(working.GetPlayableBeatmap(info.Ruleset).GetStatistics().Select(s => new InfoLabel(s)));
+                    IBeatmap playableBeatmap;
+
+                    try
+                    {
+                        // Try to get the beatmap with the user's ruleset
+                        playableBeatmap = working.GetPlayableBeatmap(ruleset);
+                    }
+                    catch (BeatmapInvalidForRulesetException)
+                    {
+                        // Can't be converted to the user's ruleset, so use the beatmap's own ruleset
+                        playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset);
+                    }
+
+                    labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)));
                 }
 
                 return labels.ToArray();

From 674e45d49bb751eef903f7264e05563fa463b23e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 16:32:15 +0900
Subject: [PATCH 048/177] Fix beatmap/ruleset being changeable while in the
 editor

---
 osu.Game/Screens/Edit/Editor.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index adb749b492..e4eaee76fc 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Screens.Edit
         protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
 
         public override bool ShowOverlaysOnEnter => false;
+        public override bool AllowBeatmapRulesetChange => false;
 
         private Box bottomBackground;
         private Container screenContainer;

From 44bbb8700ecc1bdd652c35766bfbaa54310a5855 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Tue, 8 May 2018 00:22:11 +0300
Subject: [PATCH 049/177] Handle mouse back button using OnMouseDown override
 instead of using GlobalAction

---
 .../Input/Bindings/GlobalActionContainer.cs   | 10 +----
 osu.Game/Screens/Loader.cs                    |  1 +
 osu.Game/Screens/Menu/ButtonSystem.cs         | 44 +++++++------------
 osu.Game/Screens/Menu/MainMenu.cs             |  1 +
 osu.Game/Screens/OsuScreen.cs                 | 28 ++++++++----
 osu.Game/Screens/Play/PlayerLoader.cs         |  1 -
 .../Play/ScreenWithBeatmapBackground.cs       |  2 +
 7 files changed, 43 insertions(+), 44 deletions(-)

diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 565d530395..dd8f00f6cd 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Input.Bindings
         {
             new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
             new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
-            new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
+            new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
 
             new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
             new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
@@ -36,9 +36,6 @@ namespace osu.Game.Input.Bindings
             new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
-
-            new KeyBinding(InputKey.Escape, GlobalAction.Back),
-            new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
         };
 
         public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@@ -79,9 +76,6 @@ namespace osu.Game.Input.Bindings
         QuickRetry,
 
         [Description("Take screenshot")]
-        TakeScreenshot,
-
-        [Description("Go back")]
-        Back
+        TakeScreenshot
     }
 }
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index 1d152361df..dc8dbb4421 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -18,6 +18,7 @@ namespace osu.Game.Screens
         private bool showDisclaimer;
 
         public override bool ShowOverlaysOnEnter => false;
+        protected override bool AllowBackButton => false;
 
         public Loader()
         {
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 8cf0d24f7d..5a1dafe404 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -13,17 +13,15 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
 using osu.Framework.Input;
-using osu.Framework.Input.Bindings;
 using osu.Framework.Threading;
 using osu.Game.Graphics;
-using osu.Game.Input.Bindings;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
 
 namespace osu.Game.Screens.Menu
 {
-    public class ButtonSystem : Container, IStateful<MenuState>, IKeyBindingHandler<GlobalAction>
+    public class ButtonSystem : Container, IStateful<MenuState>
     {
         public event Action<MenuState> StateChanged;
 
@@ -148,43 +146,35 @@ namespace osu.Game.Screens.Menu
                 case Key.Space:
                     logo?.TriggerOnClick(state);
                     return true;
+                case Key.Escape:
+                    return handleBack();
             }
 
             return false;
         }
 
-        public bool OnPressed(GlobalAction action)
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
         {
-            switch (action)
-            {
-                case GlobalAction.Back:
-                    switch (State)
-                    {
-                        case MenuState.TopLevel:
-                            State = MenuState.Initial;
-                            return true;
-                        case MenuState.Play:
-                            backButton.TriggerOnClick();
-                            return true;
-                        default:
-                            return false;
-                    }
-                default:
-                    return false;
-            }
+            if (state.Mouse.IsPressed(MouseButton.Button1))
+                return handleBack();
+
+            return base.OnMouseDown(state, args);
         }
 
-        public bool OnReleased(GlobalAction action)
+        private bool handleBack()
         {
-            switch (action)
+            switch (State)
             {
-                case GlobalAction.Back:
+                case MenuState.TopLevel:
+                    State = MenuState.Initial;
+                    return true;
+                case MenuState.Play:
+                    backButton.TriggerOnClick();
                     return true;
-                default:
-                    return false;
             }
-        }
 
+            return false;
+        }
 
         private void onPlay()
         {
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index f2ea6d85a8..d91ac099fd 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -25,6 +25,7 @@ namespace osu.Game.Screens.Menu
         private readonly ButtonSystem buttons;
 
         public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial;
+        protected override bool AllowBackButton => false;
 
         private readonly BackgroundScreenDefault background;
         private Screen songSelect;
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index fb5d3d12e6..9fa30181a1 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -7,18 +7,18 @@ using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
-using osu.Framework.Input.Bindings;
+using osu.Framework.Input;
 using osu.Framework.Screens;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics.Containers;
-using osu.Game.Input.Bindings;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Menu;
 using OpenTK;
+using OpenTK.Input;
 
 namespace osu.Game.Screens
 {
-    public abstract class OsuScreen : Screen, IKeyBindingHandler<GlobalAction>
+    public abstract class OsuScreen : Screen
     {
         public BackgroundScreen Background { get; private set; }
 
@@ -92,19 +92,31 @@ namespace osu.Game.Screens
             sampleExit = audio.Sample.Get(@"UI/screen-back");
         }
 
-        public bool OnPressed(GlobalAction action)
+        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
         {
-            if (action == GlobalAction.Back && AllowBackButton)
+            if (args.Repeat || !IsCurrentScreen) return false;
+
+            switch (args.Key)
+            {
+                case Key.Escape:
+                    Exit();
+                    return true;
+            }
+
+            return base.OnKeyDown(state, args);
+        }
+
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        {
+            if (AllowBackButton && state.Mouse.IsPressed(MouseButton.Button1))
             {
                 Exit();
                 return true;
             }
 
-            return false;
+            return base.OnMouseDown(state, args);
         }
 
-        public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
-
         protected override void OnResuming(Screen last)
         {
             sampleExit?.Play();
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 6eb156914e..56fbd7b6e7 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -27,7 +27,6 @@ namespace osu.Game.Screens.Play
 
         private bool showOverlays = true;
         public override bool ShowOverlaysOnEnter => showOverlays;
-        protected override bool AllowBackButton => false;
 
         private Task loadTask;
 
diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
index 1ccc5e2fe8..30ae6db346 100644
--- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
+++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
@@ -17,6 +17,8 @@ namespace osu.Game.Screens.Play
 
         public override bool AllowBeatmapRulesetChange => false;
 
+        protected override bool AllowBackButton => false;
+
         protected const float BACKGROUND_FADE_DURATION = 800;
 
         protected float BackgroundOpacity => 1 - (float)DimLevel;

From c7bc79bce34a9c7f46f738b19d93b80cfbf2dcb4 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 9 May 2018 12:40:04 +0900
Subject: [PATCH 050/177] Remove explicit clock in TestCaseBreakOverlay

---
 osu.Game.Tests/Visual/TestCaseBreakOverlay.cs | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
index a6a6130a8f..442ca1322a 100644
--- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Framework.Timing;
 using osu.Game.Beatmaps.Timing;
 using System.Collections.Generic;
 using NUnit.Framework;
@@ -16,8 +15,6 @@ namespace osu.Game.Tests.Visual
 
         public TestCaseBreakOverlay()
         {
-            Clock = new FramedClock();
-
             Child = breakOverlay = new BreakOverlay(true);
 
             AddStep("2s break", () => startBreak(2000));

From 5df9f126d162ea33a8eb1af68746ec5291d4f21d Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 May 2018 17:07:19 +0900
Subject: [PATCH 051/177] Implement a hold-to-confirm screen when exiting game
 using escape key

---
 osu.Game/Overlays/HoldToConfirmOverlay.cs   | 59 +++++++++++++++++++++
 osu.Game/Screens/Menu/ExitConfirmOverlay.cs | 34 ++++++++++++
 osu.Game/Screens/Menu/MainMenu.cs           |  4 ++
 osu.Game/Screens/Play/HotkeyRetryOverlay.cs | 49 ++---------------
 4 files changed, 101 insertions(+), 45 deletions(-)
 create mode 100644 osu.Game/Overlays/HoldToConfirmOverlay.cs
 create mode 100644 osu.Game/Screens/Menu/ExitConfirmOverlay.cs

diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs
new file mode 100644
index 0000000000..4bc7154ce8
--- /dev/null
+++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs
@@ -0,0 +1,59 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using OpenTK.Graphics;
+
+namespace osu.Game.Overlays
+{
+    /// <summary>
+    /// An overlay which will display a black screen that dims over a period before confirming an exit action.
+    /// Action is BYO (derived class will need to call <see cref="BeginConfirm"/> and <see cref="AbortConfirm"/> from a user event).
+    /// </summary>
+    public abstract class HoldToConfirmOverlay : Container
+    {
+        public Action Action;
+
+        private Box overlay;
+
+        private const int activate_delay = 400;
+        private const int fadeout_delay = 200;
+
+        private bool fired;
+
+        [BackgroundDependencyLoader]
+        private void load()
+        {
+            RelativeSizeAxes = Axes.Both;
+            AlwaysPresent = true;
+
+            Children = new Drawable[]
+            {
+                overlay = new Box
+                {
+                    Alpha = 0,
+                    Colour = Color4.Black,
+                    RelativeSizeAxes = Axes.Both,
+                }
+            };
+        }
+
+        protected void BeginConfirm() => overlay.FadeIn(activate_delay, Easing.Out);
+
+        protected void AbortConfirm() => overlay.FadeOut(fadeout_delay, Easing.Out);
+
+        protected override void Update()
+        {
+            base.Update();
+            if (!fired && overlay.Alpha == 1)
+            {
+                fired = true;
+                Action?.Invoke();
+            }
+        }
+    }
+}
diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
new file mode 100644
index 0000000000..62605da5a4
--- /dev/null
+++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Input;
+using osu.Game.Overlays;
+using OpenTK.Input;
+
+namespace osu.Game.Screens.Menu
+{
+    public class ExitConfirmOverlay : HoldToConfirmOverlay
+    {
+        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+        {
+            if (args.Key == Key.Escape && !args.Repeat)
+            {
+                BeginConfirm();
+                return true;
+            }
+
+            return base.OnKeyDown(state, args);
+        }
+
+        protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
+        {
+            if (args.Key == Key.Escape)
+            {
+                AbortConfirm();
+                return true;
+            }
+
+            return base.OnKeyUp(state, args);
+        }
+    }
+}
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index f2ea6d85a8..ce5aace50b 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -39,6 +39,10 @@ namespace osu.Game.Screens.Menu
 
             Children = new Drawable[]
             {
+                new ExitConfirmOverlay
+                {
+                    Action = Exit,
+                },
                 new ParallaxContainer
                 {
                     ParallaxAmount = 0.01f,
diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
index a018a2697a..926a96eb6c 100644
--- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
+++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs
@@ -1,50 +1,19 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Framework.Allocation;
-using System;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
 using osu.Framework.Input.Bindings;
 using osu.Game.Input.Bindings;
-using OpenTK.Graphics;
+using osu.Game.Overlays;
 
 namespace osu.Game.Screens.Play
 {
-    public class HotkeyRetryOverlay : Container, IKeyBindingHandler<GlobalAction>
+    public class HotkeyRetryOverlay : HoldToConfirmOverlay, IKeyBindingHandler<GlobalAction>
     {
-        public Action Action;
-
-        private Box overlay;
-
-        private const int activate_delay = 400;
-        private const int fadeout_delay = 200;
-
-        private bool fired;
-
-        [BackgroundDependencyLoader]
-        private void load()
-        {
-            RelativeSizeAxes = Axes.Both;
-            AlwaysPresent = true;
-
-            Children = new Drawable[]
-            {
-                overlay = new Box
-                {
-                    Alpha = 0,
-                    Colour = Color4.Black,
-                    RelativeSizeAxes = Axes.Both,
-                }
-            };
-        }
-
         public bool OnPressed(GlobalAction action)
         {
             if (action != GlobalAction.QuickRetry) return false;
 
-            overlay.FadeIn(activate_delay, Easing.Out);
+            BeginConfirm();
             return true;
         }
 
@@ -52,18 +21,8 @@ namespace osu.Game.Screens.Play
         {
             if (action != GlobalAction.QuickRetry) return false;
 
-            overlay.FadeOut(fadeout_delay, Easing.Out);
+            AbortConfirm();
             return true;
         }
-
-        protected override void Update()
-        {
-            base.Update();
-            if (!fired && overlay.Alpha == 1)
-            {
-                fired = true;
-                Action?.Invoke();
-            }
-        }
     }
 }

From 63e10ec3c2d2844e13e7e2b47b86984be5e4547a Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 May 2018 17:10:42 +0900
Subject: [PATCH 052/177] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 96ef8c43b5..e793a08417 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 96ef8c43b5e6b6ae14b01c3550c480c8d9a78518
+Subproject commit e793a084177f53920645c4f6f70cfef91e7fd19e

From fadb1a5e0b44c85b2ff132aa0ab91b9c34285621 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 May 2018 17:30:24 +0900
Subject: [PATCH 053/177] Add tests and expand functionality to ensure single
 fire

---
 .../Visual/TestCaseHoldToConfirmOverlay.cs    | 62 +++++++++++++++++++
 osu.Game/Overlays/HoldToConfirmOverlay.cs     | 25 +++++---
 2 files changed, 78 insertions(+), 9 deletions(-)
 create mode 100644 osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs

diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
new file mode 100644
index 0000000000..ef6f44b42e
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
@@ -0,0 +1,62 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual
+{
+    public class TestCaseHoldToConfirmOverlay : OsuTestCase
+    {
+        public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ExitConfirmOverlay) };
+
+        public TestCaseHoldToConfirmOverlay()
+        {
+            bool fired = false;
+
+            var abortText = new OsuSpriteText
+            {
+                Anchor = Anchor.Centre,
+                Origin = Anchor.Centre,
+                Text = "Aborted!",
+                TextSize = 50,
+                Alpha = 0,
+            };
+
+            var overlay = new TestHoldToConfirmOverlay
+            {
+                Action = () =>
+                {
+                    fired = true;
+                    abortText.FadeTo(1).Then().FadeOut(1000);
+                }
+            };
+
+            Children = new Drawable[]
+            {
+                overlay,
+                abortText
+            };
+
+            AddStep("start confirming", () => overlay.Begin());
+            AddStep("abort confirming", () => overlay.Abort());
+
+            AddAssert("ensure aborted", () => !fired);
+
+            AddStep("start confirming", () => overlay.Begin());
+
+            AddUntilStep(() => fired, "wait until confirmed");
+        }
+
+        private class TestHoldToConfirmOverlay : ExitConfirmOverlay
+        {
+            protected override bool AllowMultipleFires => true;
+
+            public void Begin() => BeginConfirm();
+            public void Abort() => AbortConfirm();
+        }
+    }
+}
diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs
index 4bc7154ce8..a0e4bf1a39 100644
--- a/osu.Game/Overlays/HoldToConfirmOverlay.cs
+++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs
@@ -25,6 +25,11 @@ namespace osu.Game.Overlays
 
         private bool fired;
 
+        /// <summary>
+        /// Whether the overlay should be allowed to return from a fired state.
+        /// </summary>
+        protected virtual bool AllowMultipleFires => false;
+
         [BackgroundDependencyLoader]
         private void load()
         {
@@ -42,18 +47,20 @@ namespace osu.Game.Overlays
             };
         }
 
-        protected void BeginConfirm() => overlay.FadeIn(activate_delay, Easing.Out);
-
-        protected void AbortConfirm() => overlay.FadeOut(fadeout_delay, Easing.Out);
-
-        protected override void Update()
+        protected void BeginConfirm()
         {
-            base.Update();
-            if (!fired && overlay.Alpha == 1)
+            if (!AllowMultipleFires && fired) return;
+            overlay.FadeIn(activate_delay * (1 - overlay.Alpha), Easing.Out).OnComplete(_ =>
             {
-                fired = true;
                 Action?.Invoke();
-            }
+                fired = true;
+            });
+        }
+
+        protected void AbortConfirm()
+        {
+            if (!AllowMultipleFires && fired) return;
+            overlay.FadeOut(fadeout_delay, Easing.Out);
         }
     }
 }

From c7443e6b3b5a19795e3b7c38f235b9e9e90c5989 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 May 2018 17:10:42 +0900
Subject: [PATCH 054/177] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 96ef8c43b5..e793a08417 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 96ef8c43b5e6b6ae14b01c3550c480c8d9a78518
+Subproject commit e793a084177f53920645c4f6f70cfef91e7fd19e

From c03ab9014f1eb2276ec9057bdd1b1a608add5d03 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 10 May 2018 15:49:37 +0900
Subject: [PATCH 055/177] Improve TestCaseOnScreenDisplay

Now doesn't rely on framesync.
---
 .../Visual/TestCaseOnScreenDisplay.cs         | 104 +++++++++++++-----
 osu.Game/Overlays/OnScreenDisplay.cs          |  23 ++--
 2 files changed, 90 insertions(+), 37 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
index 233c418d4a..123c1fe055 100644
--- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
@@ -4,6 +4,8 @@
 using NUnit.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
+using osu.Framework.Configuration.Tracking;
+using osu.Framework.Graphics;
 using osu.Game.Overlays;
 
 namespace osu.Game.Tests.Visual
@@ -11,36 +13,82 @@ namespace osu.Game.Tests.Visual
     [TestFixture]
     public class TestCaseOnScreenDisplay : OsuTestCase
     {
-        private FrameworkConfigManager config;
-        private Bindable<FrameSync> frameSyncMode;
-
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-
-            Add(new OnScreenDisplay());
-
-            frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync);
-
-            FrameSync initial = frameSyncMode.Value;
-
-            AddRepeatStep(@"Change frame limiter", setNextMode, 3);
-
-            AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial);
-        }
-
-        private void setNextMode()
-        {
-            var nextMode = frameSyncMode.Value + 1;
-            if (nextMode > FrameSync.Unlimited)
-                nextMode = FrameSync.VSync;
-            frameSyncMode.Value = nextMode;
-        }
-
         [BackgroundDependencyLoader]
-        private void load(FrameworkConfigManager config)
+        private void load()
         {
-            this.config = config;
+            var config = new TestConfigManager();
+
+            var osd = new TestOnScreenDisplay();
+            osd.BeginTracking(this, config);
+            Add(osd);
+
+            AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2);
+            AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2);
+            AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3);
+            AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeybind), 3);
+        }
+
+        private class TestConfigManager : ConfigManager<TestConfigSetting>
+        {
+            public TestConfigManager()
+            {
+                InitialiseDefaults();
+            }
+
+            protected override void InitialiseDefaults()
+            {
+                Set(TestConfigSetting.ToggleSettingNoKeybind, false);
+                Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1);
+                Set(TestConfigSetting.ToggleSettingWithKeybind, false);
+                Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1);
+
+                base.InitialiseDefaults();
+            }
+
+            public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get<bool>(setting));
+
+            public void IncrementEnumSetting(TestConfigSetting setting)
+            {
+                var nextValue = Get<EnumSetting>(setting) + 1;
+                if (nextValue > EnumSetting.Setting4)
+                    nextValue = EnumSetting.Setting1;
+                Set(setting, nextValue);
+            }
+
+            public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
+            {
+                new TrackedSetting<bool>(TestConfigSetting.ToggleSettingNoKeybind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")),
+                new TrackedSetting<EnumSetting>(TestConfigSetting.EnumSettingNoKeybind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())),
+                new TrackedSetting<bool>(TestConfigSetting.ToggleSettingWithKeybind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")),
+                new TrackedSetting<EnumSetting>(TestConfigSetting.EnumSettingWithKeybind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")),
+            };
+
+            protected override void PerformLoad()
+            {
+            }
+
+            protected override bool PerformSave() => false;
+        }
+
+        private enum TestConfigSetting
+        {
+            ToggleSettingNoKeybind,
+            EnumSettingNoKeybind,
+            ToggleSettingWithKeybind,
+            EnumSettingWithKeybind
+        }
+
+        private enum EnumSetting
+        {
+            Setting1,
+            Setting2,
+            Setting3,
+            Setting4
+        }
+
+        private class TestOnScreenDisplay : OnScreenDisplay
+        {
+            protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
         }
     }
 }
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 9882ea01f0..a643080df8 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -102,12 +102,12 @@ namespace osu.Game.Overlays
                                 },
                                 textLine3 = new OsuSpriteText
                                 {
+                                    Anchor = Anchor.TopCentre,
+                                    Origin = Anchor.TopCentre,
                                     Padding = new MarginPadding { Bottom = 15 },
                                     Font = @"Exo2.0-Bold",
                                     TextSize = 12,
                                     Alpha = 0.3f,
-                                    Anchor = Anchor.TopCentre,
-                                    Origin = Anchor.TopCentre,
                                 },
                             }
                         }
@@ -177,13 +177,7 @@ namespace osu.Game.Overlays
                 textLine2.Text = description.Value;
                 textLine3.Text = description.Shortcut.ToUpper();
 
-                box.Animate(
-                    b => b.FadeIn(500, Easing.OutQuint),
-                    b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
-                ).Then(
-                    b => b.FadeOutFromOne(1500, Easing.InQuint),
-                    b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)
-                );
+                Display(box);
 
                 int optionCount = 0;
                 int selectedOption = -1;
@@ -215,6 +209,17 @@ namespace osu.Game.Overlays
             });
         }
 
+        protected virtual void Display(Drawable toDisplay)
+        {
+            toDisplay.Animate(
+                b => b.FadeIn(500, Easing.OutQuint),
+                b => b.ResizeHeightTo(height, 500, Easing.OutQuint)
+            ).Then(
+                b => b.FadeOutFromOne(1500, Easing.InQuint),
+                b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint)
+            );
+        }
+
         private class OptionLight : Container
         {
             private Color4 glowingColour, idleColour;

From df604c40ccd33a1b70637a89f09211f6b7de5c87 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 10 May 2018 18:49:19 +0900
Subject: [PATCH 056/177] Keep shortcut padding even when there is no shortut

---
 osu-framework                        |  2 +-
 osu.Game/Overlays/OnScreenDisplay.cs | 16 +++++++++++-----
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/osu-framework b/osu-framework
index 0773d895d9..5c135a7360 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed
+Subproject commit 5c135a7360388dc41b1dc4387769a98ee1635d36
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index a643080df8..8f6bf0f539 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -31,6 +31,7 @@ namespace osu.Game.Overlays
         private readonly SpriteText textLine3;
 
         private const float height = 110;
+        private const float height_notext = 98;
         private const float height_contracted = height * 0.9f;
 
         private readonly FillFlowContainer<OptionLight> optionLights;
@@ -100,15 +101,20 @@ namespace osu.Game.Overlays
                                     Origin = Anchor.TopCentre,
                                     AutoSizeAxes = Axes.Both
                                 },
-                                textLine3 = new OsuSpriteText
+                                new Container
                                 {
                                     Anchor = Anchor.TopCentre,
                                     Origin = Anchor.TopCentre,
-                                    Padding = new MarginPadding { Bottom = 15 },
-                                    Font = @"Exo2.0-Bold",
-                                    TextSize = 12,
+                                    AutoSizeAxes = Axes.Both,
+                                    Margin = new MarginPadding { Bottom = 15 },
                                     Alpha = 0.3f,
-                                },
+                                    Child = textLine3 = new OsuSpriteText
+                                    {
+                                        Font = @"Exo2.0-Bold",
+                                        TextSize = 12,
+                                        AlwaysPresent = true
+                                    },
+                                }
                             }
                         }
                     }

From 40c1b24b3db0628bfe8f632640c413062d99562a Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 10 May 2018 19:51:40 +0900
Subject: [PATCH 057/177] Move IBeatmap to its own file

---
 osu.Game/Beatmaps/Beatmap.cs  | 39 ---------------------------
 osu.Game/Beatmaps/IBeatmap.cs | 50 +++++++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 39 deletions(-)
 create mode 100644 osu.Game/Beatmaps/IBeatmap.cs

diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 6a7d2690ff..2df0547175 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -12,45 +12,6 @@ using osu.Game.IO.Serialization.Converters;
 
 namespace osu.Game.Beatmaps
 {
-    public interface IBeatmap : IJsonSerializable
-    {
-        /// <summary>
-        /// This beatmap's info.
-        /// </summary>
-        BeatmapInfo BeatmapInfo { get; set; }
-
-        /// <summary>
-        /// This beatmap's metadata.
-        /// </summary>
-        BeatmapMetadata Metadata { get; }
-
-        /// <summary>
-        /// The control points in this beatmap.
-        /// </summary>
-        ControlPointInfo ControlPointInfo { get; }
-
-        /// <summary>
-        /// The breaks in this beatmap.
-        /// </summary>
-        List<BreakPeriod> Breaks { get; }
-
-        /// <summary>
-        /// Total amount of break time in the beatmap.
-        /// </summary>
-        double TotalBreakTime { get; }
-
-        /// <summary>
-        /// The hitobjects contained by this beatmap.
-        /// </summary>
-        IEnumerable<HitObject> HitObjects { get; }
-
-        /// <summary>
-        /// Creates a shallow-clone of this beatmap and returns it.
-        /// </summary>
-        /// <returns>The shallow-cloned beatmap.</returns>
-        IBeatmap Clone();
-    }
-
     /// <summary>
     /// A Beatmap containing converted HitObjects.
     /// </summary>
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
new file mode 100644
index 0000000000..4676f056fa
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -0,0 +1,50 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Beatmaps
+{
+    public interface IBeatmap : IJsonSerializable
+    {
+        /// <summary>
+        /// This beatmap's info.
+        /// </summary>
+        BeatmapInfo BeatmapInfo { get; set; }
+
+        /// <summary>
+        /// This beatmap's metadata.
+        /// </summary>
+        BeatmapMetadata Metadata { get; }
+
+        /// <summary>
+        /// The control points in this beatmap.
+        /// </summary>
+        ControlPointInfo ControlPointInfo { get; }
+
+        /// <summary>
+        /// The breaks in this beatmap.
+        /// </summary>
+        List<BreakPeriod> Breaks { get; }
+
+        /// <summary>
+        /// Total amount of break time in the beatmap.
+        /// </summary>
+        double TotalBreakTime { get; }
+
+        /// <summary>
+        /// The hitobjects contained by this beatmap.
+        /// </summary>
+        IEnumerable<HitObject> HitObjects { get; }
+
+        /// <summary>
+        /// Creates a shallow-clone of this beatmap and returns it.
+        /// </summary>
+        /// <returns>The shallow-cloned beatmap.</returns>
+        IBeatmap Clone();
+    }
+}

From ecb8de29a20138c057fa937ab048dc1cc32f7c2e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 20:30:03 -0300
Subject: [PATCH 058/177] Adjust BreadcrumbControl to better match the designs.

---
 .../UserInterface/BreadcrumbControl.cs         | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index 3f59eeca97..f5017de639 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -14,14 +14,18 @@ namespace osu.Game.Graphics.UserInterface
     public class BreadcrumbControl<T> : OsuTabControl<T>
     {
         private const float padding = 10;
+        private const float item_chevron_size = 10;
 
-        protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value);
+        protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value)
+        {
+            AccentColour = AccentColour,
+        };
 
-        protected override float StripWidth() => base.StripWidth() - (padding + 8);
+        protected override float StripWidth() => base.StripWidth() - (padding + item_chevron_size);
 
         public BreadcrumbControl()
         {
-            Height = 26;
+            Height = 32;
             TabContainer.Spacing = new Vector2(padding, 0f);
             Current.ValueChanged += tab =>
             {
@@ -47,6 +51,7 @@ namespace osu.Game.Graphics.UserInterface
 
             public override bool HandleKeyboardInput => State == Visibility.Visible;
             public override bool HandleMouseInput => State == Visibility.Visible;
+            public override bool IsRemovable => true;
 
             private Visibility state;
 
@@ -77,13 +82,14 @@ namespace osu.Game.Graphics.UserInterface
 
             public BreadcrumbTabItem(T value) : base(value)
             {
-                Text.TextSize = 16;
-                Padding = new MarginPadding { Right = padding + 8 }; //padding + chevron width
+                Text.TextSize = 18;
+                Text.Margin = new MarginPadding { Vertical = 8 };
+                Padding = new MarginPadding { Right = padding + item_chevron_size };
                 Add(Chevron = new SpriteIcon
                 {
                     Anchor = Anchor.CentreRight,
                     Origin = Anchor.CentreLeft,
-                    Size = new Vector2(12),
+                    Size = new Vector2(item_chevron_size),
                     Icon = FontAwesome.fa_chevron_right,
                     Margin = new MarginPadding { Left = padding },
                     Alpha = 0f,

From c35760fdef40981d1830e32e5d2c5d292300f9d8 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 20:39:05 -0300
Subject: [PATCH 059/177] Rename osu.Game/Screens/Multiplayer to Multi.

---
 osu.Game.Tests/Visual/TestCaseDrawableRoom.cs    |   2 +-
 osu.Game.Tests/Visual/TestCaseRoomInspector.cs   |   2 +-
 osu.Game/Screens/Menu/MainMenu.cs                |   2 +-
 osu.Game/Screens/Multi/.DS_Store                 | Bin 0 -> 6148 bytes
 .../{Multiplayer => Multi}/DrawableGameType.cs   |   2 +-
 .../{Multiplayer => Multi}/DrawableRoom.cs       |   2 +-
 osu.Game/Screens/{Multiplayer => Multi}/Lobby.cs |   2 +-
 osu.Game/Screens/{Multiplayer => Multi}/Match.cs |   2 +-
 .../{Multiplayer => Multi}/MatchCreate.cs        |   2 +-
 .../{Multiplayer => Multi}/ModeTypeInfo.cs       |   2 +-
 .../{Multiplayer => Multi}/ParticipantInfo.cs    |   2 +-
 .../{Multiplayer => Multi}/RoomInspector.cs      |   2 +-
 12 files changed, 11 insertions(+), 11 deletions(-)
 create mode 100644 osu.Game/Screens/Multi/.DS_Store
 rename osu.Game/Screens/{Multiplayer => Multi}/DrawableGameType.cs (96%)
 rename osu.Game/Screens/{Multiplayer => Multi}/DrawableRoom.cs (99%)
 rename osu.Game/Screens/{Multiplayer => Multi}/Lobby.cs (91%)
 rename osu.Game/Screens/{Multiplayer => Multi}/Match.cs (96%)
 rename osu.Game/Screens/{Multiplayer => Multi}/MatchCreate.cs (92%)
 rename osu.Game/Screens/{Multiplayer => Multi}/ModeTypeInfo.cs (98%)
 rename osu.Game/Screens/{Multiplayer => Multi}/ParticipantInfo.cs (99%)
 rename osu.Game/Screens/{Multiplayer => Multi}/RoomInspector.cs (99%)

diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index 25f8ba06c4..958dbd3c74 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi;
 using osu.Game.Users;
 
 namespace osu.Game.Tests.Visual
diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index 88059d2dcf..d0752cfc76 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics;
 using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi;
 using osu.Game.Users;
 
 namespace osu.Game.Tests.Visual
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index f2ea6d85a8..b09b53063a 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
 using osu.Game.Screens.Charts;
 using osu.Game.Screens.Direct;
 using osu.Game.Screens.Edit;
-using osu.Game.Screens.Multiplayer;
+using osu.Game.Screens.Multi;
 using osu.Game.Screens.Select;
 using osu.Game.Screens.Tournament;
 
diff --git a/osu.Game/Screens/Multi/.DS_Store b/osu.Game/Screens/Multi/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6
GIT binary patch
literal 6148
zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3
zem<@ulZcFPQ@L2!n>{z**<q8>++&mCkOWA81W14cNZ<zv;LbK1Poaz?KmsK2CSc!(
z0ynLxE!0092;Krf2c+FF_Fe*7ECH>lEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ
zLs35+`xjp>T0<F0fCPF1$Cyrb|F7^5{eNG?83~ZUUlGt@xh*qZDeu<Z%US-OSsOPv
j)R!Z4KLME7ReXlK;d!wEw5GODWMKRea10D2@KpjYNUI8I

literal 0
HcmV?d00001

diff --git a/osu.Game/Screens/Multiplayer/DrawableGameType.cs b/osu.Game/Screens/Multi/DrawableGameType.cs
similarity index 96%
rename from osu.Game/Screens/Multiplayer/DrawableGameType.cs
rename to osu.Game/Screens/Multi/DrawableGameType.cs
index 5790008f76..0d43e78f2b 100644
--- a/osu.Game/Screens/Multiplayer/DrawableGameType.cs
+++ b/osu.Game/Screens/Multi/DrawableGameType.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
 using osu.Game.Graphics;
 using osu.Game.Online.Multiplayer;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class DrawableGameType : CircularContainer, IHasTooltip
     {
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multi/DrawableRoom.cs
similarity index 99%
rename from osu.Game/Screens/Multiplayer/DrawableRoom.cs
rename to osu.Game/Screens/Multi/DrawableRoom.cs
index d53100526f..0ec27e0ff2 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/DrawableRoom.cs
@@ -18,7 +18,7 @@ using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class DrawableRoom : OsuClickableContainer
     {
diff --git a/osu.Game/Screens/Multiplayer/Lobby.cs b/osu.Game/Screens/Multi/Lobby.cs
similarity index 91%
rename from osu.Game/Screens/Multiplayer/Lobby.cs
rename to osu.Game/Screens/Multi/Lobby.cs
index 65fa5fbb16..80e682e9f9 100644
--- a/osu.Game/Screens/Multiplayer/Lobby.cs
+++ b/osu.Game/Screens/Multi/Lobby.cs
@@ -4,7 +4,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class Lobby : ScreenWhiteBox
     {
diff --git a/osu.Game/Screens/Multiplayer/Match.cs b/osu.Game/Screens/Multi/Match.cs
similarity index 96%
rename from osu.Game/Screens/Multiplayer/Match.cs
rename to osu.Game/Screens/Multi/Match.cs
index 5402e70ea5..eee302a115 100644
--- a/osu.Game/Screens/Multiplayer/Match.cs
+++ b/osu.Game/Screens/Multi/Match.cs
@@ -10,7 +10,7 @@ using OpenTK.Graphics;
 using osu.Game.Screens.Select;
 using osu.Framework.Graphics;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class Match : ScreenWhiteBox
     {
diff --git a/osu.Game/Screens/Multiplayer/MatchCreate.cs b/osu.Game/Screens/Multi/MatchCreate.cs
similarity index 92%
rename from osu.Game/Screens/Multiplayer/MatchCreate.cs
rename to osu.Game/Screens/Multi/MatchCreate.cs
index ca6b814cb9..db528ae2a3 100644
--- a/osu.Game/Screens/Multiplayer/MatchCreate.cs
+++ b/osu.Game/Screens/Multi/MatchCreate.cs
@@ -4,7 +4,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class MatchCreate : ScreenWhiteBox
     {
diff --git a/osu.Game/Screens/Multiplayer/ModeTypeInfo.cs b/osu.Game/Screens/Multi/ModeTypeInfo.cs
similarity index 98%
rename from osu.Game/Screens/Multiplayer/ModeTypeInfo.cs
rename to osu.Game/Screens/Multi/ModeTypeInfo.cs
index 08e96ba55d..1c175bad44 100644
--- a/osu.Game/Screens/Multiplayer/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/ModeTypeInfo.cs
@@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Online.Multiplayer;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class ModeTypeInfo : Container
     {
diff --git a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs b/osu.Game/Screens/Multi/ParticipantInfo.cs
similarity index 99%
rename from osu.Game/Screens/Multiplayer/ParticipantInfo.cs
rename to osu.Game/Screens/Multi/ParticipantInfo.cs
index ff1887fa17..a5d2bcd78a 100644
--- a/osu.Game/Screens/Multiplayer/ParticipantInfo.cs
+++ b/osu.Game/Screens/Multi/ParticipantInfo.cs
@@ -12,7 +12,7 @@ using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Users;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class ParticipantInfo : Container
     {
diff --git a/osu.Game/Screens/Multiplayer/RoomInspector.cs b/osu.Game/Screens/Multi/RoomInspector.cs
similarity index 99%
rename from osu.Game/Screens/Multiplayer/RoomInspector.cs
rename to osu.Game/Screens/Multi/RoomInspector.cs
index bfc4a44ed5..d8c5d5ea9a 100644
--- a/osu.Game/Screens/Multiplayer/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/RoomInspector.cs
@@ -21,7 +21,7 @@ using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
 
-namespace osu.Game.Screens.Multiplayer
+namespace osu.Game.Screens.Multi
 {
     public class RoomInspector : Container
     {

From 2d206d258a1fb0d13cfac45cfc0272a492d9701e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 20:44:24 -0300
Subject: [PATCH 060/177] Move multiplayer screen components to
 osu.Game/Screens/Multi/Components

---
 osu.Game.Tests/Visual/TestCaseDrawableRoom.cs    |   1 +
 osu.Game.Tests/Visual/TestCaseRoomInspector.cs   |   1 +
 osu.Game/Screens/Multi/.DS_Store                 | Bin 6148 -> 0 bytes
 .../Multi/{ => Components}/DrawableGameType.cs   |   2 +-
 .../Multi/{ => Components}/DrawableRoom.cs       |   6 +++---
 .../Multi/{ => Components}/ModeTypeInfo.cs       |   4 ++--
 .../Multi/{ => Components}/ParticipantInfo.cs    |   4 ++--
 .../Multi/{ => Components}/RoomInspector.cs      |   6 +++---
 8 files changed, 13 insertions(+), 11 deletions(-)
 delete mode 100644 osu.Game/Screens/Multi/.DS_Store
 rename osu.Game/Screens/Multi/{ => Components}/DrawableGameType.cs (96%)
 rename osu.Game/Screens/Multi/{ => Components}/DrawableRoom.cs (99%)
 rename osu.Game/Screens/Multi/{ => Components}/ModeTypeInfo.cs (98%)
 rename osu.Game/Screens/Multi/{ => Components}/ParticipantInfo.cs (99%)
 rename osu.Game/Screens/Multi/{ => Components}/RoomInspector.cs (99%)

diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index 958dbd3c74..3c61efd689 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -9,6 +9,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Multi;
+using osu.Game.Screens.Multi.Components;
 using osu.Game.Users;
 
 namespace osu.Game.Tests.Visual
diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index d0752cfc76..4cf19924e9 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Multi;
+using osu.Game.Screens.Multi.Components;
 using osu.Game.Users;
 
 namespace osu.Game.Tests.Visual
diff --git a/osu.Game/Screens/Multi/.DS_Store b/osu.Game/Screens/Multi/.DS_Store
deleted file mode 100644
index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 6148
zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3
zem<@ulZcFPQ@L2!n>{z**<q8>++&mCkOWA81W14cNZ<zv;LbK1Poaz?KmsK2CSc!(
z0ynLxE!0092;Krf2c+FF_Fe*7ECH>lEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ
zLs35+`xjp>T0<F0fCPF1$Cyrb|F7^5{eNG?83~ZUUlGt@xh*qZDeu<Z%US-OSsOPv
j)R!Z4KLME7ReXlK;d!wEw5GODWMKRea10D2@KpjYNUI8I

diff --git a/osu.Game/Screens/Multi/DrawableGameType.cs b/osu.Game/Screens/Multi/Components/DrawableGameType.cs
similarity index 96%
rename from osu.Game/Screens/Multi/DrawableGameType.cs
rename to osu.Game/Screens/Multi/Components/DrawableGameType.cs
index 0d43e78f2b..3406e179d4 100644
--- a/osu.Game/Screens/Multi/DrawableGameType.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableGameType.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
 using osu.Game.Graphics;
 using osu.Game.Online.Multiplayer;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Components
 {
     public class DrawableGameType : CircularContainer, IHasTooltip
     {
diff --git a/osu.Game/Screens/Multi/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
similarity index 99%
rename from osu.Game/Screens/Multi/DrawableRoom.cs
rename to osu.Game/Screens/Multi/Components/DrawableRoom.cs
index 0ec27e0ff2..040fbaf593 100644
--- a/osu.Game/Screens/Multi/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
@@ -1,8 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using OpenTK;
-using OpenTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
@@ -17,8 +15,10 @@ using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
+using OpenTK;
+using OpenTK.Graphics;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Components
 {
     public class DrawableRoom : OsuClickableContainer
     {
diff --git a/osu.Game/Screens/Multi/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
similarity index 98%
rename from osu.Game/Screens/Multi/ModeTypeInfo.cs
rename to osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
index 1c175bad44..e3aba685a7 100644
--- a/osu.Game/Screens/Multi/ModeTypeInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs
@@ -1,14 +1,14 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using OpenTK;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;
 using osu.Game.Online.Multiplayer;
+using OpenTK;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Components
 {
     public class ModeTypeInfo : Container
     {
diff --git a/osu.Game/Screens/Multi/ParticipantInfo.cs b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs
similarity index 99%
rename from osu.Game/Screens/Multi/ParticipantInfo.cs
rename to osu.Game/Screens/Multi/Components/ParticipantInfo.cs
index a5d2bcd78a..ab404488f1 100644
--- a/osu.Game/Screens/Multi/ParticipantInfo.cs
+++ b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs
@@ -3,7 +3,6 @@
 
 using System.Collections.Generic;
 using System.Linq;
-using OpenTK;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
@@ -11,8 +10,9 @@ using osu.Framework.Graphics.Shapes;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Users;
+using OpenTK;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Components
 {
     public class ParticipantInfo : Container
     {
diff --git a/osu.Game/Screens/Multi/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
similarity index 99%
rename from osu.Game/Screens/Multi/RoomInspector.cs
rename to osu.Game/Screens/Multi/Components/RoomInspector.cs
index d8c5d5ea9a..92910e8301 100644
--- a/osu.Game/Screens/Multi/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -2,8 +2,6 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Linq;
-using OpenTK;
-using OpenTK.Graphics;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
@@ -20,8 +18,10 @@ using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
+using OpenTK;
+using OpenTK.Graphics;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Components
 {
     public class RoomInspector : Container
     {

From bc9ac8f72a2a281cf43510cd8b81c825a423c27f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 20:47:25 -0300
Subject: [PATCH 061/177] Move multiplayer screens to
 osu.Game/Screens/Multi/Screens

---
 osu.Game/Screens/Menu/MainMenu.cs                   | 1 +
 osu.Game/Screens/Multi/{ => Screens}/Lobby.cs       | 2 +-
 osu.Game/Screens/Multi/{ => Screens}/Match.cs       | 6 +++---
 osu.Game/Screens/Multi/{ => Screens}/MatchCreate.cs | 2 +-
 4 files changed, 6 insertions(+), 5 deletions(-)
 rename osu.Game/Screens/Multi/{ => Screens}/Lobby.cs (90%)
 rename osu.Game/Screens/Multi/{ => Screens}/Match.cs (96%)
 rename osu.Game/Screens/Multi/{ => Screens}/MatchCreate.cs (91%)

diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index b09b53063a..2baa26c676 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -15,6 +15,7 @@ using osu.Game.Screens.Charts;
 using osu.Game.Screens.Direct;
 using osu.Game.Screens.Edit;
 using osu.Game.Screens.Multi;
+using osu.Game.Screens.Multi.Screens;
 using osu.Game.Screens.Select;
 using osu.Game.Screens.Tournament;
 
diff --git a/osu.Game/Screens/Multi/Lobby.cs b/osu.Game/Screens/Multi/Screens/Lobby.cs
similarity index 90%
rename from osu.Game/Screens/Multi/Lobby.cs
rename to osu.Game/Screens/Multi/Screens/Lobby.cs
index 80e682e9f9..dcda40e0d7 100644
--- a/osu.Game/Screens/Multi/Lobby.cs
+++ b/osu.Game/Screens/Multi/Screens/Lobby.cs
@@ -4,7 +4,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Screens
 {
     public class Lobby : ScreenWhiteBox
     {
diff --git a/osu.Game/Screens/Multi/Match.cs b/osu.Game/Screens/Multi/Screens/Match.cs
similarity index 96%
rename from osu.Game/Screens/Multi/Match.cs
rename to osu.Game/Screens/Multi/Screens/Match.cs
index eee302a115..4ba7fe9f6a 100644
--- a/osu.Game/Screens/Multi/Match.cs
+++ b/osu.Game/Screens/Multi/Screens/Match.cs
@@ -3,14 +3,14 @@
 
 using System;
 using System.Collections.Generic;
+using osu.Framework.Graphics;
 using osu.Framework.Screens;
 using osu.Game.Screens.Backgrounds;
 using osu.Game.Screens.Play;
-using OpenTK.Graphics;
 using osu.Game.Screens.Select;
-using osu.Framework.Graphics;
+using OpenTK.Graphics;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Screens
 {
     public class Match : ScreenWhiteBox
     {
diff --git a/osu.Game/Screens/Multi/MatchCreate.cs b/osu.Game/Screens/Multi/Screens/MatchCreate.cs
similarity index 91%
rename from osu.Game/Screens/Multi/MatchCreate.cs
rename to osu.Game/Screens/Multi/Screens/MatchCreate.cs
index db528ae2a3..6b4e26d5e5 100644
--- a/osu.Game/Screens/Multi/MatchCreate.cs
+++ b/osu.Game/Screens/Multi/Screens/MatchCreate.cs
@@ -4,7 +4,7 @@
 using System;
 using System.Collections.Generic;
 
-namespace osu.Game.Screens.Multi
+namespace osu.Game.Screens.Multi.Screens
 {
     public class MatchCreate : ScreenWhiteBox
     {

From a86843ccc93c1a88d65f527daf011c7b12db1674 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 20:49:42 -0300
Subject: [PATCH 062/177] Remove unused usings caused by multiplayer folder
 restructuring.

---
 osu.Game.Tests/Visual/TestCaseDrawableRoom.cs  | 1 -
 osu.Game.Tests/Visual/TestCaseRoomInspector.cs | 1 -
 osu.Game/Screens/Menu/MainMenu.cs              | 1 -
 3 files changed, 3 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index 3c61efd689..bb5bf93a69 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
-using osu.Game.Screens.Multi;
 using osu.Game.Screens.Multi.Components;
 using osu.Game.Users;
 
diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index 4cf19924e9..cb1425ca69 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
 using osu.Game.Beatmaps;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
-using osu.Game.Screens.Multi;
 using osu.Game.Screens.Multi.Components;
 using osu.Game.Users;
 
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 2baa26c676..907ad81111 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -14,7 +14,6 @@ using osu.Game.Screens.Backgrounds;
 using osu.Game.Screens.Charts;
 using osu.Game.Screens.Direct;
 using osu.Game.Screens.Edit;
-using osu.Game.Screens.Multi;
 using osu.Game.Screens.Multi.Screens;
 using osu.Game.Screens.Select;
 using osu.Game.Screens.Tournament;

From b9adeeb063400477e53e093438c51fb9714d9786 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 21:35:26 -0300
Subject: [PATCH 063/177] Add ScreenBreadcrumbControl.

---
 .../Visual/TestCaseScreenBreadcrumbs.cs       | 108 ++++++++++++++++++
 .../UserInterface/ScreenBreadcrumbControl.cs  |  78 +++++++++++++
 2 files changed, 186 insertions(+)
 create mode 100644 osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
 create mode 100644 osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
new file mode 100644
index 0000000000..5055b0e114
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
@@ -0,0 +1,108 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Screens;
+using OpenTK;
+
+namespace osu.Game.Tests.Visual
+{
+    [TestFixture]
+    public class TestCaseScreenBreadcrumbs : OsuTestCase
+    {
+        private readonly ScreenBreadcrumbControl<TestScreen> breadcrumbs;
+
+        public TestCaseScreenBreadcrumbs()
+        {
+            TestScreen startScreen;
+            OsuSpriteText titleText;
+
+            Children = new Drawable[]
+            {
+                new FillFlowContainer
+                {
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
+                    Direction = FillDirection.Vertical,
+                    Spacing = new Vector2(10),
+                    Children = new Drawable[]
+                    {
+                        breadcrumbs = new ScreenBreadcrumbControl<TestScreen>
+                        {
+                            RelativeSizeAxes = Axes.X,
+                        },
+                        titleText = new OsuSpriteText(),
+                    },
+                },
+                startScreen = new TestScreenOne(),
+            };
+
+            breadcrumbs.OnScreenChanged += s => titleText.Text = $"Changed to {s.ToString()}";
+            breadcrumbs.CurrentScreen = startScreen;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            breadcrumbs.StripColour = colours.Blue;
+        }
+
+        private abstract class TestScreen : OsuScreen
+        {
+            protected abstract string Title { get; }
+            protected abstract string NextTitle { get; }
+            protected abstract TestScreen CreateNextScreen();
+
+            public override string ToString() => Title;
+
+            protected TestScreen()
+            {
+                Child = new FillFlowContainer
+                {
+                    Anchor = Anchor.Centre,
+                    Origin = Anchor.Centre,
+                    AutoSizeAxes = Axes.Both,
+                    Direction = FillDirection.Vertical,
+                    Spacing = new Vector2(10),
+                    Children = new Drawable[]
+                    {
+                        new OsuSpriteText
+                        {
+                            Anchor = Anchor.TopCentre,
+                            Origin = Anchor.TopCentre,
+                            Text = Title,
+                        },
+                        new TriangleButton
+                        {
+                            Anchor = Anchor.TopCentre,
+                            Origin = Anchor.TopCentre,
+                            Width = 100,
+                            Text = $"Push {NextTitle}",
+                            Action = () => Push(CreateNextScreen()),
+                        },
+                    },
+                };
+            }
+        }
+
+        private class TestScreenOne : TestScreen
+        {
+            protected override string Title => @"Screen One";
+            protected override string NextTitle => @"Two";
+            protected override TestScreen CreateNextScreen() => new TestScreenTwo();
+        }
+
+        private class TestScreenTwo : TestScreen
+        {
+            protected override string Title => @"Screen Two";
+            protected override string NextTitle => @"One";
+            protected override TestScreen CreateNextScreen() => new TestScreenOne();
+        }
+    }
+}
diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
new file mode 100644
index 0000000000..fbdb27a81c
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -0,0 +1,78 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Screens;
+
+namespace osu.Game.Graphics.UserInterface
+{
+    public class ScreenBreadcrumbControl : ScreenBreadcrumbControl<Screen>
+    {
+    }
+
+    public class ScreenBreadcrumbControl<T> : BreadcrumbControl<T> where T : Screen
+    {
+        private T currentScreen;
+        public T CurrentScreen
+        {
+            get { return currentScreen; }
+            set
+            {
+                if (value == currentScreen) return;
+
+                if (CurrentScreen != null)
+                {
+                    CurrentScreen.Exited -= onExited;
+                    CurrentScreen.ModePushed -= onPushed;
+                }
+                else
+                {
+                    // this is the first screen in the stack, so call the initial onPushed
+                    currentScreen = value;
+                    onPushed(CurrentScreen);
+                }
+
+                currentScreen = value;
+
+                if (CurrentScreen != null)
+                {
+                    CurrentScreen.Exited += onExited;
+                    CurrentScreen.ModePushed += onPushed;
+                    Current.Value = CurrentScreen;
+                    OnScreenChanged?.Invoke(CurrentScreen);
+                }
+            }
+        }
+
+        public event Action<T> OnScreenChanged;
+
+        public ScreenBreadcrumbControl()
+        {
+            Current.ValueChanged += s =>
+            {
+                if (s != CurrentScreen)
+                {
+                    CurrentScreen = s;
+                    s.MakeCurrent();
+                }
+            };
+        }
+
+        private void onExited(Screen screen)
+        {
+            CurrentScreen = screen as T;
+        }
+
+        private void onPushed(Screen screen)
+        {
+            var newScreen = screen as T;
+
+            Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem);
+            AddItem(newScreen);
+
+            CurrentScreen = newScreen;
+        }
+    }
+}

From a294f187ee90a187604d7e3788842f46c2e592fe Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 21:52:26 -0300
Subject: [PATCH 064/177] Add steps and asserts to TestCaseScreenBreadcrumbs.

---
 .../Visual/TestCaseScreenBreadcrumbs.cs       | 38 +++++++++++++++++--
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
index 5055b0e114..6bb6b09746 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Tests.Visual
     public class TestCaseScreenBreadcrumbs : OsuTestCase
     {
         private readonly ScreenBreadcrumbControl<TestScreen> breadcrumbs;
+        private TestScreen currentScreen, changedScreen = null;
 
         public TestCaseScreenBreadcrumbs()
         {
@@ -40,11 +41,29 @@ namespace osu.Game.Tests.Visual
                         titleText = new OsuSpriteText(),
                     },
                 },
-                startScreen = new TestScreenOne(),
+                currentScreen = startScreen = new TestScreenOne(),
             };
 
-            breadcrumbs.OnScreenChanged += s => titleText.Text = $"Changed to {s.ToString()}";
-            breadcrumbs.CurrentScreen = startScreen;
+            breadcrumbs.OnScreenChanged += s =>
+            {
+                titleText.Text = $"Changed to {s.ToString()}";
+                changedScreen = s;
+            };
+
+            AddStep(@"make start current", () => breadcrumbs.CurrentScreen = startScreen);
+            assertCurrent();
+            pushNext();
+            assertCurrent();
+            pushNext();
+            assertCurrent();
+
+            AddStep(@"make start current", () =>
+            {
+                startScreen.MakeCurrent();
+                currentScreen = startScreen;
+            });
+
+            assertCurrent();
         }
 
         [BackgroundDependencyLoader]
@@ -53,6 +72,9 @@ namespace osu.Game.Tests.Visual
             breadcrumbs.StripColour = colours.Blue;
         }
 
+        private void pushNext() => AddStep(@"push next screen", () => currentScreen = currentScreen.PushNext());
+        private void assertCurrent() => AddAssert(@"assert the current screen is correct", () => currentScreen == changedScreen);
+
         private abstract class TestScreen : OsuScreen
         {
             protected abstract string Title { get; }
@@ -61,6 +83,14 @@ namespace osu.Game.Tests.Visual
 
             public override string ToString() => Title;
 
+            public TestScreen PushNext()
+            {
+                TestScreen screen = CreateNextScreen();
+                Push(screen);
+
+                return screen;
+            }
+
             protected TestScreen()
             {
                 Child = new FillFlowContainer
@@ -84,7 +114,7 @@ namespace osu.Game.Tests.Visual
                             Origin = Anchor.TopCentre,
                             Width = 100,
                             Text = $"Push {NextTitle}",
-                            Action = () => Push(CreateNextScreen()),
+                            Action = () => PushNext(),
                         },
                     },
                 };

From 6f7d0c19efe10356d91a243a08d13ed01112a2a0 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 22:02:22 -0300
Subject: [PATCH 065/177] Remove redundant default value.

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

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
index 6bb6b09746..3e0dd9e018 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual
     public class TestCaseScreenBreadcrumbs : OsuTestCase
     {
         private readonly ScreenBreadcrumbControl<TestScreen> breadcrumbs;
-        private TestScreen currentScreen, changedScreen = null;
+        private TestScreen currentScreen, changedScreen;
 
         public TestCaseScreenBreadcrumbs()
         {

From d87ac5a1cbb0afb35979f3f1a150eddef7bafb44 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 22:12:25 -0300
Subject: [PATCH 066/177] Create the drawable hierarchy for DrawableRoom in
 load.

---
 osu.Game/Screens/Multiplayer/DrawableRoom.cs | 49 +++++++++-----------
 1 file changed, 22 insertions(+), 27 deletions(-)

diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index d53100526f..b9f464ff78 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -29,11 +29,10 @@ namespace osu.Game.Screens.Multiplayer
         private const float cover_width = 145;
 
         private readonly Box sideStrip;
-        private readonly Container coverContainer;
-        private readonly OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
-        private readonly FillFlowContainer<OsuSpriteText> beatmapInfoFlow;
-        private readonly ParticipantInfo participantInfo;
-        private readonly ModeTypeInfo modeTypeInfo;
+        private Container coverContainer;
+        private OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
+        private ParticipantInfo participantInfo;
+        private ModeTypeInfo modeTypeInfo;
 
         private readonly Bindable<string> nameBind = new Bindable<string>();
         private readonly Bindable<User> hostBind = new Bindable<User>();
@@ -62,6 +61,19 @@ namespace osu.Game.Screens.Multiplayer
                 Radius = 5,
             };
 
+            sideStrip = new Box
+            {
+                RelativeSizeAxes = Axes.Y,
+                Width = side_strip_width,
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation)
+        {
+            this.localisation = localisation;
+            this.colours = colours;
+
             Children = new Drawable[]
             {
                 new Box
@@ -69,11 +81,7 @@ namespace osu.Game.Screens.Multiplayer
                     RelativeSizeAxes = Axes.Both,
                     Colour = OsuColour.FromHex(@"212121"),
                 },
-                sideStrip = new Box
-                {
-                    RelativeSizeAxes = Axes.Y,
-                    Width = side_strip_width,
-                },
+                sideStrip,
                 new Container
                 {
                     Width = cover_width,
@@ -133,10 +141,11 @@ namespace osu.Game.Screens.Multiplayer
                                     TextSize = 14,
                                     Font = @"Exo2.0-Bold",
                                 },
-                                beatmapInfoFlow = new FillFlowContainer<OsuSpriteText>
+                                new FillFlowContainer<OsuSpriteText>
                                 {
                                     RelativeSizeAxes = Axes.X,
                                     AutoSizeAxes = Axes.Y,
+                                    Colour = colours.Gray9,
                                     Direction = FillDirection.Horizontal,
                                     Children = new[]
                                     {
@@ -170,7 +179,9 @@ namespace osu.Game.Screens.Multiplayer
 
             nameBind.ValueChanged += displayName;
             hostBind.ValueChanged += displayUser;
+            statusBind.ValueChanged += displayStatus;
             typeBind.ValueChanged += displayGameType;
+            beatmapBind.ValueChanged += displayBeatmap;
             participantsBind.ValueChanged += displayParticipants;
 
             nameBind.BindTo(Room.Name);
@@ -181,22 +192,6 @@ namespace osu.Game.Screens.Multiplayer
             participantsBind.BindTo(Room.Participants);
         }
 
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours, LocalisationEngine localisation)
-        {
-            this.localisation = localisation;
-            this.colours = colours;
-
-            beatmapInfoFlow.Colour = colours.Gray9;
-
-            //binded here instead of ctor because dependencies are needed
-            statusBind.ValueChanged += displayStatus;
-            beatmapBind.ValueChanged += displayBeatmap;
-
-            statusBind.TriggerChange();
-            beatmapBind.TriggerChange();
-        }
-
         private void displayName(string value)
         {
             name.Text = value;

From ec53927d8e77730bd0cc6c820d6287f004018180 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 22:48:07 -0300
Subject: [PATCH 067/177] Add selection to DrawableRoom.

---
 osu.Game.Tests/Visual/TestCaseDrawableRoom.cs |   3 +
 .../Graphics/UserInterface/SelectionState.cs  |  11 +
 osu.Game/Rulesets/Edit/HitObjectMask.cs       |   7 +-
 osu.Game/Screens/Multiplayer/DrawableRoom.cs  | 364 +++++++++---------
 4 files changed, 206 insertions(+), 179 deletions(-)
 create mode 100644 osu.Game/Graphics/UserInterface/SelectionState.cs

diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index 25f8ba06c4..65e782b828 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Multiplayer;
@@ -111,6 +112,7 @@ namespace osu.Game.Tests.Visual
                 }
             });
 
+            AddStep(@"select", () => first.State = SelectionState.Selected);
             AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name");
             AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
             AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying());
@@ -121,6 +123,7 @@ namespace osu.Game.Tests.Visual
                 new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } },
                 new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } },
             });
+            AddStep(@"deselect", () => first.State = SelectionState.NotSelected);
         }
 
         [BackgroundDependencyLoader]
diff --git a/osu.Game/Graphics/UserInterface/SelectionState.cs b/osu.Game/Graphics/UserInterface/SelectionState.cs
new file mode 100644
index 0000000000..079ae343eb
--- /dev/null
+++ b/osu.Game/Graphics/UserInterface/SelectionState.cs
@@ -0,0 +1,11 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Graphics.UserInterface
+{
+    public enum SelectionState
+    {
+        NotSelected,
+        Selected
+    }
+}
diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs
index ad7c27ad80..61fb700dd3 100644
--- a/osu.Game/Rulesets/Edit/HitObjectMask.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs
@@ -6,6 +6,7 @@ using osu.Framework;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Primitives;
 using osu.Framework.Input;
+using osu.Game.Graphics.UserInterface;
 using osu.Game.Rulesets.Objects.Drawables;
 using OpenTK;
 
@@ -137,10 +138,4 @@ namespace osu.Game.Rulesets.Edit
         /// </summary>
         public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
     }
-
-    public enum SelectionState
-    {
-        NotSelected,
-        Selected
-    }
 }
diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index b9f464ff78..16eb93d3f0 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -1,6 +1,8 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
+using osu.Framework;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Allocation;
@@ -15,24 +17,23 @@ using osu.Game.Beatmaps.Drawables;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
 using osu.Game.Online.Multiplayer;
 using osu.Game.Users;
 
 namespace osu.Game.Screens.Multiplayer
 {
-    public class DrawableRoom : OsuClickableContainer
+    public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>
     {
+        private const float corner_radius = 5;
+        private const float selection_border_width = 4;
         private const float transition_duration = 100;
         private const float content_padding = 10;
         private const float height = 100;
         private const float side_strip_width = 5;
         private const float cover_width = 145;
 
-        private readonly Box sideStrip;
-        private Container coverContainer;
-        private OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
-        private ParticipantInfo participantInfo;
-        private ModeTypeInfo modeTypeInfo;
+        private readonly Box selectionBox;
 
         private readonly Bindable<string> nameBind = new Bindable<string>();
         private readonly Bindable<User> hostBind = new Bindable<User>();
@@ -41,148 +42,227 @@ namespace osu.Game.Screens.Multiplayer
         private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
         private readonly Bindable<User[]> participantsBind = new Bindable<User[]>();
 
-        private OsuColour colours;
-        private LocalisationEngine localisation;
-
         public readonly Room Room;
 
+        private SelectionState state;
+
+        public SelectionState State
+        {
+            get { return state; }
+            set
+            {
+                if (value == state) return;
+                state = value;
+
+                if (state == SelectionState.Selected)
+                    selectionBox.FadeIn(transition_duration);
+                else
+                    selectionBox.FadeOut(transition_duration);
+
+                StateChanged?.Invoke(State);
+            }
+        }
+
+        public event Action<SelectionState> StateChanged;
+
         public DrawableRoom(Room room)
         {
             Room = room;
 
             RelativeSizeAxes = Axes.X;
-            Height = height;
-            CornerRadius = 5;
+            Height = height + selection_border_width * 2;
+            CornerRadius = corner_radius + selection_border_width / 2;
             Masking = true;
-            EdgeEffect = new EdgeEffectParameters
-            {
-                Type = EdgeEffectType.Shadow,
-                Colour = Color4.Black.Opacity(40),
-                Radius = 5,
-            };
 
-            sideStrip = new Box
+            // create selectionBox here so State can be set before being loaded
+            selectionBox = new Box
             {
-                RelativeSizeAxes = Axes.Y,
-                Width = side_strip_width,
+                RelativeSizeAxes = Axes.Both,
+                Alpha = 0f,
             };
         }
 
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
-            this.localisation = localisation;
-            this.colours = colours;
+            Box sideStrip;
+            Container coverContainer;
+            OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist;
+            ParticipantInfo participantInfo;
+            ModeTypeInfo modeTypeInfo;
 
             Children = new Drawable[]
             {
-                new Box
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Colour = OsuColour.FromHex(@"212121"),
-                },
-                sideStrip,
-                new Container
-                {
-                    Width = cover_width,
-                    RelativeSizeAxes = Axes.Y,
-                    Masking = true,
-                    Margin = new MarginPadding { Left = side_strip_width },
-                    Children = new Drawable[]
-                    {
-                        new Box
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            Colour = Color4.Black,
-                        },
-                        coverContainer = new Container
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                        },
-                    },
-                },
+                selectionBox,
                 new Container
                 {
                     RelativeSizeAxes = Axes.Both,
-                    Padding = new MarginPadding
+                    Padding = new MarginPadding(selection_border_width),
+                    Child = new Container
                     {
-                        Vertical = content_padding,
-                        Left = side_strip_width + cover_width + content_padding,
-                        Right = content_padding,
-                    },
-                    Children = new Drawable[]
-                    {
-                        new FillFlowContainer
+                        RelativeSizeAxes = Axes.Both,
+                        Masking = true,
+                        CornerRadius = corner_radius,
+                        EdgeEffect = new EdgeEffectParameters
                         {
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Direction = FillDirection.Vertical,
-                            Spacing = new Vector2(5f),
-                            Children = new Drawable[]
+                            Type = EdgeEffectType.Shadow,
+                            Colour = Color4.Black.Opacity(40),
+                            Radius = 5,
+                        },
+                        Children = new Drawable[]
+                        {
+                            new Box
                             {
-                                name = new OsuSpriteText
-                                {
-                                    TextSize = 18,
-                                },
-                                participantInfo = new ParticipantInfo(),
+                                RelativeSizeAxes = Axes.Both,
+                                Colour = OsuColour.FromHex(@"212121"),
                             },
-                        },
-                        new FillFlowContainer
-                        {
-                            Anchor = Anchor.BottomLeft,
-                            Origin = Anchor.BottomLeft,
-                            RelativeSizeAxes = Axes.X,
-                            AutoSizeAxes = Axes.Y,
-                            Direction = FillDirection.Vertical,
-                            Children = new Drawable[]
+                            sideStrip = new Box
                             {
-                                status = new OsuSpriteText
+                                RelativeSizeAxes = Axes.Y,
+                                Width = side_strip_width,
+                            },
+                            new Container
+                            {
+                                Width = cover_width,
+                                RelativeSizeAxes = Axes.Y,
+                                Masking = true,
+                                Margin = new MarginPadding { Left = side_strip_width },
+                                Children = new Drawable[]
                                 {
-                                    TextSize = 14,
-                                    Font = @"Exo2.0-Bold",
-                                },
-                                new FillFlowContainer<OsuSpriteText>
-                                {
-                                    RelativeSizeAxes = Axes.X,
-                                    AutoSizeAxes = Axes.Y,
-                                    Colour = colours.Gray9,
-                                    Direction = FillDirection.Horizontal,
-                                    Children = new[]
+                                    new Box
                                     {
-                                        beatmapTitle = new OsuSpriteText
-                                        {
-                                            TextSize = 14,
-                                            Font = @"Exo2.0-BoldItalic",
-                                        },
-                                        beatmapDash = new OsuSpriteText
-                                        {
-                                            TextSize = 14,
-                                            Font = @"Exo2.0-BoldItalic",
-                                        },
-                                        beatmapArtist = new OsuSpriteText
-                                        {
-                                            TextSize = 14,
-                                            Font = @"Exo2.0-RegularItalic",
-                                        },
+                                        RelativeSizeAxes = Axes.Both,
+                                        Colour = Color4.Black,
+                                    },
+                                    coverContainer = new Container
+                                    {
+                                        RelativeSizeAxes = Axes.Both,
+                                    },
+                                },
+                            },
+                            new Container
+                            {
+                                RelativeSizeAxes = Axes.Both,
+                                Padding = new MarginPadding
+                                {
+                                    Vertical = content_padding,
+                                    Left = side_strip_width + cover_width + content_padding,
+                                    Right = content_padding,
+                                },
+                                Children = new Drawable[]
+                                {
+                                    new FillFlowContainer
+                                    {
+                                        RelativeSizeAxes = Axes.X,
+                                        AutoSizeAxes = Axes.Y,
+                                        Direction = FillDirection.Vertical,
+                                        Spacing = new Vector2(5f),
+                                        Children = new Drawable[]
+                                        {
+                                            name = new OsuSpriteText
+                                            {
+                                                TextSize = 18,
+                                            },
+                                            participantInfo = new ParticipantInfo(),
+                                        },
+                                    },
+                                    new FillFlowContainer
+                                    {
+                                        Anchor = Anchor.BottomLeft,
+                                        Origin = Anchor.BottomLeft,
+                                        RelativeSizeAxes = Axes.X,
+                                        AutoSizeAxes = Axes.Y,
+                                        Direction = FillDirection.Vertical,
+                                        Children = new Drawable[]
+                                        {
+                                            status = new OsuSpriteText
+                                            {
+                                                TextSize = 14,
+                                                Font = @"Exo2.0-Bold",
+                                            },
+                                            new FillFlowContainer<OsuSpriteText>
+                                            {
+                                                RelativeSizeAxes = Axes.X,
+                                                AutoSizeAxes = Axes.Y,
+                                                Colour = colours.Gray9,
+                                                Direction = FillDirection.Horizontal,
+                                                Children = new[]
+                                                {
+                                                    beatmapTitle = new OsuSpriteText
+                                                    {
+                                                        TextSize = 14,
+                                                        Font = @"Exo2.0-BoldItalic",
+                                                    },
+                                                    beatmapDash = new OsuSpriteText
+                                                    {
+                                                        TextSize = 14,
+                                                        Font = @"Exo2.0-BoldItalic",
+                                                    },
+                                                    beatmapArtist = new OsuSpriteText
+                                                    {
+                                                        TextSize = 14,
+                                                        Font = @"Exo2.0-RegularItalic",
+                                                    },
+                                                },
+                                            },
+                                        },
+                                    },
+                                    modeTypeInfo = new ModeTypeInfo
+                                    {
+                                        Anchor = Anchor.BottomRight,
+                                        Origin = Anchor.BottomRight,
                                     },
                                 },
                             },
-                        },
-                        modeTypeInfo = new ModeTypeInfo
-                        {
-                            Anchor = Anchor.BottomRight,
-                            Origin = Anchor.BottomRight,
                         },
                     },
                 },
             };
 
-            nameBind.ValueChanged += displayName;
-            hostBind.ValueChanged += displayUser;
-            statusBind.ValueChanged += displayStatus;
-            typeBind.ValueChanged += displayGameType;
-            beatmapBind.ValueChanged += displayBeatmap;
-            participantsBind.ValueChanged += displayParticipants;
+            nameBind.ValueChanged += n => name.Text = n;
+            hostBind.ValueChanged += h => participantInfo.Host = h;
+            typeBind.ValueChanged += m => modeTypeInfo.Type = m;
+            participantsBind.ValueChanged += p => participantInfo.Participants = p;
+
+            statusBind.ValueChanged += s =>
+            {
+                status.Text = s.Message;
+
+                foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status })
+                    d.FadeColour(s.GetAppropriateColour(colours), 100);
+            };
+
+            beatmapBind.ValueChanged += b =>
+            {
+                modeTypeInfo.Beatmap = b;
+
+                if (b != null)
+                {
+                    coverContainer.FadeIn(transition_duration);
+
+                    LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
+                    {
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        FillMode = FillMode.Fill,
+                        OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
+                    }, coverContainer.Add);
+
+                    beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
+                    beatmapDash.Text = @" - ";
+                    beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
+                }
+                else
+                {
+                    coverContainer.FadeOut(transition_duration);
+
+                    beatmapTitle.Current = null;
+                    beatmapArtist.Current = null;
+
+                    beatmapTitle.Text = "Changing map";
+                    beatmapDash.Text = beatmapArtist.Text = string.Empty;
+                }
+            };
 
             nameBind.BindTo(Room.Name);
             hostBind.BindTo(Room.Host);
@@ -191,67 +271,5 @@ namespace osu.Game.Screens.Multiplayer
             beatmapBind.BindTo(Room.Beatmap);
             participantsBind.BindTo(Room.Participants);
         }
-
-        private void displayName(string value)
-        {
-            name.Text = value;
-        }
-
-        private void displayUser(User value)
-        {
-            participantInfo.Host = value;
-        }
-
-        private void displayStatus(RoomStatus value)
-        {
-            if (value == null) return;
-            status.Text = value.Message;
-
-            foreach (Drawable d in new Drawable[] { sideStrip, status })
-                d.FadeColour(value.GetAppropriateColour(colours), 100);
-        }
-
-        private void displayGameType(GameType value)
-        {
-            modeTypeInfo.Type = value;
-        }
-
-        private void displayBeatmap(BeatmapInfo value)
-        {
-            modeTypeInfo.Beatmap = value;
-
-            if (value != null)
-            {
-                coverContainer.FadeIn(transition_duration);
-
-                LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
-                {
-                    Anchor = Anchor.Centre,
-                    Origin = Anchor.Centre,
-                    FillMode = FillMode.Fill,
-                    OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
-                },
-                coverContainer.Add);
-
-                beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
-                beatmapDash.Text = @" - ";
-                beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
-            }
-            else
-            {
-                coverContainer.FadeOut(transition_duration);
-
-                beatmapTitle.Current = null;
-                beatmapArtist.Current = null;
-
-                beatmapTitle.Text = "Changing map";
-                beatmapDash.Text = beatmapArtist.Text = string.Empty;
-            }
-        }
-
-        private void displayParticipants(User[] value)
-        {
-            participantInfo.Participants = value;
-        }
     }
 }

From a241ff1c051447338442ae9e30d3b2b402182b0d Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 10 May 2018 22:50:03 -0300
Subject: [PATCH 068/177] Cleanup.

---
 osu.Game/Screens/Multiplayer/DrawableRoom.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
index 16eb93d3f0..a67ead74a1 100644
--- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs
+++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs
@@ -45,7 +45,6 @@ namespace osu.Game.Screens.Multiplayer
         public readonly Room Room;
 
         private SelectionState state;
-
         public SelectionState State
         {
             get { return state; }

From 2bab08c4373a96360d48a8ed183ff79c1385b3d7 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 14:07:17 +0900
Subject: [PATCH 069/177] Fix post-merge errors

---
 osu-framework                 |  2 +-
 osu.Game/Beatmaps/Beatmap.cs  | 44 -----------------------------------
 osu.Game/Beatmaps/IBeatmap.cs |  6 +++++
 3 files changed, 7 insertions(+), 45 deletions(-)

diff --git a/osu-framework b/osu-framework
index 0773d895d9..8c4f232694 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 0773d895d9aa0729995cd4a23efc28238e35ceed
+Subproject commit 8c4f23269447d9ce21a5dbd3a0fd4f6caae9ab38
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 9517d44fcd..84897853d8 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -12,50 +12,6 @@ using osu.Game.IO.Serialization.Converters;
 
 namespace osu.Game.Beatmaps
 {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
     /// <summary>
     /// A Beatmap containing converted HitObjects.
     /// </summary>
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index 4676f056fa..fe20bce98a 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -41,6 +41,12 @@ namespace osu.Game.Beatmaps
         /// </summary>
         IEnumerable<HitObject> HitObjects { get; }
 
+        /// <summary>
+        /// Returns statistics for the <see cref="HitObjects"/> contained in this beatmap.
+        /// </summary>
+        /// <returns></returns>
+        IEnumerable<BeatmapStatistic> GetStatistics();
+
         /// <summary>
         /// Creates a shallow-clone of this beatmap and returns it.
         /// </summary>

From 816ad5c4269c080611156ed6cc798207188fb86a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 14:10:53 +0900
Subject: [PATCH 070/177] Pass down ruleset to the buffered wedge

---
 osu.Game/Screens/Select/BeatmapInfoWedge.cs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 7a8a04bd43..c88e01562f 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Screens.Select
 
         private void loadBeatmap()
         {
-            LoadComponentAsync(new BufferedWedgeInfo(beatmap)
+            LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value)
             {
                 Shear = -Shear,
                 Depth = Info?.Depth + 1 ?? 0,
@@ -114,18 +114,18 @@ namespace osu.Game.Screens.Select
             private UnicodeBindableString titleBinding;
             private UnicodeBindableString artistBinding;
 
-            private RulesetInfo ruleset;
+            private readonly RulesetInfo ruleset;
 
-            public BufferedWedgeInfo(WorkingBeatmap working)
+            public BufferedWedgeInfo(WorkingBeatmap working, RulesetInfo userRuleset)
             {
                 this.working = working;
+
+                ruleset = userRuleset ?? working.BeatmapInfo.Ruleset;
             }
 
             [BackgroundDependencyLoader(true)]
-            private void load([NotNull] LocalisationEngine localisation, [CanBeNull] OsuGame osuGame)
+            private void load([NotNull] LocalisationEngine localisation)
             {
-                ruleset = osuGame?.Ruleset.Value ?? working.BeatmapInfo.Ruleset;
-
                 var beatmapInfo = working.BeatmapInfo;
                 var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
 

From fd9796d08c1c3aa74d7691f8825f353fefeb5b7a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 14:13:52 +0900
Subject: [PATCH 071/177] Remove some unnecessary changes

---
 osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index c88e01562f..236b1310e1 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -123,8 +123,8 @@ namespace osu.Game.Screens.Select
                 ruleset = userRuleset ?? working.BeatmapInfo.Ruleset;
             }
 
-            [BackgroundDependencyLoader(true)]
-            private void load([NotNull] LocalisationEngine localisation)
+            [BackgroundDependencyLoader]
+            private void load(LocalisationEngine localisation)
             {
                 var beatmapInfo = working.BeatmapInfo;
                 var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();

From 8216bc311af892c778cad4ae46366755b6fd8bda Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:30:26 +0900
Subject: [PATCH 072/177] Restructure HitWindows to allow outside setting

---
 osu.Game/Rulesets/Objects/HitObject.cs  | 25 +++++++++++++++----------
 osu.Game/Rulesets/Objects/HitWindows.cs |  4 ++--
 2 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 13fa61f536..70604855e9 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -51,16 +51,10 @@ namespace osu.Game.Rulesets.Objects
 
         private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
 
-        private HitWindows hitWindows;
-
         /// <summary>
         /// The hit windows for this <see cref="HitObject"/>.
         /// </summary>
-        public HitWindows HitWindows
-        {
-            get => hitWindows ?? (hitWindows = new HitWindows(overallDifficulty));
-            protected set => hitWindows = value;
-        }
+        public HitWindows HitWindows { get; set; }
 
         private readonly SortedList<HitObject> nestedHitObjects = new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
 
@@ -78,7 +72,11 @@ namespace osu.Game.Rulesets.Objects
 
             nestedHitObjects.Clear();
             CreateNestedHitObjects();
-            nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty));
+            nestedHitObjects.ForEach(h =>
+            {
+                h.HitWindows = HitWindows;
+                h.ApplyDefaults(controlPointInfo, difficulty);
+            });
         }
 
         protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@@ -89,8 +87,9 @@ namespace osu.Game.Rulesets.Objects
             Kiai = effectPoint.KiaiMode;
             SampleControlPoint = samplePoint;
 
-            overallDifficulty = difficulty.OverallDifficulty;
-            hitWindows = null;
+            if (HitWindows == null)
+                HitWindows = CreateHitWindows();
+            HitWindows?.SetDifficulty(difficulty.OverallDifficulty);
         }
 
         protected virtual void CreateNestedHitObjects()
@@ -98,5 +97,11 @@ namespace osu.Game.Rulesets.Objects
         }
 
         protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
+
+        /// <summary>
+        /// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
+        /// </summary>
+        /// <returns></returns>
+        protected virtual HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs
index bf0878a408..7610593d6a 100644
--- a/osu.Game/Rulesets/Objects/HitWindows.cs
+++ b/osu.Game/Rulesets/Objects/HitWindows.cs
@@ -63,10 +63,10 @@ namespace osu.Game.Rulesets.Objects
         public bool AllowsOk;
 
         /// <summary>
-        /// Constructs hit windows by fitting a parameter to a 2-part piecewise linear function for each hit window.
+        /// Sets hit windows with values that correspond to a difficulty parameter.
         /// </summary>
         /// <param name="difficulty">The parameter.</param>
-        public HitWindows(double difficulty)
+        public virtual void SetDifficulty(double difficulty)
         {
             Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
             Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);

From faa82f17cc1edd90bfa30c6dc53473b55b219335 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:30:50 +0900
Subject: [PATCH 073/177] Pass original hitobject HitWindows during conversion

---
 osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 4 +++-
 osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs     | 8 +++++---
 osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 8 +++++++-
 3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index c8a7402904..4f7c52860f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
 
         protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
         {
-
             BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
 
             int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
@@ -85,7 +84,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 yield break;
 
             foreach (ManiaHitObject obj in objects)
+            {
+                obj.HitWindows = original.HitWindows;
                 yield return obj;
+            }
         }
 
         private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 1cd4ec5668..54720548b4 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -40,7 +40,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     RepeatSamples = curveData.RepeatSamples,
                     RepeatCount = curveData.RepeatCount,
                     Position = positionData?.Position ?? Vector2.Zero,
-                    NewCombo = comboData?.NewCombo ?? false
+                    NewCombo = comboData?.NewCombo ?? false,
+                    HitWindows = original.HitWindows
                 };
             }
             else if (endTimeData != null)
@@ -50,8 +51,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     StartTime = original.StartTime,
                     Samples = original.Samples,
                     EndTime = endTimeData.EndTime,
-
                     Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
+                    HitWindows = original.HitWindows
                 };
             }
             else
@@ -61,7 +62,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     StartTime = original.StartTime,
                     Samples = original.Samples,
                     Position = positionData?.Position ?? Vector2.Zero,
-                    NewCombo = comboData?.NewCombo ?? false
+                    NewCombo = comboData?.NewCombo ?? false,
+                    HitWindows = original.HitWindows
                 };
             }
         }
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index eeb0fa1871..58efc3336d 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -132,7 +132,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                             {
                                 StartTime = j,
                                 Samples = currentSamples,
-                                IsStrong = strong
+                                IsStrong = strong,
+                                HitWindows = obj.HitWindows
                             };
                         }
                         else
@@ -142,6 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                                 StartTime = j,
                                 Samples = currentSamples,
                                 IsStrong = strong,
+                                HitWindows = obj.HitWindows
                             };
                         }
 
@@ -157,6 +159,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                         IsStrong = strong,
                         Duration = taikoDuration,
                         TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
+                        HitWindows = obj.HitWindows
                     };
                 }
             }
@@ -171,6 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                     IsStrong = strong,
                     Duration = endTimeData.Duration,
                     RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
+                    HitWindows = obj.HitWindows
                 };
             }
             else
@@ -184,6 +188,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                         StartTime = obj.StartTime,
                         Samples = obj.Samples,
                         IsStrong = strong,
+                        HitWindows = obj.HitWindows
                     };
                 }
                 else
@@ -193,6 +198,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                         StartTime = obj.StartTime,
                         Samples = obj.Samples,
                         IsStrong = strong,
+                        HitWindows = obj.HitWindows
                     };
                 }
             }

From 9c65d347ba271ff0775b96ca19c6b121681c8261 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:31:12 +0900
Subject: [PATCH 074/177] Add convert taiko HitWindows

---
 .../Objects/Legacy/Taiko/ConvertHit.cs        |  2 ++
 .../Objects/Legacy/Taiko/ConvertSlider.cs     |  2 ++
 .../Objects/Legacy/Taiko/ConvertSpinner.cs    |  2 ++
 .../Legacy/Taiko/ConvertTaikoHitWindows.cs    | 28 +++++++++++++++++++
 4 files changed, 34 insertions(+)
 create mode 100644 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs

diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
index 72d18664bf..bdcd1abf3c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
@@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     internal sealed class ConvertHit : HitObject, IHasCombo
     {
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
index e810e687bd..0237ab9619 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
@@ -11,5 +11,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasCombo
     {
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index 193e50aed6..899f383bee 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
         public double EndTime { get; set; }
 
         public double Duration => EndTime - StartTime;
+
+        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs
new file mode 100644
index 0000000000..d3e8718307
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Objects.Legacy.Taiko
+{
+    public class ConvertTaikoHitWindows : HitWindows
+    {
+        private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
+        {
+            { HitResult.Great, (100, 70, 40) },
+            { HitResult.Good, (240, 160, 100) },
+            { HitResult.Meh, (270, 190, 140) },
+            { HitResult.Miss, (400, 400, 400) },
+        };
+
+        public override void SetDifficulty(double difficulty)
+        {
+            Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
+            Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
+            Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
+            Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
+        }
+    }
+}

From c052ee71c10b222a979b4449054f829b4d806f7c Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:31:36 +0900
Subject: [PATCH 075/177] Add convert osu HitWindows

---
 .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs |  2 ++
 .../Legacy/Osu/ConvertOsuHitWindows.cs        | 28 +++++++++++++++++++
 .../Objects/Legacy/Osu/ConvertSlider.cs       |  2 ++
 .../Objects/Legacy/Osu/ConvertSpinner.cs      |  2 ++
 4 files changed, 34 insertions(+)
 create mode 100644 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs

diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
index f83173f498..bb5b0393e5 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
         public float Y => Position.Y;
 
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs
new file mode 100644
index 0000000000..07d90f7012
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Objects.Legacy.Osu
+{
+    public class ConvertOsuHitWindows : HitWindows
+    {
+        private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
+        {
+            { HitResult.Great, (160, 100, 40) },
+            { HitResult.Good, (280, 200, 120) },
+            { HitResult.Meh, (400, 300, 200) },
+            { HitResult.Miss, (400, 400, 400) },
+        };
+
+        public override void SetDifficulty(double difficulty)
+        {
+            Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
+            Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
+            Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
+            Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
+        }
+    }
+}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
index c6033d482c..cbe044605b 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
@@ -18,5 +18,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
         public float Y => Position.Y;
 
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index 28aac6862e..b580c5d1b3 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -20,5 +20,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
         public float X => Position.X;
 
         public float Y => Position.Y;
+
+        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
     }
 }

From 856329179612d679f3da8f1ae4f34341a8c080d0 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:32:00 +0900
Subject: [PATCH 076/177] Add convert mania HitWindows

---
 .../Objects/Legacy/Mania/ConvertHit.cs        |  2 ++
 .../Objects/Legacy/Mania/ConvertHold.cs       |  2 ++
 .../Legacy/Mania/ConvertManiaHitWindows.cs    | 32 +++++++++++++++++++
 .../Objects/Legacy/Mania/ConvertSlider.cs     |  2 ++
 .../Objects/Legacy/Mania/ConvertSpinner.cs    |  2 ++
 5 files changed, 40 insertions(+)
 create mode 100644 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs

diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
index 0db5a1dff1..6bf28c5e45 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
         public float X { get; set; }
 
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index e3b35e2f8e..087e5d5c12 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -12,5 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
         public double EndTime { get; set; }
 
         public double Duration => EndTime - StartTime;
+
+        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs
new file mode 100644
index 0000000000..072b45dc64
--- /dev/null
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Objects.Legacy.Mania
+{
+    public class ConvertManiaHitWindows : HitWindows
+    {
+        private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
+        {
+            { HitResult.Perfect, (44.8, 38.8, 27.8) },
+            { HitResult.Great, (128, 98, 68 ) },
+            { HitResult.Good, (194, 164, 134) },
+            { HitResult.Ok, (254, 224, 194) },
+            { HitResult.Meh, (302, 272, 242) },
+            { HitResult.Miss, (376, 346, 316) },
+        };
+
+        public override void SetDifficulty(double difficulty)
+        {
+            Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
+            Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
+            Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
+            Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
+            Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
+            Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
+        }
+    }
+}
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
index 32fb197c62..08ce465619 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
@@ -13,5 +13,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
         public float X { get; set; }
 
         public bool NewCombo { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index c9b3046698..031ba54c59 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -15,5 +15,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
         public double Duration => EndTime - StartTime;
 
         public float X { get; set; }
+
+        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
     }
 }

From 0cdd39b8b8b06487339891af566d42d43cd36550 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:32:13 +0900
Subject: [PATCH 077/177] Add todo because TailNote shouldn't hardcore lenience

---
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 4cf22ccd39..12e3d2de51 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -103,6 +103,7 @@ namespace osu.Game.Rulesets.Mania.Objects
             /// <summary>
             /// Lenience of release hit windows. This is to make cases where the hold note release
             /// is timed alongside presses of other hit objects less awkward.
+            /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
             /// </summary>
             private const double release_window_lenience = 1.5;
 

From 5245d2d4268633565006069190bd14776e3d03eb Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:36:53 +0900
Subject: [PATCH 078/177] Add info about when CreateHitWindows is invoked

---
 osu.Game/Rulesets/Objects/HitObject.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index 70604855e9..d7d827bc97 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Objects
 
         /// <summary>
         /// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
+        /// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter"/>.
         /// </summary>
-        /// <returns></returns>
         protected virtual HitWindows CreateHitWindows() => null;
     }
 }

From bfad101d17ad04d8ee1093e730fa3cc2ce6bff52 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:42:34 +0900
Subject: [PATCH 079/177] Standardise naming

---
 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs            | 2 +-
 .../Mania/{ConvertManiaHitWindows.cs => ConvertHitWindows.cs}   | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs           | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs         | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs        | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs              | 2 +-
 .../Osu/{ConvertOsuHitWindows.cs => ConvertHitWindows.cs}       | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs           | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs          | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs            | 2 +-
 .../Taiko/{ConvertTaikoHitWindows.cs => ConvertHitWindows.cs}   | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs         | 2 +-
 osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs        | 2 +-
 13 files changed, 13 insertions(+), 13 deletions(-)
 rename osu.Game/Rulesets/Objects/Legacy/Mania/{ConvertManiaHitWindows.cs => ConvertHitWindows.cs} (96%)
 rename osu.Game/Rulesets/Objects/Legacy/Osu/{ConvertOsuHitWindows.cs => ConvertHitWindows.cs} (95%)
 rename osu.Game/Rulesets/Objects/Legacy/Taiko/{ConvertTaikoHitWindows.cs => ConvertHitWindows.cs} (95%)

diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
index 6bf28c5e45..939d3b9c93 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs
similarity index 96%
rename from osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs
rename to osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs
index 072b45dc64..131492ea12 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertManiaHitWindows.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring;
 
 namespace osu.Game.Rulesets.Objects.Legacy.Mania
 {
-    public class ConvertManiaHitWindows : HitWindows
+    public class ConvertHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index 087e5d5c12..22abc64b60 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public double Duration => EndTime - StartTime;
 
-        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
index 08ce465619..6bca5b717c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index 031ba54c59..1dc826af9b 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public float X { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertManiaHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
index bb5b0393e5..23955b2d23 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs
similarity index 95%
rename from osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs
rename to osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs
index 07d90f7012..fd86173372 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertOsuHitWindows.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring;
 
 namespace osu.Game.Rulesets.Objects.Legacy.Osu
 {
-    public class ConvertOsuHitWindows : HitWindows
+    public class ConvertHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
index cbe044605b..35b8c1c7dd 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index b580c5d1b3..73b8369aca 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public float Y => Position.Y;
 
-        protected override HitWindows CreateHitWindows() => new ConvertOsuHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
index bdcd1abf3c..11db086778 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
@@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     {
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs
similarity index 95%
rename from osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs
rename to osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs
index d3e8718307..6fbf7e122f 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertTaikoHitWindows.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring;
 
 namespace osu.Game.Rulesets.Objects.Legacy.Taiko
 {
-    public class ConvertTaikoHitWindows : HitWindows
+    public class ConvertHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
index 0237ab9619..95c69222b5 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
@@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     {
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index 899f383bee..7baea212ea 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
 
         public double Duration => EndTime - StartTime;
 
-        protected override HitWindows CreateHitWindows() => new ConvertTaikoHitWindows();
+        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
     }
 }

From 97a523584da0b8b686961cccc71763cb8189b50f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 15:52:51 +0900
Subject: [PATCH 080/177] Make HitWindows not return null by default

---
 osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 ++
 osu.Game/Rulesets/Objects/HitObject.cs            | 5 ++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 95ffd41518..548813fbd2 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Objects
 
             Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
         }
+
+        protected override HitWindows CreateHitWindows() => null;
     }
 
     public enum FruitVisualRepresentation
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index d7d827bc97..cd612a5387 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -100,8 +100,11 @@ namespace osu.Game.Rulesets.Objects
 
         /// <summary>
         /// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.
+        /// This can be null to indicate that the <see cref="HitObject"/> has no <see cref="HitWindows"/>.
+        /// <para>
         /// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter"/>.
+        /// </para>
         /// </summary>
-        protected virtual HitWindows CreateHitWindows() => null;
+        protected virtual HitWindows CreateHitWindows() => new HitWindows();
     }
 }

From 3e7fa45ad18b168fe3a0268f635143c8bbe9b05e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 16:12:56 +0900
Subject: [PATCH 081/177] Fix tests

---
 .../TestCaseManiaHitObjects.cs                   | 16 ++++++++++++----
 .../Beatmaps/Formats/OsuJsonDecoderTest.cs       |  3 ++-
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
index 281c2789af..dd81d015f1 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Tests.Visual;
 using OpenTK;
 using OpenTK.Graphics;
@@ -17,6 +18,9 @@ namespace osu.Game.Rulesets.Mania.Tests
     {
         public TestCaseManiaHitObjects()
         {
+            var hitWindows = new HitWindows();
+            hitWindows.SetDifficulty(5);
+
             Add(new FillFlowContainer
             {
                 Anchor = Anchor.Centre,
@@ -43,14 +47,14 @@ namespace osu.Game.Rulesets.Mania.Tests
                                 RelativeChildSize = new Vector2(1, 10000),
                                 Children = new[]
                                 {
-                                    new DrawableNote(new Note(), ManiaAction.Key1)
+                                    new DrawableNote(new Note { HitWindows = hitWindows }, ManiaAction.Key1)
                                     {
                                         Y = 5000,
                                         LifetimeStart = double.MinValue,
                                         LifetimeEnd = double.MaxValue,
                                         AccentColour = Color4.Red
                                     },
-                                    new DrawableNote(new Note(), ManiaAction.Key1)
+                                    new DrawableNote(new Note { HitWindows = hitWindows }, ManiaAction.Key1)
                                     {
                                         Y = 6000,
                                         LifetimeStart = double.MinValue,
@@ -77,13 +81,17 @@ namespace osu.Game.Rulesets.Mania.Tests
                                 RelativeChildSize = new Vector2(1, 10000),
                                 Children = new[]
                                 {
-                                    new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1)
+                                    new DrawableHoldNote(new HoldNote
+                                    {
+                                        Duration = 1000,
+                                        HitWindows = hitWindows
+                                    } , ManiaAction.Key1)
                                     {
                                         Y = 5000,
                                         Height = 1000,
                                         LifetimeStart = double.MinValue,
                                         LifetimeEnd = double.MaxValue,
-                                        AccentColour = Color4.Red
+                                        AccentColour = Color4.Red,
                                     }
                                 }
                             }
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 6e0cf6be2e..489c38c420 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -9,6 +9,7 @@ using osu.Game.Audio;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Formats;
 using osu.Game.IO.Serialization;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Tests.Resources;
 using OpenTK;
@@ -117,7 +118,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
         public void TestParity(string beatmap)
         {
             var legacy = decode(beatmap, out Beatmap json);
-            json.ShouldDeepEqual(legacy);
+            json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert();
         }
 
         /// <summary>

From fa403e4e2ae4a2a0ad36f5f2261239093994c2d3 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 11 May 2018 04:45:27 -0300
Subject: [PATCH 082/177] Add test step to test pushing after a previous screen
 is made current.

---
 osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs   | 3 +++
 osu.Game/Graphics/UserInterface/BreadcrumbControl.cs | 1 +
 2 files changed, 4 insertions(+)

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
index 3e0dd9e018..f477a0b97e 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System.Linq;
 using NUnit.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
@@ -64,6 +65,8 @@ namespace osu.Game.Tests.Visual
             });
 
             assertCurrent();
+            pushNext();
+            AddAssert(@"assert there are only 2 items", () => breadcrumbs.Items.Count() == 2);
         }
 
         [BackgroundDependencyLoader]
diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
index 3f59eeca97..6b7f235b34 100644
--- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs
@@ -47,6 +47,7 @@ namespace osu.Game.Graphics.UserInterface
 
             public override bool HandleKeyboardInput => State == Visibility.Visible;
             public override bool HandleMouseInput => State == Visibility.Visible;
+            public override bool IsRemovable => true;
 
             private Visibility state;
 

From 4fc887b25f1addd4cc08d7cdcb6e93df23c3ad16 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 11 May 2018 21:40:36 +0900
Subject: [PATCH 083/177] Add a pressing effect to make mouse up response feel
 good

---
 osu.Game/Overlays/Mods/ModButton.cs | 21 +++++++++++++++++++--
 1 file changed, 19 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 3f1541aee3..1d012b1288 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -116,6 +116,7 @@ namespace osu.Game.Overlays.Mods
         }
 
         private Mod mod;
+        private Container scaleContainer;
 
         public Mod Mod
         {
@@ -147,8 +148,16 @@ namespace osu.Game.Overlays.Mods
 
         public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex);
 
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        {
+            scaleContainer.ScaleTo(0.9f, 800, Easing.Out);
+            return base.OnMouseDown(state, args);
+        }
+
         protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
         {
+            scaleContainer.ScaleTo(1, 500, Easing.OutElastic);
+
             // only trigger the event if we are inside the area of the button
             if (Contains(ToScreenSpace(state.Mouse.Position - Position)))
             {
@@ -162,6 +171,7 @@ namespace osu.Game.Overlays.Mods
                         break;
                 }
             }
+
             return true;
         }
 
@@ -179,7 +189,8 @@ namespace osu.Game.Overlays.Mods
                 start = Mods.Length - 1;
 
             for (int i = start; i < Mods.Length && i >= 0; i += direction)
-                if (SelectAt(i)) return;
+                if (SelectAt(i))
+                    return;
 
             Deselect();
         }
@@ -245,8 +256,14 @@ namespace osu.Game.Overlays.Mods
                     Anchor = Anchor.TopCentre,
                     Children = new Drawable[]
                     {
-                        iconsContainer = new Container<ModIcon>
+                        scaleContainer = new Container
                         {
+                            Child = iconsContainer = new Container<ModIcon>
+                            {
+                                RelativeSizeAxes = Axes.Both,
+                                Origin = Anchor.Centre,
+                                Anchor = Anchor.Centre,
+                            },
                             RelativeSizeAxes = Axes.Both,
                             Origin = Anchor.Centre,
                             Anchor = Anchor.Centre,

From 7cb0d328e60fbccc495b419fc69eed730b348881 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 11 May 2018 21:40:48 +0900
Subject: [PATCH 084/177] Make mods screen dynamically testable

---
 osu.Game.Tests/Visual/TestCaseMods.cs | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs
index dad8fb8fed..d3d21509fd 100644
--- a/osu.Game.Tests/Visual/TestCaseMods.cs
+++ b/osu.Game.Tests/Visual/TestCaseMods.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using System.ComponentModel;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
@@ -17,6 +18,7 @@ using osu.Game.Graphics.UserInterface;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Rulesets.Mania;
 using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.UI;
 using OpenTK.Graphics;
 
 namespace osu.Game.Tests.Visual
@@ -24,6 +26,19 @@ namespace osu.Game.Tests.Visual
     [Description("mod select and icon display")]
     public class TestCaseMods : OsuTestCase
     {
+        public override IReadOnlyList<Type> RequiredTypes => new[]
+        {
+            typeof(ModSelectOverlay),
+            typeof(ModDisplay),
+            typeof(ModSection),
+            typeof(ModIcon),
+            typeof(ModButton),
+            typeof(ModButtonEmpty),
+            typeof(DifficultyReductionSection),
+            typeof(DifficultyIncreaseSection),
+            typeof(SpecialSection),
+        };
+
         private const string unranked_suffix = " (Unranked)";
 
         private RulesetStore rulesets;
@@ -66,7 +81,8 @@ namespace osu.Game.Tests.Visual
                 Ruleset ruleset = rulesetInfo.CreateInstance();
                 AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo);
 
-                switch (ruleset) {
+                switch (ruleset)
+                {
                     case OsuRuleset or:
                         testOsuMods(or);
                         break;

From aa5d5ab2a844c5834134b159c1960b2c3f397dca Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 11 May 2018 21:48:35 +0900
Subject: [PATCH 085/177] Fix readonly field

---
 osu.Game/Overlays/Mods/ModButton.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs
index 1d012b1288..f4e0e3db04 100644
--- a/osu.Game/Overlays/Mods/ModButton.cs
+++ b/osu.Game/Overlays/Mods/ModButton.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Mods
         }
 
         private Mod mod;
-        private Container scaleContainer;
+        private readonly Container scaleContainer;
 
         public Mod Mod
         {

From 8ca67f63ece8c3469557ebe6278f67e22d6f5a57 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 20:31:57 +0900
Subject: [PATCH 086/177] Fix score statistics not being read

---
 .../Scoring/Legacy/LegacyScoreParser.cs       | 27 ++++++++++---------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index d5ab856697..1cc30afe7b 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -49,18 +49,21 @@ namespace osu.Game.Rulesets.Scoring.Legacy
                 score.User = new User { Username = sr.ReadString() };
                 /* var localScoreChecksum = */
                 sr.ReadString();
-                /* score.Count300 = */
-                sr.ReadUInt16();
-                /* score.Count100 = */
-                sr.ReadUInt16();
-                /* score.Count50 = */
-                sr.ReadUInt16();
-                /* score.CountGeki = */
-                sr.ReadUInt16();
-                /* score.CountKatu = */
-                sr.ReadUInt16();
-                /* score.CountMiss = */
-                sr.ReadUInt16();
+
+                var count300 = sr.ReadUInt16();
+                var count100 = sr.ReadUInt16();
+                var count50 = sr.ReadUInt16();
+                var countGeki = sr.ReadUInt16();
+                var countKatu = sr.ReadUInt16();
+                var countMiss = sr.ReadUInt16();
+
+                score.Statistics[HitResult.Great] = count300;
+                score.Statistics[HitResult.Good] = count100;
+                score.Statistics[HitResult.Meh] = count50;
+                score.Statistics[HitResult.Perfect] = countGeki;
+                score.Statistics[HitResult.Ok] = countKatu;
+                score.Statistics[HitResult.Miss] = countMiss;
+
                 score.TotalScore = sr.ReadInt32();
                 score.MaxCombo = sr.ReadUInt16();
                 /* score.Perfect = */

From 7e7a5f8964972a045cf4f6ef2f47332937a49f82 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 20:32:06 +0900
Subject: [PATCH 087/177] Fix score accuracy not being populated

---
 .../Scoring/Legacy/LegacyScoreParser.cs       | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index 1cc30afe7b..38873c4df1 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -84,6 +84,34 @@ namespace osu.Game.Rulesets.Scoring.Legacy
                     /*OnlineId =*/
                     sr.ReadInt32();
 
+                switch (score.Ruleset.ID)
+                {
+                    case 0:
+                    {
+                        int totalHits = count50 + count100 + count300 + countMiss;
+                        score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1;
+                        break;
+                    }
+                    case 1:
+                    {
+                        int totalHits = count50 + count100 + count300 + countMiss;
+                        score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1;
+                        break;
+                    }
+                    case 2:
+                    {
+                        int totalHits = count50 + count100 + count300 + countMiss + countKatu;
+                        score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1;
+                        break;
+                    }
+                    case 3:
+                    {
+                        int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu;
+                        score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1;
+                        break;
+                    }
+                }
+
                 using (var replayInStream = new MemoryStream(compressedReplay))
                 {
                     byte[] properties = new byte[5];

From 224f1a0810f75b6d292ee49fa0d0a0c02be60579 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 21:48:10 +0900
Subject: [PATCH 088/177] Fix incorrect osu! difficulty calculator combo count

---
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 6b9214d9dc..103fdeadea 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
 
             beatmapMaxCombo = Beatmap.HitObjects.Count();
-            beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count) + 1;
+            beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Count();
         }
 
         public override double Calculate(Dictionary<string, double> categoryRatings = null)

From a33724899973bd46204dfa981d6bc82ba66f0526 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 22:06:16 +0900
Subject: [PATCH 089/177] Fix incorrect count

---
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 103fdeadea..b4324c77fc 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
             countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
 
             beatmapMaxCombo = Beatmap.HitObjects.Count();
-            beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Count();
+            // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
+            beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
         }
 
         public override double Calculate(Dictionary<string, double> categoryRatings = null)

From cfa18bdf1fa2823dd6ead2b9c37a3ce67be4b746 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 11 May 2018 22:09:53 +0900
Subject: [PATCH 090/177] Fix song progress time counters wrapping after an
 hour

Resolves #2466.
Supersedes and closes #2487.
---
 osu.Game/Screens/Play/SongProgressInfo.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs
index 5cc4b30950..b79c212ade 100644
--- a/osu.Game/Screens/Play/SongProgressInfo.cs
+++ b/osu.Game/Screens/Play/SongProgressInfo.cs
@@ -85,11 +85,13 @@ namespace osu.Game.Screens.Play
 
             if (currentSecond != previousSecond && songCurrentTime < songLength)
             {
-                timeCurrent.Text = TimeSpan.FromSeconds(currentSecond).ToString(songCurrentTime < 0 ? @"\-m\:ss" : @"m\:ss");
-                timeLeft.Text = TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime).ToString(@"\-m\:ss");
+                timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond));
+                timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime));
 
                 previousSecond = currentSecond;
             }
         }
+
+        private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}";
     }
 }

From a14531b9a4d0f4b101d5af1e91df2eb2877bcc1c Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 11 May 2018 22:57:36 +0900
Subject: [PATCH 091/177] Fix crash on startup when adjusting volume before
 volume control loaded

---
 osu.Game/Overlays/VolumeOverlay.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index da63495fec..f922c507f7 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -100,6 +100,8 @@ namespace osu.Game.Overlays
 
         public bool Adjust(GlobalAction action)
         {
+            if (!IsLoaded) return false;
+
             switch (action)
             {
                 case GlobalAction.DecreaseVolume:

From 41de02fc78e08513e9588a6d02090044155ed32f Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 11 May 2018 13:43:53 -0300
Subject: [PATCH 092/177] Make DrawableRooms select when they are clicked.

---
 osu.Game/Screens/Multi/Components/DrawableRoom.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
index f3ce3dcd8e..994b0e886b 100644
--- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.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.Input;
 using osu.Framework.Localisation;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;
@@ -79,6 +80,8 @@ namespace osu.Game.Screens.Multi.Components
                 RelativeSizeAxes = Axes.Both,
                 Alpha = 0f,
             };
+
+            Action += () => State = SelectionState.Selected;
         }
 
         [BackgroundDependencyLoader]

From 937ff50a5a4269431973066233d776fc16ace442 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Fri, 11 May 2018 13:56:27 -0300
Subject: [PATCH 093/177] Remove unused using.

---
 osu.Game/Screens/Multi/Components/DrawableRoom.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
index 994b0e886b..88a253d719 100644
--- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs
@@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
 using osu.Framework.Localisation;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.Drawables;

From 6ebe9f88e78b1c222fcecb6ba4b3ec5f0e003eed Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sat, 12 May 2018 11:11:09 +0900
Subject: [PATCH 094/177] Update framework with upstream fixes

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index 8c4f232694..f807997301 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit 8c4f23269447d9ce21a5dbd3a0fd4f6caae9ab38
+Subproject commit f8079973011b54e84e5c0e677fe2b56e55947666

From 416ed725977527718f4c5fe744e0a60181c0cd8e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Sat, 12 May 2018 15:46:25 +0900
Subject: [PATCH 095/177] Port osu-performance changes into
 OsuPerformanceCalculator

---
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 6b9214d9dc..a06e71e20c 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             aimValue *= approachRateFactor;
 
             if (mods.Any(h => h is OsuModHidden))
-                aimValue *= 1.18f;
+                aimValue *= 1.03f;
 
             if (mods.Any(h => h is OsuModFlashlight))
             {
@@ -152,6 +152,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
             if (beatmapMaxCombo > 0)
                 speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
 
+            if (mods.Any(m => m is OsuModHidden))
+                speedValue *= 1.18f;
+
             // Scale the speed value with accuracy _slightly_
             speedValue *= 0.5f + accuracy / 2.0f;
             // It is important to also consider accuracy difficulty when doing that

From df43fc6ff8defe26d52b002f19ebd20bb8515a90 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sun, 13 May 2018 01:31:17 +0900
Subject: [PATCH 096/177] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index eaa640972c..fac688633b 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit eaa640972ca9c67f0ea15a942035b5b3a78f1570
+Subproject commit fac688633b8fcf34ae5d0514c26b03e217161eb4

From 8a5bd27c2018e0ae4f980e77aea891f70b1ce562 Mon Sep 17 00:00:00 2001
From: ocboogie <boogie@mikulec.com>
Date: Sat, 12 May 2018 16:30:29 -0700
Subject: [PATCH 097/177] Add global key bindings for changing current ruleset

---
 .../Overlays/Toolbar/ToolbarModeSelector.cs   | 49 ++++++++++++++++++-
 1 file changed, 48 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 1da51e4a5a..4855c004c4 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -1,11 +1,14 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using System.Linq;
+using System.Collections.Generic;
 using osu.Framework.Allocation;
 using osu.Framework.Caching;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
 using OpenTK;
 using OpenTK.Graphics;
 using osu.Framework.Configuration;
@@ -14,7 +17,7 @@ using osu.Game.Rulesets;
 
 namespace osu.Game.Overlays.Toolbar
 {
-    public class ToolbarModeSelector : Container
+    public class ToolbarModeSelector : KeyBindingContainer<int>
     {
         private const float padding = 10;
 
@@ -22,6 +25,7 @@ namespace osu.Game.Overlays.Toolbar
         private readonly Drawable modeButtonLine;
         private ToolbarModeButton activeButton;
 
+        private int rulesetCount;
         private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
 
         public ToolbarModeSelector()
@@ -64,9 +68,50 @@ namespace osu.Game.Overlays.Toolbar
             };
         }
 
+        public override IEnumerable<osu.Framework.Input.Bindings.KeyBinding> DefaultKeyBindings
+        {
+            get
+            {
+                var keybinds = new List<osu.Framework.Input.Bindings.KeyBinding>();
+                for (int i = 0; i < Math.Min(rulesetCount, 10); i++)
+                {
+                    InputKey numberKey;
+                    if (i == 9)
+                        numberKey = InputKey.Number0;
+                    else
+                        numberKey = (InputKey)i + 110;
+
+                    keybinds.Add(new osu.Framework.Input.Bindings.KeyBinding(new[] { InputKey.Control, numberKey }, i));
+                }
+                return keybinds;
+            }
+        }
+
+        private class RulesetSwitcherInputHandler : Container, IKeyBindingHandler<int>
+        {
+            private Bindable<RulesetInfo> ruleset;
+            private RulesetStore rulesets;
+
+            public RulesetSwitcherInputHandler(Bindable<RulesetInfo> ruleset, RulesetStore rulesets)
+            {
+                this.ruleset = ruleset;
+                this.rulesets = rulesets;
+            }
+
+            public bool OnPressed(int action)
+            {
+                ruleset.Value = rulesets.GetRuleset(action);
+
+                return true;
+            }
+
+            public bool OnReleased(int action) => false;
+        }
+
         [BackgroundDependencyLoader(true)]
         private void load(RulesetStore rulesets, OsuGame game)
         {
+            this.rulesetCount = rulesets.AvailableRulesets.Count();
             foreach (var r in rulesets.AvailableRulesets)
             {
                 modeButtons.Add(new ToolbarModeButton
@@ -85,6 +130,8 @@ namespace osu.Game.Overlays.Toolbar
                 ruleset.BindTo(game.Ruleset);
             else
                 ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
+
+            Add(new RulesetSwitcherInputHandler(ruleset, rulesets));
         }
 
         public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput;

From 26f06a9ae1612e19bdc372873fd17ed84f70179e Mon Sep 17 00:00:00 2001
From: ocboogie <boogie@mikulec.com>
Date: Sat, 12 May 2018 17:25:15 -0700
Subject: [PATCH 098/177] Resolve linting issues in ToolbarModeSelector.cs

---
 osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 4855c004c4..889cf8885e 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -89,8 +89,8 @@ namespace osu.Game.Overlays.Toolbar
 
         private class RulesetSwitcherInputHandler : Container, IKeyBindingHandler<int>
         {
-            private Bindable<RulesetInfo> ruleset;
-            private RulesetStore rulesets;
+            private readonly Bindable<RulesetInfo> ruleset;
+            private readonly RulesetStore rulesets;
 
             public RulesetSwitcherInputHandler(Bindable<RulesetInfo> ruleset, RulesetStore rulesets)
             {
@@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Toolbar
         [BackgroundDependencyLoader(true)]
         private void load(RulesetStore rulesets, OsuGame game)
         {
-            this.rulesetCount = rulesets.AvailableRulesets.Count();
+            rulesetCount = rulesets.AvailableRulesets.Count();
             foreach (var r in rulesets.AvailableRulesets)
             {
                 modeButtons.Add(new ToolbarModeButton

From db0470243ad2f3e19da40b1f8dcd67d353872173 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sun, 13 May 2018 12:55:54 +0900
Subject: [PATCH 099/177] Fix nullref when changing ruleset at main menu

---
 osu.Game/Screens/Select/BeatmapInfoWedge.cs | 20 +++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 236b1310e1..97f6371cb2 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -88,17 +88,27 @@ namespace osu.Game.Screens.Select
 
         private void loadBeatmap()
         {
+            void updateState()
+            {
+                State = beatmap == null ? Visibility.Hidden : Visibility.Visible;
+
+                Info?.FadeOut(250);
+                Info?.Expire();
+            }
+
+            if (beatmap == null)
+            {
+                updateState();
+                return;
+            }
+
             LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value)
             {
                 Shear = -Shear,
                 Depth = Info?.Depth + 1 ?? 0,
             }, newInfo =>
             {
-                State = beatmap == null ? Visibility.Hidden : Visibility.Visible;
-
-                Info?.FadeOut(250);
-                Info?.Expire();
-
+                updateState();
                 Add(Info = newInfo);
             });
         }

From e1b8a1589bae578864766632fe7d6da7b77b9354 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 11:15:14 +0900
Subject: [PATCH 100/177] Fix TimeRate not being considered for performance
 calculation

---
 .../Scoring/OsuPerformanceCalculator.cs       | 24 +++++++++++++++----
 osu.Game/Beatmaps/DifficultyCalculator.cs     |  2 +-
 .../Rulesets/Scoring/PerformanceCalculator.cs | 15 ++++++++++++
 3 files changed, 35 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index a12bdf7f20..aa94572cb4 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -18,7 +18,17 @@ namespace osu.Game.Rulesets.Osu.Scoring
         private readonly int beatmapMaxCombo;
 
         private Mod[] mods;
+        
+        /// <summary>
+        /// Approach rate adjusted by mods.
+        /// </summary>
         private double realApproachRate;
+        
+        /// <summary>
+        /// Overall difficulty adjusted by mods.
+        /// </summary>
+        private double realOverallDifficulty;
+        
         private double accuracy;
         private int scoreMaxCombo;
         private int count300;
@@ -58,9 +68,13 @@ namespace osu.Game.Rulesets.Osu.Scoring
                 ar = Math.Min(10, ar * 1.4);
             if (mods.Any(m => m is OsuModEasy))
                 ar = Math.Max(0, ar / 2);
-            double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
-            realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
 
+            double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate;
+            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; 
+            
+            realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
+            realOverallDifficulty = (80 - 0.5 - hitWindow300) / 6;
+            
             // Custom multipliers for NoFail and SpunOut.
             double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
 
@@ -133,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             // Scale the aim value with accuracy _slightly_
             aimValue *= 0.5f + accuracy / 2.0f;
             // It is important to also consider accuracy difficulty when doing that
-            aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+            aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
 
             return aimValue;
         }
@@ -159,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             // Scale the speed value with accuracy _slightly_
             speedValue *= 0.5f + accuracy / 2.0f;
             // It is important to also consider accuracy difficulty when doing that
-            speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
+            speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
 
             return speedValue;
         }
@@ -181,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
 
             // Lots of arbitrary values from testing.
             // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
-            double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+            double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
 
             // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
             accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index 37155c09cd..5cac9ed923 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
         protected readonly IBeatmap Beatmap;
         protected readonly Mod[] Mods;
 
-        protected double TimeRate = 1;
+        protected double TimeRate { get; private set; } = 1;
 
         protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
         {
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
index 5b8f5f0d0f..b23e06e15c 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -2,7 +2,11 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Timing;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
 
 namespace osu.Game.Rulesets.Scoring
 {
@@ -14,6 +18,8 @@ namespace osu.Game.Rulesets.Scoring
         protected readonly IBeatmap Beatmap;
         protected readonly Score Score;
 
+        protected double TimeRate { get; private set; } = 1;
+
         protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
         {
             Score = score;
@@ -22,6 +28,15 @@ namespace osu.Game.Rulesets.Scoring
 
             var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
             diffCalc.Calculate(attributes);
+
+            ApplyMods(score.Mods);
+        }
+        
+        protected virtual void ApplyMods(Mod[] mods)
+        {
+            var clock = new StopwatchClock();
+            mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
+            TimeRate = clock.Rate;
         }
 
         public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);

From ce6b4cc2d30d60480793d1c94838f0f0e6500680 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 11:15:37 +0900
Subject: [PATCH 101/177] Add more attributes to OsuPerformanceCalculator's
 output

---
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index aa94572cb4..e927cc946e 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -99,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
                 categoryRatings.Add("Aim", aimValue);
                 categoryRatings.Add("Speed", speedValue);
                 categoryRatings.Add("Accuracy", accuracyValue);
+                categoryRatings.Add("OD", realOverallDifficulty);
+                categoryRatings.Add("AR", realApproachRate);
+                categoryRatings.Add("Max Combo", beatmapMaxCombo);
             }
 
             return totalValue;

From 8c86f170a928404e63800d552179fff236bb10e9 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 11:33:23 +0900
Subject: [PATCH 102/177] Fix mania testcases

---
 .../TestCaseManiaHitObjects.cs                | 22 +++++----
 .../TestCaseManiaPlayfield.cs                 | 47 +++++++++----------
 2 files changed, 34 insertions(+), 35 deletions(-)

diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
index dd81d015f1..a4109722d4 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
@@ -4,9 +4,10 @@
 using NUnit.Framework;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Objects;
 using osu.Game.Tests.Visual;
 using OpenTK;
 using OpenTK.Graphics;
@@ -18,8 +19,13 @@ namespace osu.Game.Rulesets.Mania.Tests
     {
         public TestCaseManiaHitObjects()
         {
-            var hitWindows = new HitWindows();
-            hitWindows.SetDifficulty(5);
+            Note note1 = new Note();
+            Note note2 = new Note();
+            HoldNote holdNote = new HoldNote { StartTime = 1000 };
+
+            note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+            note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+            holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
 
             Add(new FillFlowContainer
             {
@@ -47,14 +53,14 @@ namespace osu.Game.Rulesets.Mania.Tests
                                 RelativeChildSize = new Vector2(1, 10000),
                                 Children = new[]
                                 {
-                                    new DrawableNote(new Note { HitWindows = hitWindows }, ManiaAction.Key1)
+                                    new DrawableNote(note1, ManiaAction.Key1)
                                     {
                                         Y = 5000,
                                         LifetimeStart = double.MinValue,
                                         LifetimeEnd = double.MaxValue,
                                         AccentColour = Color4.Red
                                     },
-                                    new DrawableNote(new Note { HitWindows = hitWindows }, ManiaAction.Key1)
+                                    new DrawableNote(note2, ManiaAction.Key1)
                                     {
                                         Y = 6000,
                                         LifetimeStart = double.MinValue,
@@ -81,11 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests
                                 RelativeChildSize = new Vector2(1, 10000),
                                 Children = new[]
                                 {
-                                    new DrawableHoldNote(new HoldNote
-                                    {
-                                        Duration = 1000,
-                                        HitWindows = hitWindows
-                                    } , ManiaAction.Key1)
+                                    new DrawableHoldNote(holdNote, ManiaAction.Key1)
                                     {
                                         Y = 5000,
                                         Height = 1000,
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
index 053f478027..dff2b2d56a 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
@@ -8,6 +8,8 @@ using NUnit.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Configuration;
 using osu.Game.Rulesets.Mania.Beatmaps;
 using osu.Game.Rulesets.Mania.Configuration;
@@ -83,13 +85,16 @@ namespace osu.Game.Rulesets.Mania.Tests
 
                 int col = rng.Next(0, 4);
 
-                var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1)
+                var note = new Note { Column = col };
+                note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+                var drawableNote = new DrawableNote(note, ManiaAction.Key1)
                 {
                     AccentColour = playfield.Columns.ElementAt(col).AccentColour
                 };
 
-                playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
-                playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
+                playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
+                playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
             });
         }
 
@@ -162,32 +167,24 @@ namespace osu.Game.Rulesets.Mania.Tests
 
             for (double t = start_time; t <= start_time + duration; t += 100)
             {
-                playfield.Add(new DrawableNote(new Note
-                {
-                    StartTime = t,
-                    Column = 0
-                }, ManiaAction.Key1));
+                var note1 = new Note { StartTime = t, Column = 0 };
+                var note2 = new Note { StartTime = t, Column = 3 };
 
-                playfield.Add(new DrawableNote(new Note
-                {
-                    StartTime = t,
-                    Column = 3
-                }, ManiaAction.Key4));
+                note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+                note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+                playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
+                playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
             }
 
-            playfield.Add(new DrawableHoldNote(new HoldNote
-            {
-                StartTime = start_time,
-                Duration = duration,
-                Column = 1
-            }, ManiaAction.Key2));
+            var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
+            var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
 
-            playfield.Add(new DrawableHoldNote(new HoldNote
-            {
-                StartTime = start_time,
-                Duration = duration,
-                Column = 2
-            }, ManiaAction.Key3));
+            holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+            holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+            playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
+            playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
         }
     }
 }

From 327c7432be72ab79e882a6dc9d8e0c51d310d485 Mon Sep 17 00:00:00 2001
From: ocboogie <boogie@mikulec.com>
Date: Sun, 13 May 2018 19:33:52 -0700
Subject: [PATCH 103/177] Use OnKeyDown instead of a IKeyBindingHandler

---
 .../Overlays/Toolbar/ToolbarModeSelector.cs   | 66 ++++++-------------
 1 file changed, 21 insertions(+), 45 deletions(-)

diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 889cf8885e..7286cf3f1c 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -4,12 +4,14 @@
 using System;
 using System.Linq;
 using System.Collections.Generic;
+using osu.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Caching;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Bindings;
+using osu.Framework.Input;
 using OpenTK;
+using OpenTK.Input;
 using OpenTK.Graphics;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics.Shapes;
@@ -17,7 +19,7 @@ using osu.Game.Rulesets;
 
 namespace osu.Game.Overlays.Toolbar
 {
-    public class ToolbarModeSelector : KeyBindingContainer<int>
+    public class ToolbarModeSelector : Container
     {
         private const float padding = 10;
 
@@ -25,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar
         private readonly Drawable modeButtonLine;
         private ToolbarModeButton activeButton;
 
-        private int rulesetCount;
+        private RulesetStore rulesets;
         private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
 
         public ToolbarModeSelector()
@@ -68,50 +70,10 @@ namespace osu.Game.Overlays.Toolbar
             };
         }
 
-        public override IEnumerable<osu.Framework.Input.Bindings.KeyBinding> DefaultKeyBindings
-        {
-            get
-            {
-                var keybinds = new List<osu.Framework.Input.Bindings.KeyBinding>();
-                for (int i = 0; i < Math.Min(rulesetCount, 10); i++)
-                {
-                    InputKey numberKey;
-                    if (i == 9)
-                        numberKey = InputKey.Number0;
-                    else
-                        numberKey = (InputKey)i + 110;
-
-                    keybinds.Add(new osu.Framework.Input.Bindings.KeyBinding(new[] { InputKey.Control, numberKey }, i));
-                }
-                return keybinds;
-            }
-        }
-
-        private class RulesetSwitcherInputHandler : Container, IKeyBindingHandler<int>
-        {
-            private readonly Bindable<RulesetInfo> ruleset;
-            private readonly RulesetStore rulesets;
-
-            public RulesetSwitcherInputHandler(Bindable<RulesetInfo> ruleset, RulesetStore rulesets)
-            {
-                this.ruleset = ruleset;
-                this.rulesets = rulesets;
-            }
-
-            public bool OnPressed(int action)
-            {
-                ruleset.Value = rulesets.GetRuleset(action);
-
-                return true;
-            }
-
-            public bool OnReleased(int action) => false;
-        }
-
         [BackgroundDependencyLoader(true)]
         private void load(RulesetStore rulesets, OsuGame game)
         {
-            rulesetCount = rulesets.AvailableRulesets.Count();
+            this.rulesets = rulesets;
             foreach (var r in rulesets.AvailableRulesets)
             {
                 modeButtons.Add(new ToolbarModeButton
@@ -130,8 +92,22 @@ namespace osu.Game.Overlays.Toolbar
                 ruleset.BindTo(game.Ruleset);
             else
                 ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault();
+        }
 
-            Add(new RulesetSwitcherInputHandler(ruleset, rulesets));
+        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+        {
+            base.OnKeyDown(state, args);
+            if (!state.Keyboard.ControlPressed || args.Repeat || (int)args.Key < 109 ||  (int)args.Key > 118) {
+                return false;
+            }
+
+            RulesetInfo targetRuleset = rulesets.GetRuleset(args.Key == Key.Number0 ? 9 : (int)args.Key - 110);
+            if (targetRuleset == null || targetRuleset == ruleset.Value) {
+                return false;
+            }
+
+            ruleset.Value = targetRuleset;
+            return true;
         }
 
         public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput;

From ebd9d1a0376becee8ee0f71e93cca67daf9799e7 Mon Sep 17 00:00:00 2001
From: ocboogie <boogie@mikulec.com>
Date: Sun, 13 May 2018 19:43:26 -0700
Subject: [PATCH 104/177] Resolve linting issues in ToolbarModeSelector.cs

---
 osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index 7286cf3f1c..eeaa15d58a 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -1,10 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System;
 using System.Linq;
-using System.Collections.Generic;
-using osu.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Caching;
 using osu.Framework.Graphics;

From a81921118dac58379808e0c55a0b281c02ffdf5b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 11:52:22 +0900
Subject: [PATCH 105/177] Strip whitespaces

---
 .../Scoring/OsuPerformanceCalculator.cs              | 12 ++++++------
 osu.Game/Rulesets/Scoring/PerformanceCalculator.cs   |  2 +-
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index e927cc946e..4942a55004 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -18,17 +18,17 @@ namespace osu.Game.Rulesets.Osu.Scoring
         private readonly int beatmapMaxCombo;
 
         private Mod[] mods;
-        
+
         /// <summary>
         /// Approach rate adjusted by mods.
         /// </summary>
         private double realApproachRate;
-        
+
         /// <summary>
         /// Overall difficulty adjusted by mods.
         /// </summary>
         private double realOverallDifficulty;
-        
+
         private double accuracy;
         private int scoreMaxCombo;
         private int count300;
@@ -70,11 +70,11 @@ namespace osu.Game.Rulesets.Osu.Scoring
                 ar = Math.Max(0, ar / 2);
 
             double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate;
-            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; 
-            
+            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+
             realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
             realOverallDifficulty = (80 - 0.5 - hitWindow300) / 6;
-            
+
             // Custom multipliers for NoFail and SpunOut.
             double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
 
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
index b23e06e15c..f2c495fa5d 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Scoring
 
             ApplyMods(score.Mods);
         }
-        
+
         protected virtual void ApplyMods(Mod[] mods)
         {
             var clock = new StopwatchClock();

From 7d027098ec1c0fc5920ba85b8f85797127cf99b0 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 14:13:07 +0900
Subject: [PATCH 106/177] Fix drumroll completions always giving GREAT
 judgements

Due to requiredgoodhits/requiredgreathits being calculated prior to nested hitobjects.
---
 osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 64219c7b52..4c9ec5473b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// </summary>
         private double tickSpacing = 100;
 
+        private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
+
         protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
         {
             base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -47,9 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
             TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
 
             tickSpacing = timingPoint.BeatLength / TickRate;
-
-            RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty);
-            RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty);
+            overallDifficulty = difficulty.OverallDifficulty;
         }
 
         protected override void CreateNestedHitObjects()
@@ -57,6 +57,9 @@ namespace osu.Game.Rulesets.Taiko.Objects
             base.CreateNestedHitObjects();
 
             createTicks();
+
+            RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty);
+            RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty);
         }
 
         private void createTicks()

From f5068804d5c2dcf87493cd75bbfd7c929654993a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 15:14:29 +0900
Subject: [PATCH 107/177] Re-enable slider distance overflow

Fixes #2467.
---
 osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs | 24 +++++++++++++++++++
 osu.Game/Rulesets/Objects/SliderCurve.cs      | 10 ++++----
 2 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
index f7f73f74a5..cb1ea5cc5f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
@@ -93,12 +93,36 @@ namespace osu.Game.Rulesets.Osu.Tests
 
             AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
             AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
+
+            AddStep("Distance Overflow", () => testDistanceOverflow());
+            AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1));
         }
 
         private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
 
         private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
 
+        private void testDistanceOverflow(int repeats = 0)
+        {
+            var slider = new Slider
+            {
+                StartTime = Time.Current + 1000,
+                Position = new Vector2(239, 176),
+                ControlPoints = new List<Vector2>
+                {
+                    Vector2.Zero,
+                    new Vector2(154, 28),
+                    new Vector2(52, -34)
+                },
+                Distance = 700,
+                RepeatCount = repeats,
+                RepeatSamples = createEmptySamples(repeats),
+                StackHeight = 10
+            };
+
+            addSlider(slider, 2, 2);
+        }
+
         private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
 
         private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs
index 86fe74f9af..3932d8ed9d 100644
--- a/osu.Game/Rulesets/Objects/SliderCurve.cs
+++ b/osu.Game/Rulesets/Objects/SliderCurve.cs
@@ -99,11 +99,9 @@ namespace osu.Game.Rulesets.Objects
                 cumulativeLength.Add(l);
             }
 
-            //TODO: Figure out if the following code is needed in some cases. Judging by the map
-            //      "Transform" http://osu.ppy.sh/s/484689 it seems like we should _not_ be doing this.
             // Lengthen slider curves that are too short compared to what's
             // in the .osu file.
-            /*if (l < Length && calculatedPath.Count > 1)
+            if (l < Distance && calculatedPath.Count > 1)
             {
                 Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2];
                 double d = diff.Length;
@@ -111,9 +109,9 @@ namespace osu.Game.Rulesets.Objects
                 if (d <= 0)
                     return;
 
-                calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d);
-                cumulativeLength[calculatedPath.Count - 1] = Length;
-            }*/
+                calculatedPath[calculatedPath.Count - 1] += diff * (float)((Distance - l) / d);
+                cumulativeLength[calculatedPath.Count - 1] = Distance;
+            }
         }
 
         public void Calculate()

From 43409127b73d3721f7edb5d5b915489ff14dbb8b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 15:36:42 +0900
Subject: [PATCH 108/177] Place break overlay underneath the hud

---
 osu.Game/Screens/Play/Player.cs | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index f397d0c3d4..af5021a7eb 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -174,6 +174,13 @@ namespace osu.Game.Screens.Play
                             RelativeSizeAxes = Axes.Both,
                             Child = RulesetContainer
                         },
+                        new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            ProcessCustomClock = false,
+                            Breaks = beatmap.Breaks
+                        },
                         new SkipOverlay(firstObjectTime)
                         {
                             Clock = Clock, // skip button doesn't want to use the audio clock directly
@@ -187,13 +194,6 @@ namespace osu.Game.Screens.Play
                             ProcessCustomClock = false,
                             Anchor = Anchor.Centre,
                             Origin = Anchor.Centre
-                        },
-                        new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
-                        {
-                            Anchor = Anchor.Centre,
-                            Origin = Anchor.Centre,
-                            ProcessCustomClock = false,
-                            Breaks = beatmap.Breaks
                         }
                     }
                 },

From 64fba5f6d1ed525a1ea4a0f1f844e1e8352f9159 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 15:36:56 +0900
Subject: [PATCH 109/177] Proxy the ruleset cursor above the break overlay

---
 osu.Game/Screens/Play/Player.cs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index af5021a7eb..f748d5236d 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -149,6 +149,8 @@ namespace osu.Game.Screens.Play
 
             scoreProcessor = RulesetContainer.CreateScoreProcessor();
 
+            Drawable rulesetCursor = RulesetContainer.Cursor?.CreateProxy() ?? new Container();
+
             Children = new Drawable[]
             {
                 pauseContainer = new PauseContainer(offsetClock, adjustableClock)
@@ -162,7 +164,7 @@ namespace osu.Game.Screens.Play
                         hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
                     },
                     OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
-                    Children = new Drawable[]
+                    Children = new[]
                     {
                         storyboardContainer = new Container
                         {
@@ -181,6 +183,7 @@ namespace osu.Game.Screens.Play
                             ProcessCustomClock = false,
                             Breaks = beatmap.Breaks
                         },
+                        rulesetCursor,
                         new SkipOverlay(firstObjectTime)
                         {
                             Clock = Clock, // skip button doesn't want to use the audio clock directly

From fe1c1fec0d37ecacbeb5ff328237307b95843706 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 14 May 2018 15:42:27 +0900
Subject: [PATCH 110/177] Stop overlays from handling DragStart

This was causing weird behaviour with the key configuration section and back button in settings.
---
 .../Containers/OsuFocusedOverlayContainer.cs        | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 2a30e0d032..f657c0cae5 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -44,19 +44,6 @@ namespace osu.Game.Graphics.Containers
             return base.OnClick(state);
         }
 
-        protected override bool OnDragStart(InputState state)
-        {
-            if (!base.ReceiveMouseInputAt(state.Mouse.NativeState.Position))
-            {
-                State = Visibility.Hidden;
-                return true;
-            }
-
-            return base.OnDragStart(state);
-        }
-
-        protected override bool OnDrag(InputState state) => State == Visibility.Hidden;
-
         private void onStateChanged(Visibility visibility)
         {
             switch (visibility)

From 6eb7590ab0151b86a047e157b4369bdf59978691 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 17:41:35 +0900
Subject: [PATCH 111/177] Make MusicController handle all movement to
 previous/next tracks

---
 osu.Game/Overlays/Music/PlaylistList.cs    | 95 +++++++++-------------
 osu.Game/Overlays/Music/PlaylistOverlay.cs | 77 +++---------------
 osu.Game/Overlays/MusicController.cs       | 49 +++++++++--
 3 files changed, 90 insertions(+), 131 deletions(-)

diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs
index d63babf3b6..8c8ff89420 100644
--- a/osu.Game/Overlays/Music/PlaylistList.cs
+++ b/osu.Game/Overlays/Music/PlaylistList.cs
@@ -4,7 +4,8 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Input;
@@ -16,7 +17,8 @@ namespace osu.Game.Overlays.Music
 {
     public class PlaylistList : CompositeDrawable
     {
-        public Action<BeatmapSetInfo> OnSelect;
+        public Action<BeatmapSetInfo> Selected;
+        public Action<BeatmapSetInfo, int> OrderChanged;
 
         private readonly ItemsScrollContainer items;
 
@@ -25,7 +27,8 @@ namespace osu.Game.Overlays.Music
             InternalChild = items = new ItemsScrollContainer
             {
                 RelativeSizeAxes = Axes.Both,
-                OnSelect = set => OnSelect?.Invoke(set)
+                Selected = set => Selected?.Invoke(set),
+                OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
             };
         }
 
@@ -35,34 +38,20 @@ namespace osu.Game.Overlays.Music
             set { base.Padding = value; }
         }
 
-        public IEnumerable<BeatmapSetInfo> BeatmapSets
-        {
-            get { return items.Sets; }
-            set { items.Sets = value; }
-        }
-
         public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
-        public BeatmapSetInfo NextSet => items.NextSet;
-        public BeatmapSetInfo PreviousSet => items.PreviousSet;
-
-        public BeatmapSetInfo SelectedSet
-        {
-            get { return items.SelectedSet; }
-            set { items.SelectedSet = value; }
-        }
-
-        public void AddBeatmapSet(BeatmapSetInfo beatmapSet) => items.AddBeatmapSet(beatmapSet);
-        public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => items.RemoveBeatmapSet(beatmapSet);
 
         public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
 
         private class ItemsScrollContainer : OsuScrollContainer
         {
-            public Action<BeatmapSetInfo> OnSelect;
+            public Action<BeatmapSetInfo> Selected;
+            public Action<BeatmapSetInfo, int> OrderChanged;
 
             private readonly SearchContainer search;
             private readonly FillFlowContainer<PlaylistItem> items;
 
+            private readonly IBindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
+
             public ItemsScrollContainer()
             {
                 Children = new Drawable[]
@@ -83,14 +72,36 @@ namespace osu.Game.Overlays.Music
                 };
             }
 
-            public IEnumerable<BeatmapSetInfo> Sets
+            [BackgroundDependencyLoader]
+            private void load(BeatmapManager beatmaps, OsuGameBase osuGame)
             {
-                get { return items.Select(x => x.BeatmapSetInfo).ToList(); }
-                set
-                {
-                    items.Clear();
-                    value.ForEach(AddBeatmapSet);
-                }
+                beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet);
+                beatmaps.ItemAdded += addBeatmapSet;
+                beatmaps.ItemRemoved += removeBeatmapSet;
+
+                beatmapBacking.BindTo(osuGame.Beatmap);
+                beatmapBacking.ValueChanged += _ => updateSelectedSet();
+            }
+
+            private void addBeatmapSet(BeatmapSetInfo obj)
+            {
+                var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) };
+
+                items.Add(newItem);
+                items.SetLayoutPosition(newItem, items.Count - 1);
+            }
+
+            private void removeBeatmapSet(BeatmapSetInfo obj)
+            {
+                var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID);
+                if (itemToRemove != null)
+                    items.Remove(itemToRemove);
+            }
+
+            private void updateSelectedSet()
+            {
+                foreach (PlaylistItem s in items.Children)
+                    s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo.ID;
             }
 
             public string SearchTerm
@@ -99,34 +110,7 @@ namespace osu.Game.Overlays.Music
                 set { search.SearchTerm = value; }
             }
 
-            public void AddBeatmapSet(BeatmapSetInfo beatmapSet)
-            {
-                var newItem = new PlaylistItem(beatmapSet) { OnSelect = set => OnSelect?.Invoke(set) };
-
-                items.Add(newItem);
-                items.SetLayoutPosition(newItem, items.Count);
-            }
-
-            public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet)
-            {
-                var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID);
-                if (itemToRemove != null)
-                    items.Remove(itemToRemove);
-            }
-
-            public BeatmapSetInfo SelectedSet
-            {
-                get { return items.FirstOrDefault(i => i.Selected)?.BeatmapSetInfo; }
-                set
-                {
-                    foreach (PlaylistItem s in items.Children)
-                        s.Selected = s.BeatmapSetInfo.ID == value?.ID;
-                }
-            }
-
             public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo;
-            public BeatmapSetInfo NextSet => (items.SkipWhile(i => !i.Selected).Skip(1).FirstOrDefault() ?? items.FirstOrDefault())?.BeatmapSetInfo;
-            public BeatmapSetInfo PreviousSet => (items.TakeWhile(i => !i.Selected).LastOrDefault() ?? items.LastOrDefault())?.BeatmapSetInfo;
 
             private Vector2 nativeDragPosition;
             private PlaylistItem draggedItem;
@@ -227,6 +211,7 @@ namespace osu.Game.Overlays.Music
                 }
 
                 items.SetLayoutPosition(draggedItem, dstIndex);
+                OrderChanged?.Invoke(draggedItem.BeatmapSetInfo, dstIndex);
             }
 
             private class ItemSearchContainer : FillFlowContainer<PlaylistItem>, IHasFilterableChildren
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index 3496c044fb..76c2222f8b 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -1,7 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System.Collections.Generic;
+using System;
 using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
@@ -19,18 +19,16 @@ namespace osu.Game.Overlays.Music
     public class PlaylistOverlay : OverlayContainer
     {
         private const float transition_duration = 600;
-
         private const float playlist_height = 510;
 
+        public Action<BeatmapSetInfo, int> OrderChanged;
+
+        private BeatmapManager beatmaps;
         private FilterControl filter;
         private PlaylistList list;
 
-        private BeatmapManager beatmaps;
-
         private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
 
-        public IEnumerable<BeatmapSetInfo> BeatmapSets => list.BeatmapSets;
-
         [BackgroundDependencyLoader]
         private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours)
         {
@@ -60,7 +58,8 @@ namespace osu.Game.Overlays.Music
                         {
                             RelativeSizeAxes = Axes.Both,
                             Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
-                            OnSelect = itemSelected,
+                            Selected = itemSelected,
+                            OrderChanged = (s, i) => OrderChanged?.Invoke(s, i)
                         },
                         filter = new FilterControl
                         {
@@ -74,30 +73,16 @@ namespace osu.Game.Overlays.Music
                 },
             };
 
-            beatmaps.ItemAdded += handleBeatmapAdded;
-            beatmaps.ItemRemoved += handleBeatmapRemoved;
-
-            list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
-
             beatmapBacking.BindTo(game.Beatmap);
 
             filter.Search.OnCommit = (sender, newText) =>
             {
-                var beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
-                if (beatmap != null) playSpecified(beatmap);
+                BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
+                if (beatmap != null)
+                    beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap);
             };
         }
 
-        protected override void LoadComplete()
-        {
-            base.LoadComplete();
-            beatmapBacking.ValueChanged += b => list.SelectedSet = b?.BeatmapSetInfo;
-            beatmapBacking.TriggerChange();
-        }
-
-        private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo));
-        private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo));
-
         protected override void PopIn()
         {
             filter.Search.HoldFocus = true;
@@ -123,49 +108,7 @@ namespace osu.Game.Overlays.Music
                 return;
             }
 
-            playSpecified(set.Beatmaps.First());
-        }
-
-        public void PlayPrevious()
-        {
-            var playable = list.PreviousSet;
-
-            if (playable != null)
-            {
-                playSpecified(playable.Beatmaps.First());
-                list.SelectedSet = playable;
-            }
-        }
-
-        public void PlayNext()
-        {
-            var playable = list.NextSet;
-
-            if (playable != null)
-            {
-                playSpecified(playable.Beatmaps.First());
-                list.SelectedSet = playable;
-            }
-        }
-
-        private void playSpecified(BeatmapInfo info)
-        {
-            beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
-
-            var track = beatmapBacking.Value.Track;
-
-            track.Restart();
-        }
-
-        protected override void Dispose(bool isDisposing)
-        {
-            base.Dispose(isDisposing);
-
-            if (beatmaps != null)
-            {
-                beatmaps.ItemAdded -= handleBeatmapAdded;
-                beatmaps.ItemRemoved -= handleBeatmapRemoved;
-            }
+            beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First());
         }
     }
 
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index b4021f2808..c348a8f7d5 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -2,6 +2,7 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
 using osu.Framework.Allocation;
@@ -50,7 +51,10 @@ namespace osu.Game.Overlays
 
         private LocalisationEngine localisation;
 
+        private BeatmapManager beatmaps;
         private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
+        private List<BeatmapSetInfo> beatmapSets;
+        private BeatmapSetInfo currentSet;
 
         private Container dragContainer;
         private Container playerContainer;
@@ -93,8 +97,9 @@ namespace osu.Game.Overlays
         }
 
         [BackgroundDependencyLoader]
-        private void load(OsuGameBase game, OsuColour colours, LocalisationEngine localisation)
+        private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
         {
+            this.beatmaps = beatmaps;
             this.localisation = localisation;
 
             Children = new Drawable[]
@@ -111,6 +116,7 @@ namespace osu.Game.Overlays
                         {
                             RelativeSizeAxes = Axes.X,
                             Y = player_height + 10,
+                            OrderChanged = playlistOrderChanged
                         },
                         playerContainer = new Container
                         {
@@ -185,7 +191,7 @@ namespace osu.Game.Overlays
                                                 {
                                                     Anchor = Anchor.Centre,
                                                     Origin = Anchor.Centre,
-                                                    Action = next,
+                                                    Action = () => next(),
                                                     Icon = FontAwesome.fa_step_forward,
                                                 },
                                             }
@@ -214,11 +220,24 @@ namespace osu.Game.Overlays
                 }
             };
 
+            beatmapSets = beatmaps.GetAllUsableBeatmapSets();
+            beatmaps.ItemAdded += handleBeatmapAdded;
+            beatmaps.ItemRemoved += handleBeatmapRemoved;
+
             beatmapBacking.BindTo(game.Beatmap);
 
             playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
         }
 
+        private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
+        {
+            beatmapSets.Remove(beatmapSetInfo);
+            beatmapSets.Insert(index, beatmapSetInfo);
+        }
+
+        private void handleBeatmapAdded(BeatmapSetInfo obj) => beatmapSets.Add(obj);
+        private void handleBeatmapRemoved(BeatmapSetInfo obj) => beatmapSets.RemoveAll(s => s.ID == obj.ID);
+
         protected override void LoadComplete()
         {
             beatmapBacking.ValueChanged += beatmapChanged;
@@ -257,7 +276,7 @@ namespace osu.Game.Overlays
 
                 playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
 
-                if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
+                if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any())
                     next();
             }
             else
@@ -271,7 +290,7 @@ namespace osu.Game.Overlays
             if (track == null)
             {
                 if (!beatmapBacking.Disabled)
-                    playlist.PlayNext();
+                    next(true);
                 return;
             }
 
@@ -284,13 +303,25 @@ namespace osu.Game.Overlays
         private void prev()
         {
             queuedDirection = TransformDirection.Prev;
-            playlist.PlayPrevious();
+
+            var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
+            if (playable != null)
+                playSpecified(playable.Beatmaps.First());
         }
 
-        private void next()
+        private void next(bool instant = false)
         {
             queuedDirection = TransformDirection.Next;
-            playlist.PlayNext();
+
+            var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
+            if (playable != null)
+                playSpecified(playable.Beatmaps.First());
+        }
+
+        private void playSpecified(BeatmapInfo info)
+        {
+            beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
+            beatmapBacking.Value.Track.Restart();
         }
 
         private WorkingBeatmap current;
@@ -314,8 +345,8 @@ namespace osu.Game.Overlays
                 else
                 {
                     //figure out the best direction based on order in playlist.
-                    var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
-                    var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
+                    var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
+                    var next = beatmap == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count();
 
                     direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
                 }

From 4ceae6ba1f806f4cae2983c32e58c867d74ddad4 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 17:45:11 +0900
Subject: [PATCH 112/177] Inline method

---
 osu.Game/Overlays/MusicController.cs | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index c348a8f7d5..d7884bf53a 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -306,7 +306,10 @@ namespace osu.Game.Overlays
 
             var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
             if (playable != null)
-                playSpecified(playable.Beatmaps.First());
+            {
+                beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
+                beatmapBacking.Value.Track.Restart();
+            }
         }
 
         private void next(bool instant = false)
@@ -315,13 +318,10 @@ namespace osu.Game.Overlays
 
             var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
             if (playable != null)
-                playSpecified(playable.Beatmaps.First());
-        }
-
-        private void playSpecified(BeatmapInfo info)
-        {
-            beatmapBacking.Value = beatmaps.GetWorkingBeatmap(info, beatmapBacking);
-            beatmapBacking.Value.Track.Restart();
+            {
+                beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking);
+                beatmapBacking.Value.Track.Restart();
+            }
         }
 
         private WorkingBeatmap current;

From 027f6c3fa4a55e2dafb6b15588d282de465e14eb Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 17:47:38 +0900
Subject: [PATCH 113/177] Fix instant movement not doing anything

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

diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index d7884bf53a..fb4e278b0c 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -314,7 +314,8 @@ namespace osu.Game.Overlays
 
         private void next(bool instant = false)
         {
-            queuedDirection = TransformDirection.Next;
+            if (!instant)
+                queuedDirection = TransformDirection.Next;
 
             var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
             if (playable != null)

From 115c2dc239e734d3c6f1c2161c5a80dddc84a22f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 18:02:18 +0900
Subject: [PATCH 114/177] Move hud below the skip button

---
 osu.Game/Screens/Play/Player.cs | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index f748d5236d..2d7e0e8f9e 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -183,6 +183,13 @@ namespace osu.Game.Screens.Play
                             ProcessCustomClock = false,
                             Breaks = beatmap.Breaks
                         },
+                        hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
+                        {
+                            Clock = Clock, // hud overlay doesn't want to use the audio clock directly
+                            ProcessCustomClock = false,
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre
+                        },
                         rulesetCursor,
                         new SkipOverlay(firstObjectTime)
                         {
@@ -191,13 +198,6 @@ namespace osu.Game.Screens.Play
                             AdjustableClock = adjustableClock,
                             FramedClock = offsetClock,
                         },
-                        hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock)
-                        {
-                            Clock = Clock, // hud overlay doesn't want to use the audio clock directly
-                            ProcessCustomClock = false,
-                            Anchor = Anchor.Centre,
-                            Origin = Anchor.Centre
-                        }
                     }
                 },
                 failOverlay = new FailOverlay

From 9536c324fa3853bfeff8c29b54d3258ad1998509 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 19:08:00 +0900
Subject: [PATCH 115/177] Rename aborted -> fired

---
 osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
index ef6f44b42e..6fa49c4edb 100644
--- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs
@@ -17,11 +17,11 @@ namespace osu.Game.Tests.Visual
         {
             bool fired = false;
 
-            var abortText = new OsuSpriteText
+            var firedText = new OsuSpriteText
             {
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
-                Text = "Aborted!",
+                Text = "Fired!",
                 TextSize = 50,
                 Alpha = 0,
             };
@@ -31,14 +31,14 @@ namespace osu.Game.Tests.Visual
                 Action = () =>
                 {
                     fired = true;
-                    abortText.FadeTo(1).Then().FadeOut(1000);
+                    firedText.FadeTo(1).Then().FadeOut(1000);
                 }
             };
 
             Children = new Drawable[]
             {
                 overlay,
-                abortText
+                firedText
             };
 
             AddStep("start confirming", () => overlay.Begin());

From 0234bbc37f08354910ed746624e546746b0c3165 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 14 May 2018 19:14:17 +0900
Subject: [PATCH 116/177] Move definition inline

---
 osu.Game/Screens/Play/Player.cs | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 2d7e0e8f9e..4a46279d30 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -149,8 +149,6 @@ namespace osu.Game.Screens.Play
 
             scoreProcessor = RulesetContainer.CreateScoreProcessor();
 
-            Drawable rulesetCursor = RulesetContainer.Cursor?.CreateProxy() ?? new Container();
-
             Children = new Drawable[]
             {
                 pauseContainer = new PauseContainer(offsetClock, adjustableClock)
@@ -190,7 +188,7 @@ namespace osu.Game.Screens.Play
                             Anchor = Anchor.Centre,
                             Origin = Anchor.Centre
                         },
-                        rulesetCursor,
+                        RulesetContainer.Cursor?.CreateProxy() ?? new Container(),
                         new SkipOverlay(firstObjectTime)
                         {
                             Clock = Clock, // skip button doesn't want to use the audio clock directly

From 17861c2a162f1d562aa79fb4bc3e267e96dbe530 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 19:20:53 +0900
Subject: [PATCH 117/177] Display "no key bound" when no shortcut

---
 osu.Game/Overlays/OnScreenDisplay.cs | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
index 8f6bf0f539..1c80f2e626 100644
--- a/osu.Game/Overlays/OnScreenDisplay.cs
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -101,20 +101,15 @@ namespace osu.Game.Overlays
                                     Origin = Anchor.TopCentre,
                                     AutoSizeAxes = Axes.Both
                                 },
-                                new Container
+                                textLine3 = new OsuSpriteText
                                 {
                                     Anchor = Anchor.TopCentre,
                                     Origin = Anchor.TopCentre,
-                                    AutoSizeAxes = Axes.Both,
                                     Margin = new MarginPadding { Bottom = 15 },
+                                    Font = @"Exo2.0-Bold",
+                                    TextSize = 12,
                                     Alpha = 0.3f,
-                                    Child = textLine3 = new OsuSpriteText
-                                    {
-                                        Font = @"Exo2.0-Bold",
-                                        TextSize = 12,
-                                        AlwaysPresent = true
-                                    },
-                                }
+                                },
                             }
                         }
                     }
@@ -183,6 +178,9 @@ namespace osu.Game.Overlays
                 textLine2.Text = description.Value;
                 textLine3.Text = description.Shortcut.ToUpper();
 
+                if (string.IsNullOrEmpty(textLine3.Text))
+                    textLine3.Text = "NO KEY BOUND";
+
                 Display(box);
 
                 int optionCount = 0;

From 47cf4f27fbd800371a531cd396b059c0e27c30fa Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 19:39:23 +0900
Subject: [PATCH 118/177] Fix possibility of test rulesets being discovered
 from assemblies

---
 osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs | 6 +++---
 osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs | 6 +++---
 osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs     | 6 +++---
 osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs | 6 +++---
 osu.Game/Rulesets/RulesetStore.cs                           | 2 +-
 5 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index d0d623178e..808faa511b 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Catch.Tests
 {
-    public class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
+    internal class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
 
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests
         protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
     }
 
-    public struct ConvertValue : IEquatable<ConvertValue>
+    internal struct ConvertValue : IEquatable<ConvertValue>
     {
         /// <summary>
         /// A sane value to account for osu!stable using ints everwhere.
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Tests
                && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
     }
 
-    public class TestCatchRuleset : CatchRuleset
+    internal class TestCatchRuleset : CatchRuleset
     {
     }
 }
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index f1ee874b88..bd67a7d96a 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Mania.Tests
 {
-    public class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
+    internal class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
 
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
         protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
     }
 
-    public struct ConvertValue : IEquatable<ConvertValue>
+    internal struct ConvertValue : IEquatable<ConvertValue>
     {
         /// <summary>
         /// A sane value to account for osu!stable using ints everwhere.
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Tests
                && Column == other.Column;
     }
 
-    public class TestManiaRuleset : ManiaRuleset
+    internal class TestManiaRuleset : ManiaRuleset
     {
     }
 }
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index aa7de4ed01..3d54043027 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -15,7 +15,7 @@ using OpenTK;
 
 namespace osu.Game.Rulesets.Osu.Tests
 {
-    public class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
+    internal class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
 
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests
         protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
     }
 
-    public struct ConvertValue : IEquatable<ConvertValue>
+    internal struct ConvertValue : IEquatable<ConvertValue>
     {
         /// <summary>
         /// A sane value to account for osu!stable using ints everwhere.
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Tests
                && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
     }
 
-    public class TestOsuRuleset : OsuRuleset
+    internal class TestOsuRuleset : OsuRuleset
     {
     }
 }
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 33a5e1772e..ca4fc3ec57 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -14,7 +14,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Taiko.Tests
 {
-    public class TaikoBeatmapConversionTest : BeatmapConversionTest<TestTaikoRuleset, ConvertValue>
+    internal class TaikoBeatmapConversionTest : BeatmapConversionTest<TestTaikoRuleset, ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
 
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
         protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
     }
 
-    public struct ConvertValue : IEquatable<ConvertValue>
+    internal struct ConvertValue : IEquatable<ConvertValue>
     {
         /// <summary>
         /// A sane value to account for osu!stable using ints everwhere.
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
                && IsStrong == other.IsStrong;
     }
 
-    public class TestTaikoRuleset : TaikoRuleset
+    internal class TestTaikoRuleset : TaikoRuleset
     {
     }
 }
diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs
index 67a9a59d4a..1847b63658 100644
--- a/osu.Game/Rulesets/RulesetStore.cs
+++ b/osu.Game/Rulesets/RulesetStore.cs
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets
             try
             {
                 var assembly = Assembly.LoadFrom(file);
-                loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset)));
+                loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
             }
             catch (Exception)
             {

From addb864d1047202af87541763e24a116a4e3cd04 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 14 May 2018 15:41:31 +0900
Subject: [PATCH 119/177] Allow help text to wrap

---
 osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 14 ++++++++------
 osu.Game/Overlays/KeyBindingOverlay.cs        |  4 ----
 2 files changed, 8 insertions(+), 10 deletions(-)

diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
index 41f23a8cd0..7406a9ec4f 100644
--- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
+++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
 using osu.Framework.Input;
 using osu.Framework.Input.Bindings;
 using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Input;
 using OpenTK.Graphics;
@@ -43,7 +44,7 @@ namespace osu.Game.Overlays.KeyBinding
         }
 
         private OsuSpriteText text;
-        private OsuSpriteText pressAKey;
+        private OsuTextFlowContainer pressAKey;
 
         private FillFlowContainer<KeyButton> buttons;
 
@@ -95,10 +96,11 @@ namespace osu.Game.Overlays.KeyBinding
                     Anchor = Anchor.TopRight,
                     Origin = Anchor.TopRight
                 },
-                pressAKey = new OsuSpriteText
+                pressAKey = new OsuTextFlowContainer
                 {
-                    Text = "Press a key to change binding, SHIFT+DEL to delete, ESC to cancel.",
-                    Y = height,
+                    Text = "Press a key to change binding, Shift+Delete to delete, Escape to cancel.",
+                    RelativeSizeAxes = Axes.X,
+                    AutoSizeAxes = Axes.Y,
                     Margin = new MarginPadding(padding),
                     Alpha = 0,
                     Colour = colours.YellowDark
@@ -268,7 +270,7 @@ namespace osu.Game.Overlays.KeyBinding
                 GetContainingInputManager().ChangeFocus(null);
 
             pressAKey.FadeOut(300, Easing.OutQuint);
-            pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight };
+            pressAKey.Padding = new MarginPadding { Top = height, Bottom = -pressAKey.DrawHeight };
         }
 
         protected override void OnFocus(InputState state)
@@ -277,7 +279,7 @@ namespace osu.Game.Overlays.KeyBinding
             AutoSizeEasing = Easing.OutQuint;
 
             pressAKey.FadeIn(300, Easing.OutQuint);
-            pressAKey.Padding = new MarginPadding();
+            pressAKey.Padding = new MarginPadding { Top = height };
 
             updateBindTarget();
             base.OnFocus(state);
diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs
index bd9c8c20de..06432cfcea 100644
--- a/osu.Game/Overlays/KeyBindingOverlay.cs
+++ b/osu.Game/Overlays/KeyBindingOverlay.cs
@@ -12,8 +12,6 @@ namespace osu.Game.Overlays
 {
     public class KeyBindingOverlay : SettingsOverlay
     {
-        protected const float WIDTH = 430;
-
         protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
 
         [BackgroundDependencyLoader(permitNulls: true)]
@@ -23,8 +21,6 @@ namespace osu.Game.Overlays
 
             foreach (var ruleset in rulesets.AvailableRulesets)
                 AddSection(new RulesetBindingsSection(ruleset));
-
-            ContentContainer.Width = WIDTH;
         }
 
         public KeyBindingOverlay()

From e802b722f053b0f375566eaee81859d07bb98178 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Mon, 14 May 2018 20:27:05 +0300
Subject: [PATCH 120/177] Revert "Handle mouse back button using OnMouseDown
 override instead of using GlobalAction"

This reverts commit 44bbb8700ecc1bdd652c35766bfbaa54310a5855.
---
 .../Input/Bindings/GlobalActionContainer.cs   | 10 +++-
 osu.Game/Screens/Loader.cs                    |  1 -
 osu.Game/Screens/Menu/ButtonSystem.cs         | 52 +++++++++++--------
 osu.Game/Screens/Menu/MainMenu.cs             |  1 -
 osu.Game/Screens/OsuScreen.cs                 | 28 +++-------
 osu.Game/Screens/Play/PlayerLoader.cs         |  1 +
 .../Play/ScreenWithBeatmapBackground.cs       |  2 -
 7 files changed, 48 insertions(+), 47 deletions(-)

diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index dd8f00f6cd..565d530395 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Input.Bindings
         {
             new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
             new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
-            new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
+            new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot),
 
             new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
             new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
@@ -36,6 +36,9 @@ namespace osu.Game.Input.Bindings
             new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
             new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
+
+            new KeyBinding(InputKey.Escape, GlobalAction.Back),
+            new KeyBinding(InputKey.MouseButton1, GlobalAction.Back)
         };
 
         public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@@ -76,6 +79,9 @@ namespace osu.Game.Input.Bindings
         QuickRetry,
 
         [Description("Take screenshot")]
-        TakeScreenshot
+        TakeScreenshot,
+
+        [Description("Go back")]
+        Back
     }
 }
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index dc8dbb4421..1d152361df 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -18,7 +18,6 @@ namespace osu.Game.Screens
         private bool showDisclaimer;
 
         public override bool ShowOverlaysOnEnter => false;
-        protected override bool AllowBackButton => false;
 
         public Loader()
         {
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 5a1dafe404..8cf0d24f7d 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -13,15 +13,17 @@ using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Shapes;
 using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
 using osu.Framework.Threading;
 using osu.Game.Graphics;
+using osu.Game.Input.Bindings;
 using OpenTK;
 using OpenTK.Graphics;
 using OpenTK.Input;
 
 namespace osu.Game.Screens.Menu
 {
-    public class ButtonSystem : Container, IStateful<MenuState>
+    public class ButtonSystem : Container, IStateful<MenuState>, IKeyBindingHandler<GlobalAction>
     {
         public event Action<MenuState> StateChanged;
 
@@ -146,36 +148,44 @@ namespace osu.Game.Screens.Menu
                 case Key.Space:
                     logo?.TriggerOnClick(state);
                     return true;
-                case Key.Escape:
-                    return handleBack();
             }
 
             return false;
         }
 
-        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        public bool OnPressed(GlobalAction action)
         {
-            if (state.Mouse.IsPressed(MouseButton.Button1))
-                return handleBack();
-
-            return base.OnMouseDown(state, args);
-        }
-
-        private bool handleBack()
-        {
-            switch (State)
+            switch (action)
             {
-                case MenuState.TopLevel:
-                    State = MenuState.Initial;
-                    return true;
-                case MenuState.Play:
-                    backButton.TriggerOnClick();
-                    return true;
+                case GlobalAction.Back:
+                    switch (State)
+                    {
+                        case MenuState.TopLevel:
+                            State = MenuState.Initial;
+                            return true;
+                        case MenuState.Play:
+                            backButton.TriggerOnClick();
+                            return true;
+                        default:
+                            return false;
+                    }
+                default:
+                    return false;
             }
-
-            return false;
         }
 
+        public bool OnReleased(GlobalAction action)
+        {
+            switch (action)
+            {
+                case GlobalAction.Back:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+
         private void onPlay()
         {
             State = MenuState.Play;
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index d91ac099fd..f2ea6d85a8 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -25,7 +25,6 @@ namespace osu.Game.Screens.Menu
         private readonly ButtonSystem buttons;
 
         public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial;
-        protected override bool AllowBackButton => false;
 
         private readonly BackgroundScreenDefault background;
         private Screen songSelect;
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 9fa30181a1..fb5d3d12e6 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -7,18 +7,18 @@ using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
-using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
 using osu.Framework.Screens;
 using osu.Game.Beatmaps;
 using osu.Game.Graphics.Containers;
+using osu.Game.Input.Bindings;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Menu;
 using OpenTK;
-using OpenTK.Input;
 
 namespace osu.Game.Screens
 {
-    public abstract class OsuScreen : Screen
+    public abstract class OsuScreen : Screen, IKeyBindingHandler<GlobalAction>
     {
         public BackgroundScreen Background { get; private set; }
 
@@ -92,31 +92,19 @@ namespace osu.Game.Screens
             sampleExit = audio.Sample.Get(@"UI/screen-back");
         }
 
-        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+        public bool OnPressed(GlobalAction action)
         {
-            if (args.Repeat || !IsCurrentScreen) return false;
-
-            switch (args.Key)
-            {
-                case Key.Escape:
-                    Exit();
-                    return true;
-            }
-
-            return base.OnKeyDown(state, args);
-        }
-
-        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
-        {
-            if (AllowBackButton && state.Mouse.IsPressed(MouseButton.Button1))
+            if (action == GlobalAction.Back && AllowBackButton)
             {
                 Exit();
                 return true;
             }
 
-            return base.OnMouseDown(state, args);
+            return false;
         }
 
+        public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
+
         protected override void OnResuming(Screen last)
         {
             sampleExit?.Play();
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 56fbd7b6e7..6eb156914e 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Screens.Play
 
         private bool showOverlays = true;
         public override bool ShowOverlaysOnEnter => showOverlays;
+        protected override bool AllowBackButton => false;
 
         private Task loadTask;
 
diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
index 30ae6db346..1ccc5e2fe8 100644
--- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
+++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
@@ -17,8 +17,6 @@ namespace osu.Game.Screens.Play
 
         public override bool AllowBeatmapRulesetChange => false;
 
-        protected override bool AllowBackButton => false;
-
         protected const float BACKGROUND_FADE_DURATION = 800;
 
         protected float BackgroundOpacity => 1 - (float)DimLevel;

From f2f2fb8c73670a02dc6cb877fec342423ff27b9b Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Mon, 14 May 2018 22:09:09 +0300
Subject: [PATCH 121/177] Use both OnKeyDown and GlobalAction.Back

---
 osu.Game/Screens/Loader.cs                       |  2 ++
 osu.Game/Screens/Menu/MainMenu.cs                |  2 ++
 osu.Game/Screens/OsuScreen.cs                    | 16 ++++++++++++++++
 .../Screens/Play/ScreenWithBeatmapBackground.cs  |  1 +
 4 files changed, 21 insertions(+)

diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index 1d152361df..555c497d92 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -19,6 +19,8 @@ namespace osu.Game.Screens
 
         public override bool ShowOverlaysOnEnter => false;
 
+        protected override bool AllowBackButton => false;
+
         public Loader()
         {
             ValidForResume = false;
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index f2ea6d85a8..f9f62b764b 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Screens.Menu
 
         public override bool ShowOverlaysOnEnter => buttons.State != MenuState.Initial;
 
+        protected override bool AllowBackButton => buttons.State != MenuState.Initial;
+
         private readonly BackgroundScreenDefault background;
         private Screen songSelect;
 
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index fb5d3d12e6..24945ea347 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -7,6 +7,7 @@ using osu.Framework.Audio;
 using osu.Framework.Audio.Sample;
 using osu.Framework.Configuration;
 using osu.Framework.Graphics;
+using osu.Framework.Input;
 using osu.Framework.Input.Bindings;
 using osu.Framework.Screens;
 using osu.Game.Beatmaps;
@@ -15,6 +16,7 @@ using osu.Game.Input.Bindings;
 using osu.Game.Rulesets;
 using osu.Game.Screens.Menu;
 using OpenTK;
+using OpenTK.Input;
 
 namespace osu.Game.Screens
 {
@@ -105,6 +107,20 @@ namespace osu.Game.Screens
 
         public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton;
 
+        protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
+        {
+            if (args.Repeat || !IsCurrentScreen) return false;
+
+            switch (args.Key)
+            {
+                case Key.Escape:
+                    Exit();
+                    return true;
+            }
+
+            return base.OnKeyDown(state, args);
+        }
+
         protected override void OnResuming(Screen last)
         {
             sampleExit?.Play();
diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
index 1ccc5e2fe8..f29f5b328a 100644
--- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
+++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Screens.Play
         protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
 
         public override bool AllowBeatmapRulesetChange => false;
+        protected override bool AllowBackButton => false;
 
         protected const float BACKGROUND_FADE_DURATION = 800;
 

From fe2ea17e7f8a4ef7952409ac82be57c20d42d687 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 11:42:40 +0900
Subject: [PATCH 122/177] Allow subclasses of LegacyScoreParser to specify
 beatmap/ruleset retrieval

---
 .../Rulesets/Scoring/Legacy/LegacyScoreParser.cs | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index 38873c4df1..f7ab9df52a 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Scoring.Legacy
             this.beatmaps = beatmaps;
         }
 
+        protected LegacyScoreParser()
+        {
+        }
+
         private IBeatmap currentBeatmap;
         private Ruleset currentRuleset;
 
@@ -34,16 +38,15 @@ namespace osu.Game.Rulesets.Scoring.Legacy
 
             using (SerializationReader sr = new SerializationReader(stream))
             {
-                score = new Score { Ruleset = rulesets.GetRuleset(sr.ReadByte()) };
-                currentRuleset = score.Ruleset.CreateInstance();
+                currentRuleset = GetRuleset(sr.ReadByte());
+                score = new Score { Ruleset = currentRuleset.RulesetInfo };
 
                 /* score.Pass = true;*/
                 var version = sr.ReadInt32();
 
                 /* score.FileChecksum = */
-                var beatmapHash = sr.ReadString();
-                score.Beatmap = beatmaps.QueryBeatmap(b => b.MD5Hash == beatmapHash);
-                currentBeatmap = beatmaps.GetWorkingBeatmap(score.Beatmap).Beatmap;
+                currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap;
+                score.Beatmap = currentBeatmap.BeatmapInfo;
 
                 /* score.PlayerName = */
                 score.User = new User { Username = sr.ReadString() };
@@ -181,5 +184,8 @@ namespace osu.Game.Rulesets.Scoring.Legacy
 
             return frame;
         }
+
+        protected virtual Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
+        protected virtual WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
     }
 }

From 10e2f7453802b5166f7cb924924033f15d4ddd75 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 11:53:11 +0900
Subject: [PATCH 123/177] Port osu-performance HD AR fix

---
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 4942a55004..793060197d 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -138,8 +138,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
 
             aimValue *= approachRateFactor;
 
+            // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
             if (mods.Any(h => h is OsuModHidden))
-                aimValue *= 1.03f;
+                aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
 
             if (mods.Any(h => h is OsuModFlashlight))
             {

From 1ee68c1c545662321b8404d899da1136b799991d Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 15:27:57 +0900
Subject: [PATCH 124/177] Make LegacyScoreParser abstract

---
 .../Legacy/DatabasedLegacyScoreParser.cs      | 26 ++++++++++++++++
 .../Scoring/Legacy/LegacyScoreParser.cs       | 30 +++++++++----------
 osu.Game/Rulesets/Scoring/ScoreStore.cs       |  2 +-
 3 files changed, 41 insertions(+), 17 deletions(-)
 create mode 100644 osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs

diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs
new file mode 100644
index 0000000000..bfb2b7c13b
--- /dev/null
+++ b/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Rulesets.Scoring.Legacy
+{
+    /// <summary>
+    /// A <see cref="LegacyScoreParser"/> which retrieves the applicable <see cref="Beatmap"/> and <see cref="Ruleset"/>
+    /// for the score from the database.
+    /// </summary>
+    public class DatabasedLegacyScoreParser : LegacyScoreParser
+    {
+        private readonly RulesetStore rulesets;
+        private readonly BeatmapManager beatmaps;
+
+        public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
+        {
+            this.rulesets = rulesets;
+            this.beatmaps = beatmaps;
+        }
+
+        protected override Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
+        protected override WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
+    }
+}
diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
index f7ab9df52a..a90cd79186 100644
--- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
+++ b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs
@@ -14,21 +14,8 @@ using System.Linq;
 
 namespace osu.Game.Rulesets.Scoring.Legacy
 {
-    public class LegacyScoreParser
+    public abstract class LegacyScoreParser
     {
-        private readonly RulesetStore rulesets;
-        private readonly BeatmapManager beatmaps;
-
-        public LegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps)
-        {
-            this.rulesets = rulesets;
-            this.beatmaps = beatmaps;
-        }
-
-        protected LegacyScoreParser()
-        {
-        }
-
         private IBeatmap currentBeatmap;
         private Ruleset currentRuleset;
 
@@ -185,7 +172,18 @@ namespace osu.Game.Rulesets.Scoring.Legacy
             return frame;
         }
 
-        protected virtual Ruleset GetRuleset(int rulesetId) => rulesets.GetRuleset(rulesetId).CreateInstance();
-        protected virtual WorkingBeatmap GetBeatmap(string md5Hash) => beatmaps.GetWorkingBeatmap(beatmaps.QueryBeatmap(b => b.MD5Hash == md5Hash));
+        /// <summary>
+        /// Retrieves the <see cref="Ruleset"/> for a specific id.
+        /// </summary>
+        /// <param name="rulesetId">The id.</param>
+        /// <returns>The <see cref="Ruleset"/>.</returns>
+        protected abstract Ruleset GetRuleset(int rulesetId);
+
+        /// <summary>
+        /// Retrieves the <see cref="WorkingBeatmap"/> corresponding to an MD5 hash.
+        /// </summary>
+        /// <param name="md5Hash">The MD5 hash.</param>
+        /// <returns>The <see cref="WorkingBeatmap"/>.</returns>
+        protected abstract WorkingBeatmap GetBeatmap(string md5Hash);
     }
 }
diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs
index 957fd037e0..69d25fcb67 100644
--- a/osu.Game/Rulesets/Scoring/ScoreStore.cs
+++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Scoring
         public Score ReadReplayFile(string replayFilename)
         {
             using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
-                return new LegacyScoreParser(rulesets, beatmaps).Parse(s);
+                return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(s);
         }
     }
 }

From 9fd972e0a3a3b582fd4d30935c1e7bd8af9ef74c Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 17:36:29 +0900
Subject: [PATCH 125/177] Rename OsuDifficulty -> Difficulty

---
 .../OsuDifficultyCalculator.cs                              | 6 +++---
 .../Preprocessing/OsuDifficultyBeatmap.cs                   | 2 +-
 .../Preprocessing/OsuDifficultyHitObject.cs                 | 4 ++--
 .../{OsuDifficulty => Difficulty}/Skills/Aim.cs             | 4 ++--
 .../{OsuDifficulty => Difficulty}/Skills/Skill.cs           | 6 +++---
 .../{OsuDifficulty => Difficulty}/Skills/Speed.cs           | 4 ++--
 .../{OsuDifficulty => Difficulty}/Utils/History.cs          | 2 +-
 osu.Game.Rulesets.Osu/OsuRuleset.cs                         | 2 +-
 8 files changed, 15 insertions(+), 15 deletions(-)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/OsuDifficultyCalculator.cs (93%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Preprocessing/OsuDifficultyBeatmap.cs (98%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Preprocessing/OsuDifficultyHitObject.cs (98%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Skills/Aim.cs (85%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Skills/Skill.cs (96%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Skills/Speed.cs (93%)
 rename osu.Game.Rulesets.Osu/{OsuDifficulty => Difficulty}/Utils/History.cs (98%)

diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
similarity index 93%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
rename to osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 4853cd66cd..875f1f6097 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -5,11 +5,11 @@ using System;
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Skills;
 using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty
+namespace osu.Game.Rulesets.Osu.Difficulty
 {
     public class OsuDifficultyCalculator : DifficultyCalculator
     {
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
similarity index 98%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
index 5c8ab0f3d4..e97042a9aa 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -5,7 +5,7 @@ using System.Collections;
 using System.Collections.Generic;
 using osu.Game.Rulesets.Osu.Objects;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
+namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
 {
     /// <summary>
     /// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
similarity index 98%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 415f76ced8..f95426466b 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -3,10 +3,10 @@
 
 using System;
 using System.Linq;
-using OpenTK;
 using osu.Game.Rulesets.Osu.Objects;
+using OpenTK;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
+namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
 {
     /// <summary>
     /// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
similarity index 85%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
index 5c736d7bb5..0a45c62671 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Aim.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
@@ -2,9 +2,9 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
 {
     /// <summary>
     /// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
similarity index 96%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
index 983599432f..47037c1503 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Skill.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs
@@ -3,11 +3,11 @@
 
 using System;
 using System.Collections.Generic;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Utils;
 using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
-using osu.Game.Rulesets.Osu.OsuDifficulty.Utils;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
 {
     /// <summary>
     /// Used to processes strain values of <see cref="OsuDifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
similarity index 93%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index ae3caa1e66..b807f20037 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -1,9 +1,9 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
+namespace osu.Game.Rulesets.Osu.Difficulty.Skills
 {
     /// <summary>
     /// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
similarity index 98%
rename from osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs
rename to osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
index f6933a3e5d..55bd950209 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Utils/History.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs
@@ -5,7 +5,7 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 
-namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
+namespace osu.Game.Rulesets.Osu.Difficulty.Utils
 {
     /// <summary>
     /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full.
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 69a54fb533..b5dc9063af 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
 using osu.Game.Graphics;
 using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Rulesets.Osu.OsuDifficulty;
 using osu.Game.Rulesets.Osu.UI;
 using osu.Game.Rulesets.UI;
 using System.Collections.Generic;
@@ -20,6 +19,7 @@ using osu.Game.Rulesets.Osu.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Difficulty;
 
 namespace osu.Game.Rulesets.Osu
 {

From 995fda9b6f9a9d3bde52c9e943592e376dc6271f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 17:38:04 +0900
Subject: [PATCH 126/177] Move base classes to osu.Game.Rulesets.Difficulty
 namespace

---
 osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs       | 1 +
 osu.Game.Rulesets.Catch/CatchRuleset.cs                    | 1 +
 osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs       | 1 +
 osu.Game.Rulesets.Mania/ManiaRuleset.cs                    | 1 +
 .../Difficulty/OsuDifficultyCalculator.cs                  | 1 +
 osu.Game.Rulesets.Osu/OsuRuleset.cs                        | 1 +
 osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs  | 1 +
 osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs       | 1 +
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs                    | 1 +
 osu.Game/Beatmaps/DummyWorkingBeatmap.cs                   | 1 +
 .../Difficulty}/DifficultyCalculator.cs                    | 7 ++++---
 .../{Scoring => Difficulty}/PerformanceCalculator.cs       | 3 ++-
 osu.Game/Rulesets/Ruleset.cs                               | 1 +
 13 files changed, 17 insertions(+), 4 deletions(-)
 rename osu.Game/{Beatmaps => Rulesets/Difficulty}/DifficultyCalculator.cs (94%)
 rename osu.Game/Rulesets/{Scoring => Difficulty}/PerformanceCalculator.cs (95%)

diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
index f47d09fe20..1ea70c54c9 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
@@ -3,6 +3,7 @@
 
 using osu.Game.Beatmaps;
 using System.Collections.Generic;
+using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets.Catch
 {
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 15e51fa126..c4342ec4b0 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Catch.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets.Catch
 {
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 822ba53eeb..d5ae568bc4 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mods;
 using System;
 using System.Collections.Generic;
+using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets.Mania
 {
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index f1d65f855b..2672486460 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -15,6 +15,7 @@ using osu.Game.Graphics;
 using osu.Game.Rulesets.Mania.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mania.Beatmaps;
 
 namespace osu.Game.Rulesets.Mania
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 875f1f6097..bd8d18af41 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -4,6 +4,7 @@
 using System;
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
 using osu.Game.Rulesets.Osu.Difficulty.Skills;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index b5dc9063af..a2423ffbe5 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -18,6 +18,7 @@ using osu.Game.Rulesets.Edit;
 using osu.Game.Rulesets.Osu.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Osu.Beatmaps;
 using osu.Game.Rulesets.Osu.Difficulty;
 
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 793060197d..938060a664 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.Osu.Mods;
 using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
index f14c53f7ae..1c49118d32 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
@@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Taiko.Objects;
 using System.Collections.Generic;
 using System;
+using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets.Taiko
 {
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 102de5717f..154b66f4ad 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Rulesets.Taiko.Replays;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Taiko.Beatmaps;
 
 namespace osu.Game.Rulesets.Taiko
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index da52dc7284..8094abe5ed 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using osu.Framework.Audio.Track;
 using osu.Framework.Graphics.Textures;
 using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.UI;
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
similarity index 94%
rename from osu.Game/Beatmaps/DifficultyCalculator.cs
rename to osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
index 5cac9ed923..070bc7ddb0 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -2,12 +2,13 @@
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
 using System.Collections.Generic;
-using osu.Game.Rulesets.Mods;
-using osu.Framework.Timing;
 using System.Linq;
 using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
 
-namespace osu.Game.Beatmaps
+namespace osu.Game.Rulesets.Difficulty
 {
     public abstract class DifficultyCalculator
     {
diff --git a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
similarity index 95%
rename from osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
rename to osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
index f2c495fa5d..9fd7a0156d 100644
--- a/osu.Game/Rulesets/Scoring/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
@@ -7,8 +7,9 @@ using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Timing;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
 
-namespace osu.Game.Rulesets.Scoring
+namespace osu.Game.Rulesets.Difficulty
 {
     public abstract class PerformanceCalculator
     {
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 6883d319f4..395eeab419 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Rulesets.Scoring;
 using osu.Game.Rulesets.UI;
 using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets
 {

From 803200ff6a724e1c7143c0901776b7a95756b76d Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 17:40:19 +0900
Subject: [PATCH 127/177] Apply similar changes to other rulesets

---
 osu.Game.Rulesets.Catch/CatchRuleset.cs                   | 1 +
 .../{ => Difficulty}/CatchDifficultyCalculator.cs         | 4 ++--
 .../{ => Difficulty}/ManiaDifficultyCalculator.cs         | 8 ++++----
 osu.Game.Rulesets.Mania/ManiaRuleset.cs                   | 1 +
 .../{ => Difficulty}/TaikoDifficultyCalculator.cs         | 8 ++++----
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs                   | 1 +
 6 files changed, 13 insertions(+), 10 deletions(-)
 rename osu.Game.Rulesets.Catch/{ => Difficulty}/CatchDifficultyCalculator.cs (92%)
 rename osu.Game.Rulesets.Mania/{ => Difficulty}/ManiaDifficultyCalculator.cs (99%)
 rename osu.Game.Rulesets.Taiko/{ => Difficulty}/TaikoDifficultyCalculator.cs (99%)

diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index c4342ec4b0..2325a8cad9 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Catch.Replays;
 using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Difficulty;
 using osu.Game.Rulesets.Difficulty;
 
 namespace osu.Game.Rulesets.Catch
diff --git a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
similarity index 92%
rename from osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
rename to osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 1ea70c54c9..f8351b7519 100644
--- a/osu.Game.Rulesets.Catch/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,11 +1,11 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Game.Beatmaps;
 using System.Collections.Generic;
+using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Difficulty;
 
-namespace osu.Game.Rulesets.Catch
+namespace osu.Game.Rulesets.Catch.Difficulty
 {
     public class CatchDifficultyCalculator : DifficultyCalculator
     {
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
similarity index 99%
rename from osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
rename to osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index d5ae568bc4..ac45130fd8 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -1,15 +1,15 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
+using System.Collections.Generic;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mania.Beatmaps;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Mods;
-using System;
-using System.Collections.Generic;
-using osu.Game.Rulesets.Difficulty;
 
-namespace osu.Game.Rulesets.Mania
+namespace osu.Game.Rulesets.Mania.Difficulty
 {
     internal class ManiaDifficultyCalculator : DifficultyCalculator
     {
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 2672486460..b1702de537 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Difficulty;
 
 namespace osu.Game.Rulesets.Mania
 {
diff --git a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
similarity index 99%
rename from osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
rename to osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 1c49118d32..acff0d286d 100644
--- a/osu.Game.Rulesets.Taiko/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -1,13 +1,13 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Taiko.Objects;
-using System.Collections.Generic;
 using System;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Taiko.Objects;
 
-namespace osu.Game.Rulesets.Taiko
+namespace osu.Game.Rulesets.Taiko.Difficulty
 {
     internal class TaikoDifficultyCalculator : DifficultyCalculator
     {
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 154b66f4ad..35dc17c0e2 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Taiko.Replays;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Difficulty;
 
 namespace osu.Game.Rulesets.Taiko
 {

From 0325b1bd7ac7b9b0af4bc9f2bd210bfe00aa12bf Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 15 May 2018 19:42:30 +0900
Subject: [PATCH 128/177] Remove unused class

---
 osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
index fbdb27a81c..ae03a06b4f 100644
--- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -8,10 +8,6 @@ using osu.Framework.Screens;
 
 namespace osu.Game.Graphics.UserInterface
 {
-    public class ScreenBreadcrumbControl : ScreenBreadcrumbControl<Screen>
-    {
-    }
-
     public class ScreenBreadcrumbControl<T> : BreadcrumbControl<T> where T : Screen
     {
         private T currentScreen;

From 28df77e83887d2eae75454bdd6de5b3a8afca46a Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 15 May 2018 21:08:55 +0900
Subject: [PATCH 129/177] Simplify code and remove generic

---
 ....cs => TestCaseScreenBreadcrumbControl.cs} | 18 ++---
 .../UserInterface/ScreenBreadcrumbControl.cs  | 75 +++++++------------
 2 files changed, 36 insertions(+), 57 deletions(-)
 rename osu.Game.Tests/Visual/{TestCaseScreenBreadcrumbs.cs => TestCaseScreenBreadcrumbControl.cs} (89%)

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
similarity index 89%
rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
rename to osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
index f477a0b97e..a64e60dbd6 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -6,6 +6,7 @@ using NUnit.Framework;
 using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
+using osu.Framework.Screens;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Sprites;
 using osu.Game.Graphics.UserInterface;
@@ -15,18 +16,19 @@ using OpenTK;
 namespace osu.Game.Tests.Visual
 {
     [TestFixture]
-    public class TestCaseScreenBreadcrumbs : OsuTestCase
+    public class TestCaseScreenBreadcrumbControl : OsuTestCase
     {
-        private readonly ScreenBreadcrumbControl<TestScreen> breadcrumbs;
-        private TestScreen currentScreen, changedScreen;
+        private readonly ScreenBreadcrumbControl breadcrumbs;
+        private Screen currentScreen, changedScreen;
 
-        public TestCaseScreenBreadcrumbs()
+        public TestCaseScreenBreadcrumbControl()
         {
             TestScreen startScreen;
             OsuSpriteText titleText;
 
             Children = new Drawable[]
             {
+                changedScreen = currentScreen = startScreen = new TestScreenOne(),
                 new FillFlowContainer
                 {
                     RelativeSizeAxes = Axes.X,
@@ -35,23 +37,21 @@ namespace osu.Game.Tests.Visual
                     Spacing = new Vector2(10),
                     Children = new Drawable[]
                     {
-                        breadcrumbs = new ScreenBreadcrumbControl<TestScreen>
+                        breadcrumbs = new ScreenBreadcrumbControl(startScreen)
                         {
                             RelativeSizeAxes = Axes.X,
                         },
                         titleText = new OsuSpriteText(),
                     },
                 },
-                currentScreen = startScreen = new TestScreenOne(),
             };
 
-            breadcrumbs.OnScreenChanged += s =>
+            breadcrumbs.Current.ValueChanged += s =>
             {
                 titleText.Text = $"Changed to {s.ToString()}";
                 changedScreen = s;
             };
 
-            AddStep(@"make start current", () => breadcrumbs.CurrentScreen = startScreen);
             assertCurrent();
             pushNext();
             assertCurrent();
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual
             breadcrumbs.StripColour = colours.Blue;
         }
 
-        private void pushNext() => AddStep(@"push next screen", () => currentScreen = currentScreen.PushNext());
+        private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
         private void assertCurrent() => AddAssert(@"assert the current screen is correct", () => currentScreen == changedScreen);
 
         private abstract class TestScreen : OsuScreen
diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
index ae03a06b4f..49d854ccd2 100644
--- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -8,67 +8,46 @@ using osu.Framework.Screens;
 
 namespace osu.Game.Graphics.UserInterface
 {
-    public class ScreenBreadcrumbControl<T> : BreadcrumbControl<T> where T : Screen
+    /// <summary>
+    /// A <see cref="BreadcrumbControl"/> which follows the active screen (and allows navigation) in a <see cref="Screen"/> stack.
+    /// </summary>
+    public class ScreenBreadcrumbControl : BreadcrumbControl<Screen>
     {
-        private T currentScreen;
-        public T CurrentScreen
+        private Screen last;
+
+        public ScreenBreadcrumbControl(Screen initialScreen)
         {
-            get { return currentScreen; }
-            set
+            Current.ValueChanged += newScreen =>
             {
-                if (value == currentScreen) return;
-
-                if (CurrentScreen != null)
-                {
-                    CurrentScreen.Exited -= onExited;
-                    CurrentScreen.ModePushed -= onPushed;
-                }
-                else
-                {
-                    // this is the first screen in the stack, so call the initial onPushed
-                    currentScreen = value;
-                    onPushed(CurrentScreen);
-                }
-
-                currentScreen = value;
-
-                if (CurrentScreen != null)
-                {
-                    CurrentScreen.Exited += onExited;
-                    CurrentScreen.ModePushed += onPushed;
-                    Current.Value = CurrentScreen;
-                    OnScreenChanged?.Invoke(CurrentScreen);
-                }
-            }
-        }
-
-        public event Action<T> OnScreenChanged;
-
-        public ScreenBreadcrumbControl()
-        {
-            Current.ValueChanged += s =>
-            {
-                if (s != CurrentScreen)
-                {
-                    CurrentScreen = s;
-                    s.MakeCurrent();
-                }
+                if (last != newScreen && !newScreen.IsCurrentScreen)
+                    newScreen.MakeCurrent();
             };
+
+            onPushed(initialScreen);
         }
 
-        private void onExited(Screen screen)
+        private void screenChanged(Screen newScreen)
         {
-            CurrentScreen = screen as T;
+            if (last != null)
+            {
+                last.Exited -= screenChanged;
+                last.ModePushed -= onPushed;
+            }
+
+            last = newScreen;
+
+            newScreen.Exited += screenChanged;
+            newScreen.ModePushed += onPushed;
+
+            Current.Value = newScreen;
         }
 
         private void onPushed(Screen screen)
         {
-            var newScreen = screen as T;
-
             Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem);
-            AddItem(newScreen);
+            AddItem(screen);
 
-            CurrentScreen = newScreen;
+            screenChanged(screen);
         }
     }
 }

From 49c36787d7d1365c6eda1fcae9b4fb5e77b496de Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:22:57 +0900
Subject: [PATCH 130/177] Remove triangle pattern and remove not-implemented
 reading strain

---
 .../Preprocessing/OsuDifficultyBeatmap.cs     | 58 ++-----------------
 .../Preprocessing/OsuDifficultyHitObject.cs   | 19 +++---
 2 files changed, 11 insertions(+), 66 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
index 5c8ab0f3d4..867a11f280 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyBeatmap.cs
@@ -14,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
     public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
     {
         private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
-        private readonly Queue<OsuDifficultyHitObject> onScreen = new Queue<OsuDifficultyHitObject>();
 
         /// <summary>
         /// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
@@ -30,65 +29,16 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
 
         /// <summary>
         /// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
-        /// The inner loop adds objects that appear on screen into a queue until we need to hit the next object.
-        /// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen.
-        /// This means that we can loop through every object that is on screen at the time when a new one appears,
-        /// allowing us to determine a reading strain for the object that just appeared.
         /// </summary>
-        public IEnumerator<OsuDifficultyHitObject> GetEnumerator()
-        {
-            while (true)
-            {
-                // Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued.
-                // This means there is always at least one object in the queue unless we reached the end of the map.
-                do
-                {
-                    if (!difficultyObjects.MoveNext())
-                        break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen.
-
-                    OsuDifficultyHitObject latest = difficultyObjects.Current;
-                    // Calculate flow values here
-
-                    foreach (OsuDifficultyHitObject h in onScreen)
-                    {
-                        // ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext())
-                        h.TimeUntilHit -= latest.DeltaTime;
-                        // Calculate reading strain here
-                    }
-
-                    onScreen.Enqueue(latest);
-                }
-                while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one.
-
-                if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects.
-                yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared.
-            }
-        }
-
+        public IEnumerator<OsuDifficultyHitObject> GetEnumerator() => difficultyObjects;
         IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 
         private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
         {
-            // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
-            OsuHitObject[] triangle = new OsuHitObject[3];
-
-            // OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning.
-            if (objects.Count > 1)
-            {
-                triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle.
-                triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject.
-            }
-
-            // The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump.
+            // The first jump is formed by the first two hitobjects of the map.
             // If the map has less than two OsuHitObjects, the enumerator will not return anything.
-            for (int i = 1; i < objects.Count; ++i)
-            {
-                triangle[2] = triangle[1];
-                triangle[1] = triangle[0];
-                triangle[0] = objects[i];
-
-                yield return new OsuDifficultyHitObject(triangle, timeRate);
-            }
+            for (int i = 1; i < objects.Count; i++)
+                yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate);
         }
     }
 }
diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
index 415f76ced8..c096e9a464 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -28,26 +28,22 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
         /// </summary>
         public double DeltaTime { get; private set; }
 
-        /// <summary>
-        /// Number of milliseconds until the <see cref="OsuDifficultyHitObject"/> has to be hit.
-        /// </summary>
-        public double TimeUntilHit { get; set; }
 
         private const int normalized_radius = 52;
 
+        private readonly OsuHitObject lastObject;
         private readonly double timeRate;
 
-        private readonly OsuHitObject[] t;
-
         /// <summary>
         /// Initializes the object calculating extra data required for difficulty calculation.
         /// </summary>
-        public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
+        public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate)
         {
+            this.lastObject = lastObject;
             this.timeRate = timeRate;
 
-            t = triangle;
-            BaseObject = t[0];
+            BaseObject = currentObject;
+
             setDistances();
             setTimingValues();
             // Calculate angle here
@@ -63,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
                 scalingFactor *= 1 + smallCircleBonus;
             }
 
-            Vector2 lastCursorPosition = t[1].StackedPosition;
+            Vector2 lastCursorPosition = lastObject.StackedPosition;
             float lastTravelDistance = 0;
 
-            var lastSlider = t[1] as Slider;
+            var lastSlider = lastObject as Slider;
             if (lastSlider != null)
             {
                 computeSliderCursorPosition(lastSlider);
@@ -81,7 +77,6 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
         {
             // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
             DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
-            TimeUntilHit = 450; // BaseObject.PreEmpt;
         }
 
         private void computeSliderCursorPosition(Slider slider)

From 1b5c151e0edf7f528ca344516f47977908f04a75 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:25:02 +0900
Subject: [PATCH 131/177] Fix DeltaTime being capped incorrectly

---
 .../OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
index c096e9a464..d06518df53 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
         private void setTimingValues()
         {
             // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
-            DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
+            DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
         }
 
         private void computeSliderCursorPosition(Slider slider)

From 1acfc1bbabf365c15401b121a6c2f1eab9b46c1f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:25:20 +0900
Subject: [PATCH 132/177] Fix ModHardRock not properly clamping Drain/OD

---
 osu.Game/Rulesets/Mods/ModHardRock.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 090bc737dd..1629945c87 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -21,8 +21,8 @@ namespace osu.Game.Rulesets.Mods
             const float ratio = 1.4f;
             difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio.
             difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f);
-            difficulty.DrainRate *= ratio;
-            difficulty.OverallDifficulty *= ratio;
+            difficulty.DrainRate = Math.Min(difficulty.DrainRate * ratio, 10.0f);
+            difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ratio, 10.0f);
         }
     }
 }

From bc197a88e17f745344efd688f65e520d1f814565 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:25:33 +0900
Subject: [PATCH 133/177] Fix slider length calculation including slider head

---
 .../OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs      | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
index d06518df53..1624b936c8 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -102,7 +102,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
                 }
             });
 
-            var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime);
+            // Skip the head circle
+            var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime);
             foreach (var time in scoringTimes)
                 computeVertex(time);
             computeVertex(slider.EndTime);

From 22f3bee55ae10fa62c0cb7ef9ede89946149529a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:26:06 +0900
Subject: [PATCH 134/177] Fix incorrect iteration over difficulty sections

---
 .../OsuDifficulty/OsuDifficultyCalculator.cs         | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
index 4853cd66cd..92cfb77faa 100644
--- a/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/OsuDifficulty/OsuDifficultyCalculator.cs
@@ -35,18 +35,22 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
                 new Speed()
             };
 
-            double sectionEnd = section_length / TimeRate;
+            double sectionLength = section_length * TimeRate;
+
+            // The first object doesn't generate a strain, so we begin with an incremented section end
+            double currentSectionEnd = 2 * sectionLength;
+
             foreach (OsuDifficultyHitObject h in beatmap)
             {
-                while (h.BaseObject.StartTime > sectionEnd)
+                while (h.BaseObject.StartTime > currentSectionEnd)
                 {
                     foreach (Skill s in skills)
                     {
                         s.SaveCurrentPeak();
-                        s.StartNewSectionFrom(sectionEnd);
+                        s.StartNewSectionFrom(currentSectionEnd);
                     }
 
-                    sectionEnd += section_length;
+                    currentSectionEnd += sectionLength;
                 }
 
                 foreach (Skill s in skills)

From cf08293be15de809bf79c3051278babbd164033b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:44:45 +0900
Subject: [PATCH 135/177] Remove extra newline

---
 .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs       | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
index 595e2d7e3e..29de23406b 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
     /// </summary>
     public class OsuDifficultyHitObject
     {
+        private const int normalized_radius = 52;
+
         /// <summary>
         /// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
         /// </summary>
@@ -28,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
         /// </summary>
         public double DeltaTime { get; private set; }
 
-
-        private const int normalized_radius = 52;
-
         private readonly OsuHitObject lastObject;
         private readonly double timeRate;
 

From 3f67d044291d081df01a259aa40e57ff3274fb82 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Tue, 15 May 2018 21:57:08 +0900
Subject: [PATCH 136/177] Cap CS to 10 also

---
 osu.Game/Rulesets/Mods/ModHardRock.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 1629945c87..6379be9bfc 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods
         public void ApplyToDifficulty(BeatmapDifficulty difficulty)
         {
             const float ratio = 1.4f;
-            difficulty.CircleSize *= 1.3f; // CS uses a custom 1.3 ratio.
+            difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
             difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ratio, 10.0f);
             difficulty.DrainRate = Math.Min(difficulty.DrainRate * ratio, 10.0f);
             difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ratio, 10.0f);

From c0c449e453d2a71043386cf120a633c40ff2c123 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 15 May 2018 22:53:33 +0900
Subject: [PATCH 137/177] Remove unused using

---
 osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
index 49d854ccd2..ba3927b771 100644
--- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -1,7 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using System;
 using System.Linq;
 using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Screens;

From 4019683f6ce6231c3cf04d437adccc8d48e1b651 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 01:24:53 +0900
Subject: [PATCH 138/177] Implement osu!mania performance calculation

---
 .../Difficulty/ManiaDifficultyCalculator.cs   |   3 +-
 .../Difficulty/ManiaPerformanceCalculator.cs  | 122 ++++++++++++++++++
 osu.Game.Rulesets.Mania/ManiaRuleset.cs       |   2 +
 3 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index ac45130fd8..8458e63fca 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -60,7 +60,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
 
             double starRating = calculateDifficulty() * star_scaling_factor;
 
-            categoryDifficulty?.Add("Strain", starRating);
+            if (categoryDifficulty != null)
+                categoryDifficulty["Strain"] = starRating;
 
             return starRating;
         }
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
new file mode 100644
index 0000000000..1704d924f8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -0,0 +1,122 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+    public class ManiaPerformanceCalculator : PerformanceCalculator
+    {
+        private Mod[] mods;
+        private int countGeki;
+        private int countKatu;
+        private int count300;
+        private int count100;
+        private int count50;
+        private int countMiss;
+
+        public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
+            : base(ruleset, beatmap, score)
+        {
+        }
+
+        public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
+        {
+            mods = Score.Mods;
+            countGeki = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
+            countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
+            count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+            count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+            count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+            countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+
+            if (mods.Any(m => !m.Ranked))
+                return 0;
+
+            // Custom multipliers for NoFail
+            double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+
+            if (mods.Any(m => m is ModNoFail))
+                multiplier *= 0.9;
+            if (mods.Any(m => m is ModEasy))
+                multiplier *= 0.5;
+
+            double strainValue = computeStrainValue();
+            double accValue = computeAccuracyValue();
+            double totalValue =
+                Math.Pow(
+                    Math.Pow(strainValue, 1.1) +
+                    Math.Pow(accValue, 1.1), 1.0 / 1.1
+                ) * multiplier;
+
+            if (categoryDifficulty != null)
+            {
+                categoryDifficulty["Strain"] = strainValue;
+                categoryDifficulty["Accuracy"] = accValue;
+
+            }
+
+            return totalValue;
+        }
+
+        private double computeStrainValue()
+        {
+            IEnumerable<Mod> scoreIncreaseMods = new ManiaRuleset().GetModsFor(ModType.DifficultyIncrease);
+
+            double scoreMultiplier = 1.0;
+            foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
+                scoreMultiplier *= m.ScoreMultiplier;
+
+            // Score after being scaled by non-difficulty-increasing mods
+            double scaledScore = Score.TotalScore;
+
+            // Scale score up, so it's comparable to other keymods
+            scaledScore *= 1.0 / scoreMultiplier;
+
+            // Obtain strain difficulty
+            double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.0825) - 4.0, 3.0) / 110000.0;
+
+            // Longer maps are worth more
+            strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
+
+            if (scaledScore <= 500000)
+                strainValue = 0;
+            else if (scaledScore <= 600000)
+                strainValue *= (scaledScore - 500000) / 100000 * 0.3;
+            else if (scaledScore <= 700000)
+                strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.35;
+            else if (scaledScore <= 800000)
+                strainValue *= 0.65 + (scaledScore - 700000) / 100000 * 0.20;
+            else if (scaledScore <= 900000)
+                strainValue *= 0.85 + (scaledScore - 800000) / 100000 * 0.1;
+            else
+                strainValue *= 0.95 + (scaledScore - 900000) / 100000 * 0.05;
+
+            return strainValue;
+        }
+
+        private double computeAccuracyValue()
+        {
+            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+            if (hitWindow300 <= 0)
+                return 0;
+
+            // Lots of arbitrary values from testing.
+            // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+            double accuracyValue = Math.Pow(150.0 / hitWindow300 * Math.Pow(Score.Accuracy, 16), 1.8) * 2.5;
+
+            // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+            accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+
+            return accuracyValue;
+        }
+
+        private double totalHits => countGeki + countKatu + count300 + count100 + count50 + countMiss;
+    }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index b1702de537..02ecb3afda 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -18,6 +18,7 @@ using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mania.Beatmaps;
 using osu.Game.Rulesets.Mania.Difficulty;
+using osu.Game.Rulesets.Scoring;
 
 namespace osu.Game.Rulesets.Mania
 {
@@ -25,6 +26,7 @@ namespace osu.Game.Rulesets.Mania
     {
         public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
         public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+        public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
 
         public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
         {

From 1fdc77d5791cd03af2e69fe62b1a68c57e1589c0 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 01:34:07 +0900
Subject: [PATCH 139/177] Update with the rebalance changes

---
 .../Difficulty/ManiaPerformanceCalculator.cs  | 51 ++++++++++---------
 1 file changed, 28 insertions(+), 23 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 1704d924f8..3337d4df75 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -14,6 +14,10 @@ namespace osu.Game.Rulesets.Mania.Difficulty
     public class ManiaPerformanceCalculator : PerformanceCalculator
     {
         private Mod[] mods;
+
+        // Score after being scaled by non-difficulty-increasing mods
+        private double scaledScore;
+
         private int countGeki;
         private int countKatu;
         private int count300;
@@ -29,6 +33,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
         public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
         {
             mods = Score.Mods;
+            scaledScore = Score.TotalScore;
             countGeki = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
             countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
             count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
@@ -39,8 +44,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
             if (mods.Any(m => !m.Ranked))
                 return 0;
 
-            // Custom multipliers for NoFail
-            double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+            IEnumerable<Mod> scoreIncreaseMods = new ManiaRuleset().GetModsFor(ModType.DifficultyIncrease);
+
+            double scoreMultiplier = 1.0;
+            foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
+                scoreMultiplier *= m.ScoreMultiplier;
+
+            // Scale score up, so it's comparable to other keymods
+            scaledScore *= 1.0 / scoreMultiplier;
+
+            // Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
+            // The specific number has no intrinsic meaning and can be adjusted as needed.
+            double multiplier = 0.8;
 
             if (mods.Any(m => m is ModNoFail))
                 multiplier *= 0.9;
@@ -48,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
                 multiplier *= 0.5;
 
             double strainValue = computeStrainValue();
-            double accValue = computeAccuracyValue();
+            double accValue = computeAccuracyValue(strainValue);
             double totalValue =
                 Math.Pow(
                     Math.Pow(strainValue, 1.1) +
@@ -67,20 +82,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
 
         private double computeStrainValue()
         {
-            IEnumerable<Mod> scoreIncreaseMods = new ManiaRuleset().GetModsFor(ModType.DifficultyIncrease);
-
-            double scoreMultiplier = 1.0;
-            foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
-                scoreMultiplier *= m.ScoreMultiplier;
-
-            // Score after being scaled by non-difficulty-increasing mods
-            double scaledScore = Score.TotalScore;
-
-            // Scale score up, so it's comparable to other keymods
-            scaledScore *= 1.0 / scoreMultiplier;
-
             // Obtain strain difficulty
-            double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.0825) - 4.0, 3.0) / 110000.0;
+            double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0;
 
             // Longer maps are worth more
             strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
@@ -90,18 +93,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
             else if (scaledScore <= 600000)
                 strainValue *= (scaledScore - 500000) / 100000 * 0.3;
             else if (scaledScore <= 700000)
-                strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.35;
+                strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
             else if (scaledScore <= 800000)
-                strainValue *= 0.65 + (scaledScore - 700000) / 100000 * 0.20;
+                strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
             else if (scaledScore <= 900000)
-                strainValue *= 0.85 + (scaledScore - 800000) / 100000 * 0.1;
+                strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
             else
-                strainValue *= 0.95 + (scaledScore - 900000) / 100000 * 0.05;
+                strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
 
             return strainValue;
         }
 
-        private double computeAccuracyValue()
+        private double computeAccuracyValue(double strainValue)
         {
             double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
             if (hitWindow300 <= 0)
@@ -109,10 +112,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
 
             // Lots of arbitrary values from testing.
             // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
-            double accuracyValue = Math.Pow(150.0 / hitWindow300 * Math.Pow(Score.Accuracy, 16), 1.8) * 2.5;
+            double accuracyValue = Math.Max(0.0, 0.2 - (hitWindow300 - 34) * 0.006667)
+                                       * strainValue
+                                       * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
 
             // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
-            accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+            // accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
 
             return accuracyValue;
         }

From ed902d9325eb6c7435e106224bd517a08ab9aa4c Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 01:36:28 +0900
Subject: [PATCH 140/177] Cleanup

---
 osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 3337d4df75..024362f5d2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -74,7 +74,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
             {
                 categoryDifficulty["Strain"] = strainValue;
                 categoryDifficulty["Accuracy"] = accValue;
-
             }
 
             return totalValue;

From de63a1b578d422a1c39b6212018ed3448c806ec5 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 01:43:58 +0900
Subject: [PATCH 141/177] Remove construction of new ruleset

---
 .../Difficulty/ManiaPerformanceCalculator.cs                 | 2 +-
 osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs        | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 024362f5d2..f5f815d9fb 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
             if (mods.Any(m => !m.Ranked))
                 return 0;
 
-            IEnumerable<Mod> scoreIncreaseMods = new ManiaRuleset().GetModsFor(ModType.DifficultyIncrease);
+            IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
 
             double scoreMultiplier = 1.0;
             foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
index 9fd7a0156d..07d9c80061 100644
--- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Difficulty
         private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
         protected IDictionary<string, double> Attributes => attributes;
 
+        protected readonly Ruleset Ruleset;
         protected readonly IBeatmap Beatmap;
         protected readonly Score Score;
 
@@ -23,9 +24,9 @@ namespace osu.Game.Rulesets.Difficulty
 
         protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
         {
-            Score = score;
-
+            Ruleset = ruleset;
             Beatmap = beatmap;
+            Score = score;
 
             var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
             diffCalc.Calculate(attributes);

From b9d99b5f4049531bcb9c255d78c088652c094b29 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 19:42:50 -0300
Subject: [PATCH 142/177] Fix nullref when exiting the last screen.

---
 osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
index ba3927b771..adcf401546 100644
--- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
+++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface
 
         private void screenChanged(Screen newScreen)
         {
+            if (newScreen == null) return;
+
             if (last != null)
             {
                 last.Exited -= screenChanged;

From f67ad7b8e879be7c789e9c763a335f966e4b786a Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 19:52:28 -0300
Subject: [PATCH 143/177] Add exit test.

---
 osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
index a64e60dbd6..edd2e9fd11 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -66,7 +66,9 @@ namespace osu.Game.Tests.Visual
 
             assertCurrent();
             pushNext();
-            AddAssert(@"assert there are only 2 items", () => breadcrumbs.Items.Count() == 2);
+            AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2);
+            AddStep(@"exit current", () => changedScreen.Exit());
+            AddAssert(@"current screen is first", () => startScreen == changedScreen);
         }
 
         [BackgroundDependencyLoader]
@@ -76,7 +78,7 @@ namespace osu.Game.Tests.Visual
         }
 
         private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
-        private void assertCurrent() => AddAssert(@"assert the current screen is correct", () => currentScreen == changedScreen);
+        private void assertCurrent() => AddAssert(@"current screen is correct", () => currentScreen == changedScreen);
 
         private abstract class TestScreen : OsuScreen
         {

From dda253758bfb4ade3bc3f6686cb2fccac6e17fc3 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 19:56:47 -0300
Subject: [PATCH 144/177] Cleanup test step wording.

---
 osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
index edd2e9fd11..7a743655f4 100644
--- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
+++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual
 
             Children = new Drawable[]
             {
-                changedScreen = currentScreen = startScreen = new TestScreenOne(),
+                currentScreen = startScreen = new TestScreenOne(),
                 new FillFlowContainer
                 {
                     RelativeSizeAxes = Axes.X,
@@ -52,6 +52,8 @@ namespace osu.Game.Tests.Visual
                 changedScreen = s;
             };
 
+            breadcrumbs.Current.TriggerChange();
+
             assertCurrent();
             pushNext();
             assertCurrent();
@@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual
         }
 
         private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
-        private void assertCurrent() => AddAssert(@"current screen is correct", () => currentScreen == changedScreen);
+        private void assertCurrent() => AddAssert(@"changedScreen correct", () => currentScreen == changedScreen);
 
         private abstract class TestScreen : OsuScreen
         {

From 1450bf64f5c57f08ab85bc2900876c904d03c08e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 20:34:14 -0300
Subject: [PATCH 145/177] Add multiplayer screen header.

---
 osu.Game.Tests/Visual/TestCaseMultiHeader.cs |  27 +++++
 osu.Game/Screens/Multi/Header.cs             | 101 +++++++++++++++++++
 2 files changed, 128 insertions(+)
 create mode 100644 osu.Game.Tests/Visual/TestCaseMultiHeader.cs
 create mode 100644 osu.Game/Screens/Multi/Header.cs

diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
new file mode 100644
index 0000000000..af51a6221f
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Multi;
+using osu.Game.Screens.Multi.Screens;
+
+namespace osu.Game.Tests.Visual
+{
+    [TestFixture]
+    public class TestCaseMultiHeader : OsuTestCase
+    {
+        public TestCaseMultiHeader()
+        {
+            Lobby lobby;
+            Children = new Drawable[]
+            {
+                lobby = new Lobby
+                {
+                    Padding = new MarginPadding { Top = Header.HEIGHT },
+                },
+                new Header(lobby),
+            };
+        }
+    }
+}
diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs
new file mode 100644
index 0000000000..03996f5309
--- /dev/null
+++ b/osu.Game/Screens/Multi/Header.cs
@@ -0,0 +1,101 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.SearchableList;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Multi
+{
+    public class Header : Container
+    {
+        public const float HEIGHT = 121;
+
+        private readonly OsuSpriteText screenTitle;
+        private readonly ScreenBreadcrumbControl breadcrumbs;
+
+        public Header(Screen initialScreen)
+        {
+            RelativeSizeAxes = Axes.X;
+            Height = HEIGHT;
+
+            Children = new Drawable[]
+            {
+                new Box
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Colour = OsuColour.FromHex(@"2f2043"),
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
+                    Children = new Drawable[]
+                    {
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.CentreLeft,
+                            Origin = Anchor.BottomLeft,
+                            Position = new Vector2(-35f, 5f),
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Horizontal,
+                            Spacing = new Vector2(10f, 0f),
+                            Children = new Drawable[]
+                            {
+                                new SpriteIcon
+                                {
+                                    Size = new Vector2(25),
+                                    Icon = FontAwesome.fa_osu_multi,
+                                },
+                                new FillFlowContainer
+                                {
+                                    AutoSizeAxes = Axes.Both,
+                                    Direction = FillDirection.Horizontal,
+                                    Children = new[]
+                                    {
+                                        new OsuSpriteText
+                                        {
+                                            Text = "multiplayer ",
+                                            TextSize = 25,
+                                        },
+                                        screenTitle = new OsuSpriteText
+                                        {
+                                            TextSize = 25,
+                                            Font = @"Exo2.0-Light",
+                                        },
+                                    },
+                                },
+                            },
+                        },
+                        breadcrumbs = new ScreenBreadcrumbControl(initialScreen)
+                        {
+                            Anchor = Anchor.BottomLeft,
+                            Origin = Anchor.BottomLeft,
+                            RelativeSizeAxes = Axes.X,
+                        },
+                    },
+                },
+            };
+
+            breadcrumbs.OnLoadComplete = d => breadcrumbs.AccentColour = Color4.White;
+
+            breadcrumbs.Current.ValueChanged += s => screenTitle.Text = s.ToString();
+            breadcrumbs.Current.TriggerChange();
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours)
+        {
+            screenTitle.Colour = colours.Yellow;
+            breadcrumbs.StripColour = colours.Green;
+        }
+    }
+}

From 1a78ac3d10629a12302133f6d26d209c60adfc3d Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 21:14:10 -0300
Subject: [PATCH 146/177] Add Multiplayer screen.

---
 osu.Game.Tests/Visual/TestCaseMultiScreen.cs |  17 ++++
 osu.Game/Screens/Menu/MainMenu.cs            |   4 +-
 osu.Game/Screens/Multi/Multiplayer.cs        | 100 +++++++++++++++++++
 3 files changed, 119 insertions(+), 2 deletions(-)
 create mode 100644 osu.Game.Tests/Visual/TestCaseMultiScreen.cs
 create mode 100644 osu.Game/Screens/Multi/Multiplayer.cs

diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
new file mode 100644
index 0000000000..2dd3e81ee1
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Game.Screens.Multi;
+
+namespace osu.Game.Tests.Visual
+{
+    [TestFixture]
+    public class TestCaseMultiScreen : OsuTestCase
+    {
+        public TestCaseMultiScreen()
+        {
+            Child = new Multiplayer();
+        }
+    }
+}
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index e564ab786d..600fad8da5 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -14,7 +14,7 @@ using osu.Game.Screens.Backgrounds;
 using osu.Game.Screens.Charts;
 using osu.Game.Screens.Direct;
 using osu.Game.Screens.Edit;
-using osu.Game.Screens.Multi.Screens;
+using osu.Game.Screens.Multi;
 using osu.Game.Screens.Select;
 using osu.Game.Screens.Tournament;
 
@@ -54,7 +54,7 @@ namespace osu.Game.Screens.Menu
                             OnDirect = delegate { Push(new OnlineListing()); },
                             OnEdit = delegate { Push(new Editor()); },
                             OnSolo = delegate { Push(consumeSongSelect()); },
-                            OnMulti = delegate { Push(new Lobby()); },
+                            OnMulti = delegate { Push(new Multiplayer()); },
                             OnExit = Exit,
                         }
                     }
diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs
new file mode 100644
index 0000000000..b3d393209c
--- /dev/null
+++ b/osu.Game/Screens/Multi/Multiplayer.cs
@@ -0,0 +1,100 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Graphics.Containers;
+using osu.Game.Screens.Multi.Screens;
+
+namespace osu.Game.Screens.Multi
+{
+    public class Multiplayer : OsuScreen
+    {
+        private readonly MultiplayerWaveContainer waves;
+
+        protected override Container<Drawable> Content => waves;
+
+        public Multiplayer()
+        {
+            InternalChild = waves = new MultiplayerWaveContainer
+            {
+                RelativeSizeAxes = Axes.Both,
+            };
+
+            Lobby lobby;
+            Children = new Drawable[]
+            {
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Masking = true,
+                    Children = new Drawable[]
+                    {
+                        new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Colour = OsuColour.FromHex(@"3e3a44"),
+                        },
+                        new Triangles
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            ColourLight = OsuColour.FromHex(@"3c3842"),
+                            ColourDark = OsuColour.FromHex(@"393540"),
+                            TriangleScale = 5,
+                        },
+                    },
+                },
+                new Container
+                {
+                    RelativeSizeAxes = Axes.Both,
+                    Padding = new MarginPadding { Top = Header.HEIGHT },
+                    Child = lobby = new Lobby(),
+                },
+                new Header(lobby),
+            };
+
+            lobby.Exited += s => Exit();
+        }
+
+        protected override void OnEntering(Screen last)
+        {
+            base.OnEntering(last);
+            waves.Show();
+        }
+
+        protected override bool OnExiting(Screen next)
+        {
+            waves.Hide();
+            return base.OnExiting(next);
+        }
+
+        protected override void OnResuming(Screen last)
+        {
+            base.OnResuming(last);
+            waves.Show();
+        }
+
+        protected override void OnSuspending(Screen next)
+        {
+            base.OnSuspending(next);
+            waves.Hide();
+        }
+
+        private class MultiplayerWaveContainer : WaveContainer
+        {
+            protected override bool StartHidden => true;
+
+            public MultiplayerWaveContainer()
+            {
+                FirstWaveColour = OsuColour.FromHex(@"654d8c");
+                SecondWaveColour = OsuColour.FromHex(@"554075");
+                ThirdWaveColour = OsuColour.FromHex(@"44325e");
+                FourthWaveColour = OsuColour.FromHex(@"392850");
+            }
+        }
+    }
+}

From 8e053f2166a00f96865713d7c8b1d6a5532972ac Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Tue, 15 May 2018 21:20:34 -0300
Subject: [PATCH 147/177] Add multiplayer screen test steps.

---
 osu.Game.Tests/Visual/TestCaseMultiScreen.cs | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
index 2dd3e81ee1..6c22fb020f 100644
--- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
+++ b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs
@@ -11,7 +11,11 @@ namespace osu.Game.Tests.Visual
     {
         public TestCaseMultiScreen()
         {
-            Child = new Multiplayer();
+            Multiplayer multi = new Multiplayer();
+
+            AddStep(@"show", () => Add(multi));
+            AddWaitStep(5);
+            AddStep(@"exit", multi.Exit);
         }
     }
 }

From edbb3a5a57448e1bde365f7df4a7265d8610c07a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 12:44:11 +0900
Subject: [PATCH 148/177] Rename to use new hit result namings

---
 .../Difficulty/ManiaPerformanceCalculator.cs  | 28 +++++++++----------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index f5f815d9fb..e6e3028d62 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -18,11 +18,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty
         // Score after being scaled by non-difficulty-increasing mods
         private double scaledScore;
 
-        private int countGeki;
-        private int countKatu;
-        private int count300;
-        private int count100;
-        private int count50;
+        private int countPerfect;
+        private int countGreat;
+        private int countGood;
+        private int countOk;
+        private int countMeh;
         private int countMiss;
 
         public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
@@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Mania.Difficulty
         {
             mods = Score.Mods;
             scaledScore = Score.TotalScore;
-            countGeki = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
-            countKatu = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
-            count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
-            count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
-            count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+            countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
+            countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+            countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+            countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
+            countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
             countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
 
             if (mods.Any(m => !m.Ranked))
@@ -105,13 +105,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty
 
         private double computeAccuracyValue(double strainValue)
         {
-            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
-            if (hitWindow300 <= 0)
+            double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+            if (hitWindowGreat <= 0)
                 return 0;
 
             // Lots of arbitrary values from testing.
             // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
-            double accuracyValue = Math.Max(0.0, 0.2 - (hitWindow300 - 34) * 0.006667)
+            double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
                                        * strainValue
                                        * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
 
@@ -121,6 +121,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
             return accuracyValue;
         }
 
-        private double totalHits => countGeki + countKatu + count300 + count100 + count50 + countMiss;
+        private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
     }
 }

From c1d9fcc140509ec4022f7c6d44449f31d8a0fbab Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 12:46:31 +0900
Subject: [PATCH 149/177] Make OsuPerformanceCalculator use the new hitresult
 namings

---
 .../Scoring/OsuPerformanceCalculator.cs       | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
index 938060a664..84a83d331b 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
@@ -32,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
 
         private double accuracy;
         private int scoreMaxCombo;
-        private int count300;
-        private int count100;
-        private int count50;
+        private int countGreat;
+        private int countGood;
+        private int countMeh;
         private int countMiss;
 
         public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
@@ -52,9 +52,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
             mods = Score.Mods;
             accuracy = Score.Accuracy;
             scoreMaxCombo = Score.MaxCombo;
-            count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
-            count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
-            count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+            countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+            countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+            countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
             countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
 
             // Don't count scores made with supposedly unranked mods
@@ -71,10 +71,10 @@ namespace osu.Game.Rulesets.Osu.Scoring
                 ar = Math.Max(0, ar / 2);
 
             double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate;
-            double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+            double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
 
             realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
-            realOverallDifficulty = (80 - 0.5 - hitWindow300) / 6;
+            realOverallDifficulty = (80 - 0.5 - hitWindowGreat) / 6;
 
             // Custom multipliers for NoFail and SpunOut.
             double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             int amountHitObjectsWithAccuracy = countHitCircles;
 
             if (amountHitObjectsWithAccuracy > 0)
-                betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
+                betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
             else
                 betterAccuracyPercentage = 0;
 
@@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
             return accuracyValue;
         }
 
-        private double totalHits => count300 + count100 + count50 + countMiss;
-        private double totalSuccessfulHits => count300 + count100 + count50;
+        private double totalHits => countGreat + countGood + countMeh + countMiss;
+        private double totalSuccessfulHits => countGreat + countGood + countMeh;
     }
 }

From 8d8b7e6fa2d3332575a2280704ddeb493a1f81cc Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 13:09:48 +0900
Subject: [PATCH 150/177] Fix conversion tests for ruleset-specific beatmaps
 not working

TRuleset was a mistake. This resulted in cases that checked whether the beatmap was for the current ruleset (TaikoBeatmapConverter and ManiaBeatmapConverter) failing due to TestXRuleset != XRuleset...
---
 .../CatchBeatmapConversionTest.cs                      | 10 ++--------
 .../ManiaBeatmapConversionTest.cs                      | 10 ++--------
 .../OsuBeatmapConversionTest.cs                        | 10 ++--------
 .../TaikoBeatmapConversionTest.cs                      | 10 ++--------
 osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs       |  9 ++++-----
 5 files changed, 12 insertions(+), 37 deletions(-)

diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 808faa511b..5b34e46247 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -5,8 +5,6 @@ using System;
 using System.Collections.Generic;
 using NUnit.Framework;
 using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Beatmaps;
 using osu.Game.Rulesets.Catch.Objects;
 using osu.Game.Rulesets.Catch.UI;
 using osu.Game.Rulesets.Objects;
@@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Catch.Tests
 {
-    internal class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
+    internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
 
@@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
             }
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+        protected override Ruleset CreateRuleset() => new CatchRuleset();
     }
 
     internal struct ConvertValue : IEquatable<ConvertValue>
@@ -64,8 +62,4 @@ namespace osu.Game.Rulesets.Catch.Tests
             => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
                && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
     }
-
-    internal class TestCatchRuleset : CatchRuleset
-    {
-    }
 }
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index bd67a7d96a..5ae899f6d6 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -5,8 +5,6 @@ using System;
 using System.Collections.Generic;
 using NUnit.Framework;
 using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Mania.Beatmaps;
 using osu.Game.Rulesets.Mania.Objects;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
@@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Mania.Tests
 {
-    internal class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
+    internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
 
@@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+        protected override Ruleset CreateRuleset() => new ManiaRuleset();
     }
 
     internal struct ConvertValue : IEquatable<ConvertValue>
@@ -54,8 +52,4 @@ namespace osu.Game.Rulesets.Mania.Tests
                && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
                && Column == other.Column;
     }
-
-    internal class TestManiaRuleset : ManiaRuleset
-    {
-    }
 }
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 3d54043027..386ae5eb05 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -5,17 +5,15 @@ using System;
 using System.Collections.Generic;
 using NUnit.Framework;
 using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Osu.Beatmaps;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Tests.Beatmaps;
 using OpenTK;
 
 namespace osu.Game.Rulesets.Osu.Tests
 {
-    internal class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
+    internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
 
@@ -42,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+        protected override Ruleset CreateRuleset() => new OsuRuleset();
     }
 
     internal struct ConvertValue : IEquatable<ConvertValue>
@@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Osu.Tests
                && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
                && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
     }
-
-    internal class TestOsuRuleset : OsuRuleset
-    {
-    }
 }
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index ca4fc3ec57..93f1b7faf7 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -5,16 +5,14 @@ using System;
 using System.Collections.Generic;
 using NUnit.Framework;
 using osu.Framework.MathUtils;
-using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Taiko.Beatmaps;
 using osu.Game.Rulesets.Taiko.Objects;
 using osu.Game.Tests.Beatmaps;
 
 namespace osu.Game.Rulesets.Taiko.Tests
 {
-    internal class TaikoBeatmapConversionTest : BeatmapConversionTest<TestTaikoRuleset, ConvertValue>
+    internal class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
     {
         protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
 
@@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
             };
         }
 
-        protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+        protected override Ruleset CreateRuleset() => new TaikoRuleset();
     }
 
     internal struct ConvertValue : IEquatable<ConvertValue>
@@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Taiko.Tests
                && IsSwell == other.IsSwell
                && IsStrong == other.IsStrong;
     }
-
-    internal class TestTaikoRuleset : TaikoRuleset
-    {
-    }
 }
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 2de38d49a9..7470f6ebed 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -16,8 +16,7 @@ using osu.Game.Rulesets.Objects;
 namespace osu.Game.Tests.Beatmaps
 {
     [TestFixture]
-    public abstract class BeatmapConversionTest<TRuleset, TConvertValue>
-        where TRuleset : Ruleset, new()
+    public abstract class BeatmapConversionTest<TConvertValue>
         where TConvertValue : IEquatable<TConvertValue>
     {
         private const string resource_namespace = "Testing.Beatmaps";
@@ -81,12 +80,12 @@ namespace osu.Game.Tests.Beatmaps
         {
             var beatmap = getBeatmap(name);
 
-            var rulesetInstance = new TRuleset();
+            var rulesetInstance = CreateRuleset();
             beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
 
             var result = new ConvertResult();
 
-            var converter = CreateConverter(beatmap);
+            var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
             converter.ObjectConverted += (orig, converted) =>
             {
                 converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
@@ -130,7 +129,7 @@ namespace osu.Game.Tests.Beatmaps
         }
 
         protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
-        protected abstract IBeatmapConverter CreateConverter(IBeatmap beatmap);
+        protected abstract Ruleset CreateRuleset();
 
         private class ConvertMapping
         {

From cf44357bdb1f4f6f6f4bce4e136e3ac872a12a55 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 13:30:48 +0900
Subject: [PATCH 151/177] Use a stable sort for hitobjects

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

diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 655355913c..7317c7a135 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Beatmaps.Formats
 
             // objects may be out of order *only* if a user has manually edited an .osu file.
             // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
-            this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime));
+            this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
 
             foreach (var hitObject in this.beatmap.HitObjects)
                 hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);

From 0f817d18d4cfb1e17ff7a547ad284819cdb4829a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 13:59:51 +0900
Subject: [PATCH 152/177] Add explanatory comment

---
 osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 7317c7a135..2aee419d20 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -54,8 +54,10 @@ namespace osu.Game.Beatmaps.Formats
 
             base.ParseStreamInto(stream, beatmap);
 
-            // objects may be out of order *only* if a user has manually edited an .osu file.
-            // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
+            // Objects may be out of order *only* if a user has manually edited an .osu file.
+            // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
+            // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted)
+            // The parsing order of hitobjects matters in mania difficulty calculation
             this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
 
             foreach (var hitObject in this.beatmap.HitObjects)

From 5aadc35a25ebdd5f9233e7d16c93378fdbfa26df Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 14:47:28 +0900
Subject: [PATCH 153/177] Stably-sort difficulty hitobjects to prevent future
 issues

---
 .../Difficulty/ManiaDifficultyCalculator.cs                | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index ac45130fd8..1483283283 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Difficulty;
 using osu.Game.Rulesets.Mania.Beatmaps;
@@ -49,11 +50,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
 
             int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
 
-            foreach (var hitObject in Beatmap.HitObjects)
-                difficultyHitObjects.Add(new ManiaHitObjectDifficulty((ManiaHitObject)hitObject, columnCount));
-
             // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
-            difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
+            // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
+            difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
 
             if (!calculateStrainValues())
                 return 0;

From 2fc1939d650f8187d97155455a2dff4350f9a76e Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 16 May 2018 19:43:01 +0900
Subject: [PATCH 154/177] Fix hold notes never dying

---
 .../Judgements/HoldNoteJudgement.cs                 | 13 +++++++++++++
 .../Objects/Drawables/DrawableHoldNote.cs           | 13 +++++++++++++
 2 files changed, 26 insertions(+)
 create mode 100644 osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs

diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
new file mode 100644
index 0000000000..9630ba9273
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Judgements
+{
+    public class HoldNoteJudgement : ManiaJudgement
+    {
+        public override bool AffectsCombo => false;
+        protected override int NumericResultFor(HitResult result) => 0;
+    }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index f8b2311a13..3661c0be04 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -99,6 +99,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
 
         protected override void UpdateState(ArmedState state)
         {
+            switch (state)
+            {
+                case ArmedState.Hit:
+                    // Good enough for now, we just want them to have a lifetime end
+                    this.Delay(2000).Expire();
+                    break;
+            }
+        }
+
+        protected override void CheckForJudgements(bool userTriggered, double timeOffset)
+        {
+            if (tail.AllJudged)
+                AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect });
         }
 
         protected override void Update()

From c67f37256023dfdef5bfabba951adbac330785a5 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 12:29:33 +0900
Subject: [PATCH 155/177] Don't create nested hitobjects unless absolutely
 required

---
 osu.Game/Rulesets/Objects/HitObject.cs | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index cd612a5387..15c24e2975 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -1,6 +1,7 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
+using System;
 using System.Collections.Generic;
 using Newtonsoft.Json;
 using osu.Framework.Extensions.IEnumerableExtensions;
@@ -56,10 +57,10 @@ namespace osu.Game.Rulesets.Objects
         /// </summary>
         public HitWindows HitWindows { get; set; }
 
-        private readonly SortedList<HitObject> nestedHitObjects = new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+        private readonly Lazy<SortedList<HitObject>> nestedHitObjects = new Lazy<SortedList<HitObject>>(() => new SortedList<HitObject>((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)));
 
         [JsonIgnore]
-        public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects;
+        public IReadOnlyList<HitObject> NestedHitObjects => nestedHitObjects.Value;
 
         /// <summary>
         /// Applies default values to this HitObject.
@@ -70,13 +71,19 @@ namespace osu.Game.Rulesets.Objects
         {
             ApplyDefaultsToSelf(controlPointInfo, difficulty);
 
-            nestedHitObjects.Clear();
+            if (nestedHitObjects.IsValueCreated)
+                nestedHitObjects.Value.Clear();
+
             CreateNestedHitObjects();
-            nestedHitObjects.ForEach(h =>
+
+            if (nestedHitObjects.IsValueCreated)
             {
-                h.HitWindows = HitWindows;
-                h.ApplyDefaults(controlPointInfo, difficulty);
-            });
+                nestedHitObjects.Value.ForEach(h =>
+                {
+                    h.HitWindows = HitWindows;
+                    h.ApplyDefaults(controlPointInfo, difficulty);
+                });
+            }
         }
 
         protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@@ -96,7 +103,7 @@ namespace osu.Game.Rulesets.Objects
         {
         }
 
-        protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject);
+        protected void AddNested(HitObject hitObject) => nestedHitObjects.Value.Add(hitObject);
 
         /// <summary>
         /// Creates the <see cref="HitWindows"/> for this <see cref="HitObject"/>.

From 397d93660aad78e1ba4a759c48d28b54ba99d7cd Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 12:59:48 +0900
Subject: [PATCH 156/177] Don't deep-clone beatmapinfo/control points

---
 .../Beatmaps/TaikoBeatmapConverter.cs                 |  6 +++---
 osu.Game/Beatmaps/Beatmap.cs                          | 11 ++---------
 osu.Game/Beatmaps/BeatmapConverter.cs                 |  2 --
 osu.Game/Beatmaps/BeatmapDifficulty.cs                |  5 +++++
 osu.Game/Beatmaps/BeatmapInfo.cs                      |  5 +++++
 osu.Game/IO/Serialization/IJsonSerializable.cs        |  2 --
 .../UI/Scrolling/ScrollingRulesetContainer.cs         |  3 +--
 7 files changed, 16 insertions(+), 18 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index b450e4d26c..abb2193235 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Taiko.Objects;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using osu.Game.IO.Serialization;
 using osu.Game.Audio;
 using osu.Game.Beatmaps.ControlPoints;
 
@@ -51,8 +50,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
         protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
         {
             // Rewrite the beatmap info to add the slider velocity multiplier
-            BeatmapInfo info = original.BeatmapInfo.DeepClone();
-            info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
+            original.BeatmapInfo = original.BeatmapInfo.Clone();
+            original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
+            original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
 
             Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
 
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 84897853d8..9aabb434a3 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects;
 using System.Collections.Generic;
 using System.Linq;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.IO.Serialization;
 using Newtonsoft.Json;
 using osu.Game.IO.Serialization.Converters;
 
@@ -55,17 +54,11 @@ namespace osu.Game.Beatmaps
 
         IBeatmap IBeatmap.Clone() => Clone();
 
-        public Beatmap<T> Clone()
-        {
-            var newInstance = (Beatmap<T>)MemberwiseClone();
-            newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
-
-            return newInstance;
-        }
+        public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
     }
 
     public class Beatmap : Beatmap<HitObject>
     {
-        public Beatmap Clone() => (Beatmap)base.Clone();
+        public new Beatmap Clone() => (Beatmap)base.Clone();
     }
 }
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index b7a454460f..a1bb70135a 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -53,8 +53,6 @@ namespace osu.Game.Beatmaps
         {
             var beatmap = CreateBeatmap();
 
-            // todo: this *must* share logic (or directly use) Beatmap<T>'s constructor.
-            // right now this isn't easily possible due to generic entanglement.
             beatmap.BeatmapInfo = original.BeatmapInfo;
             beatmap.ControlPointInfo = original.ControlPointInfo;
             beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 855e8fe881..508232dbfe 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps
         public double SliderMultiplier { get; set; } = 1;
         public double SliderTickRate { get; set; } = 1;
 
+        /// <summary>
+        /// Returns a shallow-clone of this <see cref="BeatmapDifficulty"/>.
+        /// </summary>
+        public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone();
+
         /// <summary>
         /// Maps a difficulty value [0, 10] to a two-piece linear range of values.
         /// </summary>
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index a1b97afc6c..40d62103a8 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -143,5 +143,10 @@ namespace osu.Game.Beatmaps
         public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
                                                       BeatmapSet.Hash == other.BeatmapSet.Hash &&
                                                       (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
+
+        /// <summary>
+        /// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
+        /// </summary>
+        public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
     }
 }
diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs
index c9727725ef..ce6ff7c82d 100644
--- a/osu.Game/IO/Serialization/IJsonSerializable.cs
+++ b/osu.Game/IO/Serialization/IJsonSerializable.cs
@@ -18,8 +18,6 @@ namespace osu.Game.IO.Serialization
 
         public static void DeserializeInto<T>(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings());
 
-        public static T DeepClone<T>(this T obj) where T : IJsonSerializable => Deserialize<T>(Serialize(obj));
-
         /// <summary>
         /// Creates the default <see cref="JsonSerializerSettings"/> that should be used for all <see cref="IJsonSerializable"/>s.
         /// </summary>
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
index efd901240a..3fc67e4e34 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs
@@ -8,7 +8,6 @@ using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Lists;
 using osu.Game.Beatmaps;
 using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.IO.Serialization;
 using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Objects.Types;
 using osu.Game.Rulesets.Timing;
@@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
             if (index < 0)
                 return new MultiplierControlPoint(time);
 
-            return new MultiplierControlPoint(time, DefaultControlPoints[index].DeepClone());
+            return new MultiplierControlPoint(time, DefaultControlPoints[index]);
         }
     }
 }

From f67d2635960ac1ca37a7297f02b255b3f47ac413 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 13:35:06 +0900
Subject: [PATCH 157/177] Move ruleset-specific hitwindows to post-converted
 hitobjects

---
 .../Beatmaps/ManiaBeatmapConverter.cs          |  3 ---
 .../Objects/ManiaHitObject.cs                  | 10 +---------
 .../Objects/ManiaHitWindows.cs                 |  8 ++++++--
 .../Beatmaps/OsuBeatmapConverter.cs            |  9 +++------
 osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs  |  2 ++
 .../Objects/OsuHitWindows.cs                   |  5 +++--
 .../Beatmaps/TaikoBeatmapConverter.cs          | 18 ++++++------------
 .../Objects/TaikoHitObject.cs                  |  2 ++
 .../Objects/TaikoHitWindows.cs                 |  5 +++--
 .../Objects/Legacy/Mania/ConvertHit.cs         |  2 +-
 .../Objects/Legacy/Mania/ConvertHold.cs        |  2 +-
 .../Objects/Legacy/Mania/ConvertSlider.cs      |  2 +-
 .../Objects/Legacy/Mania/ConvertSpinner.cs     |  2 +-
 .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs  |  2 +-
 .../Objects/Legacy/Osu/ConvertSlider.cs        |  2 +-
 .../Objects/Legacy/Osu/ConvertSpinner.cs       |  2 +-
 .../Objects/Legacy/Taiko/ConvertHit.cs         |  2 +-
 .../Objects/Legacy/Taiko/ConvertSlider.cs      |  2 +-
 .../Objects/Legacy/Taiko/ConvertSpinner.cs     |  2 +-
 19 files changed, 36 insertions(+), 46 deletions(-)
 rename osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs => osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs (88%)
 rename osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs => osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs (90%)
 rename osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs => osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs (90%)

diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 4f7c52860f..19fef9eb54 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -84,10 +84,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
                 yield break;
 
             foreach (ManiaHitObject obj in objects)
-            {
-                obj.HitWindows = original.HitWindows;
                 yield return obj;
-            }
         }
 
         private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 4f0e02ff0d..e183098a51 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -1,8 +1,6 @@
 // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
 // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
 
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
 using osu.Game.Rulesets.Mania.Objects.Types;
 using osu.Game.Rulesets.Objects;
 
@@ -12,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Objects
     {
         public virtual int Column { get; set; }
 
-        protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
-        {
-            base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
-
-            HitWindows.AllowsPerfect = true;
-            HitWindows.AllowsOk = true;
-        }
+        protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
similarity index 88%
rename from osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs
rename to osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
index 131492ea12..063b626af1 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
@@ -3,11 +3,12 @@
 
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Scoring;
 
-namespace osu.Game.Rulesets.Objects.Legacy.Mania
+namespace osu.Game.Rulesets.Mania.Objects
 {
-    public class ConvertHitWindows : HitWindows
+    public class ManiaHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
@@ -21,6 +22,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public override void SetDifficulty(double difficulty)
         {
+            AllowsPerfect = true;
+            AllowsOk = true;
+
             Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
             Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
             Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 4369a31b2c..80eb808f6e 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -40,8 +40,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     RepeatSamples = curveData.RepeatSamples,
                     RepeatCount = curveData.RepeatCount,
                     Position = positionData?.Position ?? Vector2.Zero,
-                    NewCombo = comboData?.NewCombo ?? false,
-                    HitWindows = original.HitWindows
+                    NewCombo = comboData?.NewCombo ?? false
                 };
             }
             else if (endTimeData != null)
@@ -51,8 +50,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     StartTime = original.StartTime,
                     Samples = original.Samples,
                     EndTime = endTimeData.EndTime,
-                    Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
-                    HitWindows = original.HitWindows
+                    Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2
                 };
             }
             else
@@ -62,8 +60,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
                     StartTime = original.StartTime,
                     Samples = original.Samples,
                     Position = positionData?.Position ?? Vector2.Zero,
-                    NewCombo = comboData?.NewCombo ?? false,
-                    HitWindows = original.HitWindows
+                    NewCombo = comboData?.NewCombo ?? false
                 };
             }
         }
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 2b7b7783e2..54126b934f 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -71,5 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects
         }
 
         public virtual void OffsetPosition(Vector2 offset) => Position += offset;
+
+        protected override HitWindows CreateHitWindows() => new OsuHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
similarity index 90%
rename from osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs
rename to osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
index fd86173372..8405498554 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitWindows.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs
@@ -3,11 +3,12 @@
 
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Scoring;
 
-namespace osu.Game.Rulesets.Objects.Legacy.Osu
+namespace osu.Game.Rulesets.Osu.Objects
 {
-    public class ConvertHitWindows : HitWindows
+    public class OsuHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index b450e4d26c..a84b1a64a0 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -132,8 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                             {
                                 StartTime = j,
                                 Samples = currentSamples,
-                                IsStrong = strong,
-                                HitWindows = obj.HitWindows
+                                IsStrong = strong
                             };
                         }
                         else
@@ -142,8 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                             {
                                 StartTime = j,
                                 Samples = currentSamples,
-                                IsStrong = strong,
-                                HitWindows = obj.HitWindows
+                                IsStrong = strong
                             };
                         }
 
@@ -158,8 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                         Samples = obj.Samples,
                         IsStrong = strong,
                         Duration = taikoDuration,
-                        TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
-                        HitWindows = obj.HitWindows
+                        TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
                     };
                 }
             }
@@ -173,8 +170,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                     Samples = obj.Samples,
                     IsStrong = strong,
                     Duration = endTimeData.Duration,
-                    RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
-                    HitWindows = obj.HitWindows
+                    RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
                 };
             }
             else
@@ -187,8 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                     {
                         StartTime = obj.StartTime,
                         Samples = obj.Samples,
-                        IsStrong = strong,
-                        HitWindows = obj.HitWindows
+                        IsStrong = strong
                     };
                 }
                 else
@@ -197,8 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                     {
                         StartTime = obj.StartTime,
                         Samples = obj.Samples,
-                        IsStrong = strong,
-                        HitWindows = obj.HitWindows
+                        IsStrong = strong
                     };
                 }
             }
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 63de096238..ffbbe28f2e 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -27,5 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
         /// Strong hit objects give more points for hitting the hit object with both keys.
         /// </summary>
         public bool IsStrong;
+
+        protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
similarity index 90%
rename from osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs
rename to osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
index 6fbf7e122f..289f084a45 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitWindows.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs
@@ -3,11 +3,12 @@
 
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
 using osu.Game.Rulesets.Scoring;
 
-namespace osu.Game.Rulesets.Objects.Legacy.Taiko
+namespace osu.Game.Rulesets.Taiko.Objects
 {
-    public class ConvertHitWindows : HitWindows
+    public class TaikoHitWindows : HitWindows
     {
         private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
         {
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
index 939d3b9c93..ea4e7f6907 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
index 22abc64b60..86a10fd363 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs
@@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public double Duration => EndTime - StartTime;
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
index 6bca5b717c..a8d7b23df1 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
index 1dc826af9b..5a443c2ac2 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
 
         public float X { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
index 23955b2d23..f015272b2c 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
index 35b8c1c7dd..ec5a002bbb 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs
@@ -19,6 +19,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
index 73b8369aca..0141785f31 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs
@@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
 
         public float Y => Position.Y;
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
index 11db086778..5e9786c84a 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs
@@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     {
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
index 95c69222b5..8a9a0db0a7 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs
@@ -12,6 +12,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
     {
         public bool NewCombo { get; set; }
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }
diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
index 7baea212ea..4c8807a1d3 100644
--- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs
@@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
 
         public double Duration => EndTime - StartTime;
 
-        protected override HitWindows CreateHitWindows() => new ConvertHitWindows();
+        protected override HitWindows CreateHitWindows() => null;
     }
 }

From 43cdbec0a3f78d612e901b703803abfb67e3e685 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 13:59:04 +0900
Subject: [PATCH 158/177] Fix hold note hitwindow lenience

---
 .../Objects/Drawables/DrawableHoldNote.cs     | 10 ++++++
 osu.Game.Rulesets.Mania/Objects/HoldNote.cs   | 22 +-----------
 osu.Game/Rulesets/Objects/HitWindows.cs       | 34 -------------------
 3 files changed, 11 insertions(+), 55 deletions(-)

diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index f8b2311a13..59808c5643 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -191,6 +191,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
         /// </summary>
         private class DrawableTailNote : DrawableNote
         {
+            /// <summary>
+            /// Lenience of release hit windows. This is to make cases where the hold note release
+            /// is timed alongside presses of other hit objects less awkward.
+            /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+            /// </summary>
+            private const double release_window_lenience = 1.5;
+
             private readonly DrawableHoldNote holdNote;
 
             public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
@@ -203,6 +210,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
 
             protected override void CheckForJudgements(bool userTriggered, double timeOffset)
             {
+                // Factor in the release lenience
+                timeOffset /= release_window_lenience;
+
                 if (!userTriggered)
                 {
                     if (!HitObject.HitWindows.CanBeHit(timeOffset))
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 12e3d2de51..22fa93a308 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Objects
         /// <summary>
         /// The tail note of the hold.
         /// </summary>
-        public readonly Note Tail = new TailNote();
+        public readonly Note Tail = new Note();
 
         /// <summary>
         /// The time between ticks of this hold.
@@ -94,25 +94,5 @@ namespace osu.Game.Rulesets.Mania.Objects
                 });
             }
         }
-
-        /// <summary>
-        /// The tail of the hold note.
-        /// </summary>
-        private class TailNote : Note
-        {
-            /// <summary>
-            /// Lenience of release hit windows. This is to make cases where the hold note release
-            /// is timed alongside presses of other hit objects less awkward.
-            /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
-            /// </summary>
-            private const double release_window_lenience = 1.5;
-
-            protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
-            {
-                base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
-
-                HitWindows *= release_window_lenience;
-            }
-        }
     }
 }
diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs
index 7610593d6a..3717209860 100644
--- a/osu.Game/Rulesets/Objects/HitWindows.cs
+++ b/osu.Game/Rulesets/Objects/HitWindows.cs
@@ -135,39 +135,5 @@ namespace osu.Game.Rulesets.Objects
         /// <param name="timeOffset">The time offset.</param>
         /// <returns>Whether the <see cref="HitObject"/> can be hit at any point in the future from this time offset.</returns>
         public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh);
-
-        /// <summary>
-        /// Multiplies all hit windows by a value.
-        /// </summary>
-        /// <param name="windows">The hit windows to multiply.</param>
-        /// <param name="value">The value to multiply each hit window by.</param>
-        public static HitWindows operator *(HitWindows windows, double value)
-        {
-            windows.Perfect *= value;
-            windows.Great *= value;
-            windows.Good *= value;
-            windows.Ok *= value;
-            windows.Meh *= value;
-            windows.Miss *= value;
-
-            return windows;
-        }
-
-        /// <summary>
-        /// Divides all hit windows by a value.
-        /// </summary>
-        /// <param name="windows">The hit windows to divide.</param>
-        /// <param name="value">The value to divide each hit window by.</param>
-        public static HitWindows operator /(HitWindows windows, double value)
-        {
-            windows.Perfect /= value;
-            windows.Great /= value;
-            windows.Good /= value;
-            windows.Ok /= value;
-            windows.Meh /= value;
-            windows.Miss /= value;
-
-            return windows;
-        }
     }
 }

From b9ed9769546fd05cb8c1413c480054762042a84f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 14:44:30 +0900
Subject: [PATCH 159/177] Fix taiko slider multiplier being applied twice

---
 osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index abb2193235..304fd290fc 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -98,12 +98,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
                 double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
 
                 // The velocity of the taiko hit object - calculated as the velocity of a drum roll
-                double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+                double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
                 // The duration of the taiko hit object
                 double taikoDuration = distance / taikoVelocity;
 
                 // The velocity of the osu! hit object - calculated as the velocity of a slider
-                double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
+                double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
                 // The duration of the osu! hit object
                 double osuDuration = distance / osuVelocity;
 

From 532d65f6e8aa5c8383413e3c4726d5200429c1aa Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 15:17:47 +0900
Subject: [PATCH 160/177] Re-enable basic taiko beatmap conversion tests

---
 osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 93f1b7faf7..11586e340b 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
         protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
 
         [NonParallelizable]
-        [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
-        [TestCase("slider-generating-drumroll", false)]
+        [TestCase("basic")]
+        [TestCase("slider-generating-drumroll")]
         public new void Test(string name)
         {
             base.Test(name);

From eba1d309b674eb42d16486ea603859a4e94174ed Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 16:58:22 +0900
Subject: [PATCH 161/177] Fix incorrect namespace of OsuPerformanceCalculator

---
 .../{Scoring => Difficulty}/OsuPerformanceCalculator.cs         | 2 +-
 osu.Game.Rulesets.Osu/OsuRuleset.cs                             | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)
 rename osu.Game.Rulesets.Osu/{Scoring => Difficulty}/OsuPerformanceCalculator.cs (99%)

diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
similarity index 99%
rename from osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
rename to osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 84a83d331b..eeb776fa6e 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Mods;
 using osu.Game.Rulesets.Osu.Objects;
 using osu.Game.Rulesets.Scoring;
 
-namespace osu.Game.Rulesets.Osu.Scoring
+namespace osu.Game.Rulesets.Osu.Difficulty
 {
     public class OsuPerformanceCalculator : PerformanceCalculator
     {
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index a2423ffbe5..c455bb2af6 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
 using osu.Game.Overlays.Settings;
 using osu.Framework.Input.Bindings;
 using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Osu.Scoring;
 using osu.Game.Rulesets.Osu.Edit;
 using osu.Game.Rulesets.Edit;
 using osu.Game.Rulesets.Osu.Replays;

From d20011ba58a849c0cca543b13149458d03f05f39 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 17 May 2018 17:56:25 +0900
Subject: [PATCH 162/177] Fix an endless feedback loop

---
 osu.Game/Screens/Select/MatchSongSelect.cs | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 4e252eac75..3ffac591f3 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -7,7 +7,12 @@ namespace osu.Game.Screens.Select
     {
         protected override bool OnSelectionFinalised()
         {
-            Exit();
+            Schedule(() =>
+            {
+                // needs to be scheduled else we enter an infinite feedback loop.
+                if (IsCurrentScreen) Exit();
+            });
+
             return true;
         }
     }

From 450d54eea9ccafce0df2ec8f1ad0c816be4fa6a1 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 17:12:28 +0900
Subject: [PATCH 163/177] Fix taiko difficulty calculator never considering
 mods

---
 .../Difficulty/TaikoDifficultyCalculator.cs                 | 6 ++++++
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs                     | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index acff0d286d..d33150d739 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using osu.Game.Beatmaps;
 using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
 using osu.Game.Rulesets.Taiko.Objects;
 
 namespace osu.Game.Rulesets.Taiko.Difficulty
@@ -35,6 +36,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
         {
         }
 
+        public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
+            : base(beatmap, mods)
+        {
+        }
+
         public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
         {
             // Fill our custom DifficultyHitObject class, that carries additional information
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 35dc17c0e2..04b513866a 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko
 
         public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
 
-        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
+        public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
 
         public override int? LegacyID => 1;
 

From 3091d3a01450a1185ba346d813f46c0de1658f18 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 17 May 2018 17:40:46 +0900
Subject: [PATCH 164/177] Implement the taiko performance calculator

---
 .../Difficulty/TaikoDifficultyCalculator.cs   |   5 +-
 .../Difficulty/TaikoPerformanceCalculator.cs  | 111 ++++++++++++++++++
 osu.Game.Rulesets.Taiko/TaikoRuleset.cs       |   3 +
 3 files changed, 115 insertions(+), 4 deletions(-)
 create mode 100644 osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs

diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index d33150d739..57e1e65064 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -57,10 +57,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
             double starRating = calculateDifficulty() * star_scaling_factor;
 
             if (categoryDifficulty != null)
-            {
-                categoryDifficulty.Add("Strain", starRating);
-                categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
-            }
+                categoryDifficulty["Strain"] = starRating;
 
             return starRating;
         }
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
new file mode 100644
index 0000000000..9c9cd1f0fb
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -0,0 +1,111 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty
+{
+    public class TaikoPerformanceCalculator : PerformanceCalculator
+    {
+        private readonly int beatmapMaxCombo;
+
+        private Mod[] mods;
+        private int countGreat;
+        private int countGood;
+        private int countMeh;
+        private int countMiss;
+
+        public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
+            : base(ruleset, beatmap, score)
+        {
+            beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
+        }
+
+        public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
+        {
+            mods = Score.Mods;
+            countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
+            countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
+            countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
+            countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+
+            // Don't count scores made with supposedly unranked mods
+            if (mods.Any(m => !m.Ranked))
+                return 0;
+
+            // Custom multipliers for NoFail and SpunOut.
+            double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
+
+            if (mods.Any(m => m is ModNoFail))
+                multiplier *= 0.90;
+
+            if (mods.Any(m => m is ModHidden))
+                multiplier *= 1.10;
+
+            double strainValue = computeStrainValue();
+            double accuracyValue = computeAccuracyValue();
+            double totalValue =
+                Math.Pow(
+                    Math.Pow(strainValue, 1.1) +
+                    Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
+                ) * multiplier;
+
+            if (categoryDifficulty != null)
+            {
+                categoryDifficulty["Strain"] = strainValue;
+                categoryDifficulty["Accuracy"] = accuracyValue;
+            }
+
+            return totalValue;
+        }
+
+        private double computeStrainValue()
+        {
+            double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0;
+
+            // Longer maps are worth more
+            double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
+            strainValue *= lengthBonus;
+
+            // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
+            strainValue *= Math.Pow(0.985, countMiss);
+
+            // Combo scaling
+            if (beatmapMaxCombo > 0)
+                strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
+
+            if (mods.Any(m => m is ModHidden))
+                strainValue *= 1.025;
+
+            if (mods.Any(m => m is ModFlashlight))
+                // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
+                strainValue *= 1.05 * lengthBonus;
+
+            // Scale the speed value with accuracy _slightly_
+            return strainValue * Score.Accuracy;
+        }
+
+        private double computeAccuracyValue()
+        {
+            double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
+            if (hitWindowGreat <= 0)
+                return 0;
+
+            // Lots of arbitrary values from testing.
+            // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
+            double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
+
+            // Bonus for many hitcircles - it's harder to keep good accuracy up for longer
+            return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
+        }
+
+        private int totalHits => countGreat + countGood + countMeh + countMiss;
+    }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 04b513866a..abaa8db597 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Replays.Types;
 using osu.Game.Rulesets.Taiko.Replays;
 using osu.Game.Beatmaps.Legacy;
 using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
 using osu.Game.Rulesets.Taiko.Beatmaps;
 using osu.Game.Rulesets.Taiko.Difficulty;
 
@@ -146,6 +147,8 @@ namespace osu.Game.Rulesets.Taiko
 
         public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
 
+        public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
+
         public override int? LegacyID => 1;
 
         public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();

From ebfbe58abb20c13a36ce18cf1b8ba91a9b413400 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Thu, 17 May 2018 06:19:55 -0300
Subject: [PATCH 165/177] Move Header breadcrumbs to a subclass.

---
 osu.Game/Screens/Multi/Header.cs | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs
index 03996f5309..db8898495f 100644
--- a/osu.Game/Screens/Multi/Header.cs
+++ b/osu.Game/Screens/Multi/Header.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Screens.Multi
         public const float HEIGHT = 121;
 
         private readonly OsuSpriteText screenTitle;
-        private readonly ScreenBreadcrumbControl breadcrumbs;
+        private readonly HeaderBreadcrumbControl breadcrumbs;
 
         public Header(Screen initialScreen)
         {
@@ -75,7 +75,7 @@ namespace osu.Game.Screens.Multi
                                 },
                             },
                         },
-                        breadcrumbs = new ScreenBreadcrumbControl(initialScreen)
+                        breadcrumbs = new HeaderBreadcrumbControl(initialScreen)
                         {
                             Anchor = Anchor.BottomLeft,
                             Origin = Anchor.BottomLeft,
@@ -85,8 +85,6 @@ namespace osu.Game.Screens.Multi
                 },
             };
 
-            breadcrumbs.OnLoadComplete = d => breadcrumbs.AccentColour = Color4.White;
-
             breadcrumbs.Current.ValueChanged += s => screenTitle.Text = s.ToString();
             breadcrumbs.Current.TriggerChange();
         }
@@ -97,5 +95,18 @@ namespace osu.Game.Screens.Multi
             screenTitle.Colour = colours.Yellow;
             breadcrumbs.StripColour = colours.Green;
         }
+
+        private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
+        {
+            public HeaderBreadcrumbControl(Screen initialScreen) : base(initialScreen)
+            {
+            }
+
+            protected override void LoadComplete()
+            {
+                base.LoadComplete();
+                AccentColour = Color4.White;
+            }
+        }
     }
 }

From 17d1759c37f53893dfdb9c1529206b0f0c2c5083 Mon Sep 17 00:00:00 2001
From: Roman Kapustin <TocoToucanMS@gmail.com>
Date: Fri, 18 May 2018 01:01:54 +0300
Subject: [PATCH 166/177] Get rid of multiple blank lines in a row

---
 osu.Game/Screens/Menu/ButtonSystem.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 8cf0d24f7d..997002327a 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -185,7 +185,6 @@ namespace osu.Game.Screens.Menu
             }
         }
 
-
         private void onPlay()
         {
             State = MenuState.Play;

From e2389ad7a4a4e02938bd6eb2157d36cd19e30741 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 18 May 2018 13:30:36 +0900
Subject: [PATCH 167/177] Allow using back button on PlayerLoader and Replay

---
 osu.Game/Screens/Play/Player.cs                      | 2 ++
 osu.Game/Screens/Play/PlayerLoader.cs                | 1 -
 osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs | 1 -
 3 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 4a46279d30..46919e25e1 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -45,6 +45,8 @@ namespace osu.Game.Screens.Play
         public bool AllowLeadIn { get; set; } = true;
         public bool AllowResults { get; set; } = true;
 
+        protected override bool AllowBackButton => false;
+
         private Bindable<bool> mouseWheelDisabled;
         private Bindable<double> userAudioOffset;
 
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 6eb156914e..56fbd7b6e7 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -27,7 +27,6 @@ namespace osu.Game.Screens.Play
 
         private bool showOverlays = true;
         public override bool ShowOverlaysOnEnter => showOverlays;
-        protected override bool AllowBackButton => false;
 
         private Task loadTask;
 
diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
index f29f5b328a..1ccc5e2fe8 100644
--- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
+++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs
@@ -16,7 +16,6 @@ namespace osu.Game.Screens.Play
         protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
 
         public override bool AllowBeatmapRulesetChange => false;
-        protected override bool AllowBackButton => false;
 
         protected const float BACKGROUND_FADE_DURATION = 800;
 

From d75fe4009afd0663ffcf6198014de748e22a9fef Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 18 May 2018 13:40:35 +0900
Subject: [PATCH 168/177] Add back action support to settings back button

---
 osu.Game/Overlays/MainSettings.cs | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs
index 09b5be6a85..99a86f19a1 100644
--- a/osu.Game/Overlays/MainSettings.cs
+++ b/osu.Game/Overlays/MainSettings.cs
@@ -6,9 +6,11 @@ using osu.Framework.Allocation;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
 using osu.Game.Graphics;
 using osu.Game.Graphics.Containers;
 using osu.Game.Graphics.Sprites;
+using osu.Game.Input.Bindings;
 using osu.Game.Overlays.Settings;
 using osu.Game.Overlays.Settings.Sections;
 using osu.Game.Screens.Ranking;
@@ -96,7 +98,7 @@ namespace osu.Game.Overlays
             });
         }
 
-        private class BackButton : OsuClickableContainer
+        private class BackButton : OsuClickableContainer, IKeyBindingHandler<GlobalAction>
         {
             private AspectContainer aspect;
 
@@ -146,6 +148,20 @@ namespace osu.Game.Overlays
                 aspect.ScaleTo(1, 1000, Easing.OutElastic);
                 return base.OnMouseUp(state, args);
             }
+
+            public bool OnPressed(GlobalAction action)
+            {
+                switch (action)
+                {
+                    case GlobalAction.Back:
+                        TriggerOnClick();
+                        return true;
+                }
+
+                return false;
+            }
+
+            public bool OnReleased(GlobalAction action) => false;
         }
     }
 }

From e6e37583045e6981b3c63517be44a1bb08338394 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 18 May 2018 18:11:52 +0900
Subject: [PATCH 169/177] Fix HR mod affecting original beatmap difficulty

Fixes #2575.
---
 osu.Game/Beatmaps/WorkingBeatmap.cs | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 9c389bbb8f..66a6206c16 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -107,8 +107,14 @@ namespace osu.Game.Beatmaps
             IBeatmap converted = converter.Convert();
 
             // Apply difficulty mods
-            foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
-                mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
+            if (Mods.Value.Any(m => m is IApplicableToDifficulty))
+            {
+                converted.BeatmapInfo = converted.BeatmapInfo.Clone();
+                converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone();
+
+                foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
+                    mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
+            }
 
             // Post-process
             rulesetInstance.CreateBeatmapProcessor(converted)?.PostProcess();

From 33baaf8243d04efc276e30a006118a78d68332df Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Fri, 18 May 2018 18:44:42 +0900
Subject: [PATCH 170/177] Update framework

---
 osu-framework | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/osu-framework b/osu-framework
index fac688633b..80e78fd45b 160000
--- a/osu-framework
+++ b/osu-framework
@@ -1 +1 @@
-Subproject commit fac688633b8fcf34ae5d0514c26b03e217161eb4
+Subproject commit 80e78fd45bb79ca4bc46ecc05deb6058f3879faa

From 4b2b2086df821b5889892abe9bae3775f55a2a5e Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 19 May 2018 01:26:39 -0300
Subject: [PATCH 171/177] Create drawable hierarchy for RoomInspector in load,
 remove display* methods.

---
 .../Screens/Multi/Components/RoomInspector.cs | 191 ++++++++----------
 1 file changed, 85 insertions(+), 106 deletions(-)

diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index 92910e8301..b103624dfb 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -28,14 +28,6 @@ namespace osu.Game.Screens.Multi.Components
         private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
         private const float transition_duration = 100;
 
-        private readonly Box statusStrip;
-        private readonly Container coverContainer;
-        private readonly FillFlowContainer topFlow, participantsFlow;
-        private readonly ModeTypeInfo modeTypeInfo;
-        private readonly OsuSpriteText participants, participantsSlash, maxParticipants, name, status, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
-        private readonly ParticipantInfo participantInfo;
-        private readonly ScrollContainer participantsScroll;
-
         private readonly Bindable<string> nameBind = new Bindable<string>();
         private readonly Bindable<User> hostBind = new Bindable<User>();
         private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
@@ -44,11 +36,10 @@ namespace osu.Game.Screens.Multi.Components
         private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
         private readonly Bindable<User[]> participantsBind = new Bindable<User[]>();
 
-        private OsuColour colours;
-        private LocalisationEngine localisation;
+        private FillFlowContainer topFlow;
+        private ScrollContainer participantsScroll;
 
         private Room room;
-
         public Room Room
         {
             get { return room; }
@@ -71,6 +62,17 @@ namespace osu.Game.Screens.Multi.Components
         {
             Width = 520;
             RelativeSizeAxes = Axes.Y;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(OsuColour colours, LocalisationEngine localisation)
+        {
+            Box statusStrip;
+            Container coverContainer;
+            FillFlowContainer participantsFlow;
+            ModeTypeInfo modeTypeInfo;
+            OsuSpriteText participants, participantsSlash, maxParticipants, name, status, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
+            ParticipantInfo participantInfo;
 
             Children = new Drawable[]
             {
@@ -229,6 +231,7 @@ namespace osu.Game.Screens.Multi.Components
                                                             Anchor = Anchor.BottomLeft,
                                                             Origin = Anchor.BottomLeft,
                                                             TextSize = 14,
+                                                            Colour = colours.Gray9,
                                                         },
                                                     },
                                                 },
@@ -269,27 +272,83 @@ namespace osu.Game.Screens.Multi.Components
                 },
             };
 
-            nameBind.ValueChanged += displayName;
-            hostBind.ValueChanged += displayUser;
-            typeBind.ValueChanged += displayGameType;
-            maxParticipantsBind.ValueChanged += displayMaxParticipants;
-            participantsBind.ValueChanged += displayParticipants;
-        }
+            nameBind.ValueChanged += n => name.Text = n;
+            hostBind.ValueChanged += h => participantInfo.Host = h;
+            typeBind.ValueChanged += t => modeTypeInfo.Type = t;
 
-        [BackgroundDependencyLoader]
-        private void load(OsuColour colours, LocalisationEngine localisation)
-        {
-            this.localisation = localisation;
-            this.colours = colours;
+            statusBind.ValueChanged += s =>
+            {
+                status.Text = s.Message;
 
-            beatmapAuthor.Colour = colours.Gray9;
+                foreach (Drawable d in new Drawable[] { statusStrip, status })
+                    d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
+            };
 
-            //binded here instead of ctor because dependencies are needed
-            statusBind.ValueChanged += displayStatus;
-            beatmapBind.ValueChanged += displayBeatmap;
+            beatmapBind.ValueChanged += b =>
+            {
+                modeTypeInfo.Beatmap = b;
 
+                if (b != null)
+                {
+                    coverContainer.FadeIn(transition_duration);
+
+                    LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            FillMode = FillMode.Fill,
+                            OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
+                        },
+                        coverContainer.Add);
+
+                    beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
+                    beatmapDash.Text = @" - ";
+                    beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist);
+                    beatmapAuthor.Text = $"mapped by {b.Metadata.Author}";
+                }
+                else
+                {
+                    coverContainer.FadeOut(transition_duration);
+
+                    beatmapTitle.Current = null;
+                    beatmapArtist.Current = null;
+
+                    beatmapTitle.Text = "Changing map";
+                    beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
+                }
+            };
+
+            maxParticipantsBind.ValueChanged += m =>
+            {
+                if (m == null)
+                {
+                    participantsSlash.FadeOut(transition_duration);
+                    maxParticipants.FadeOut(transition_duration);
+                }
+                else
+                {
+                    participantsSlash.FadeIn(transition_duration);
+                    maxParticipants.FadeIn(transition_duration);
+                    maxParticipants.Text = m.ToString();
+                }
+            };
+
+            participantsBind.ValueChanged += p =>
+            {
+                participants.Text = p.Length.ToString();
+                participantInfo.Participants = p;
+                participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
+            };
+
+            // trigger incase a room was set before we were loaded
+            nameBind.TriggerChange();
+            hostBind.TriggerChange();
             statusBind.TriggerChange();
+            typeBind.TriggerChange();
             beatmapBind.TriggerChange();
+            maxParticipantsBind.TriggerChange();
+            participantsBind.TriggerChange();
         }
 
         protected override void UpdateAfterChildren()
@@ -299,86 +358,6 @@ namespace osu.Game.Screens.Multi.Components
             participantsScroll.Height = DrawHeight - topFlow.DrawHeight;
         }
 
-        private void displayName(string value)
-        {
-            name.Text = value;
-        }
-
-        private void displayUser(User value)
-        {
-            participantInfo.Host = value;
-        }
-
-        private void displayStatus(RoomStatus value)
-        {
-            status.Text = value.Message;
-
-            foreach (Drawable d in new Drawable[] { statusStrip, status })
-                d.FadeColour(value.GetAppropriateColour(colours), transition_duration);
-        }
-
-        private void displayGameType(GameType value)
-        {
-            modeTypeInfo.Type = value;
-        }
-
-        private void displayBeatmap(BeatmapInfo value)
-        {
-            modeTypeInfo.Beatmap = value;
-
-            if (value != null)
-            {
-                coverContainer.FadeIn(transition_duration);
-
-                LoadComponentAsync(new BeatmapSetCover(value.BeatmapSet)
-                {
-                    RelativeSizeAxes = Axes.Both,
-                    Anchor = Anchor.Centre,
-                    Origin = Anchor.Centre,
-                    FillMode = FillMode.Fill,
-                    OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
-                },
-                coverContainer.Add);
-
-                beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title);
-                beatmapDash.Text = @" - ";
-                beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist);
-                beatmapAuthor.Text = $"mapped by {value.Metadata.Author}";
-            }
-            else
-            {
-                coverContainer.FadeOut(transition_duration);
-
-                beatmapTitle.Current = null;
-                beatmapArtist.Current = null;
-
-                beatmapTitle.Text = "Changing map";
-                beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty;
-            }
-        }
-
-        private void displayMaxParticipants(int? value)
-        {
-            if (value == null)
-            {
-                participantsSlash.FadeOut(transition_duration);
-                maxParticipants.FadeOut(transition_duration);
-            }
-            else
-            {
-                participantsSlash.FadeIn(transition_duration);
-                maxParticipants.FadeIn(transition_duration);
-                maxParticipants.Text = value.ToString();
-            }
-        }
-
-        private void displayParticipants(User[] value)
-        {
-            participants.Text = value.Length.ToString();
-            participantInfo.Participants = value;
-            participantsFlow.ChildrenEnumerable = value.Select(u => new UserTile(u));
-        }
-
         private class UserTile : Container, IHasTooltip
         {
             private readonly User user;

From ad878003f7ef6a83cb88d7e98a3d45f1d4f0aeb2 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 19 May 2018 02:23:09 -0300
Subject: [PATCH 172/177] Add null room support to RoomInspector.

---
 .../Visual/TestCaseRoomInspector.cs           |   8 +-
 .../Screens/Multi/Components/RoomInspector.cs | 116 ++++++++++++------
 2 files changed, 81 insertions(+), 43 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index cb1425ca69..48756c907b 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
         {
             base.LoadComplete();
 
-            var room = new Room
+            Room room = new Room
             {
                 Name = { Value = @"My Awesome Room" },
                 Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } },
@@ -71,9 +71,11 @@ namespace osu.Game.Tests.Visual
             {
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
-                Room = room,
             });
 
+            AddStep(@"set room", () => inspector.Room = room);
+            AddStep(@"null room", () => inspector.Room = null);
+            AddStep(@"set room", () => inspector.Room = room);
             AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above");
             AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
             AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
@@ -88,7 +90,7 @@ namespace osu.Game.Tests.Visual
 
             AddStep(@"change room", () =>
             {
-                var newRoom = new Room
+                Room newRoom = new Room
                 {
                     Name = { Value = @"My New, Better Than Ever Room" },
                     Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } },
diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index b103624dfb..b3a6d90e39 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -5,6 +5,7 @@ using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
 using osu.Framework.Graphics.Containers;
@@ -25,9 +26,9 @@ namespace osu.Game.Screens.Multi.Components
 {
     public class RoomInspector : Container
     {
-        private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
         private const float transition_duration = 100;
 
+        private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
         private readonly Bindable<string> nameBind = new Bindable<string>();
         private readonly Bindable<User> hostBind = new Bindable<User>();
         private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
@@ -36,8 +37,13 @@ namespace osu.Game.Screens.Multi.Components
         private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
         private readonly Bindable<User[]> participantsBind = new Bindable<User[]>();
 
-        private FillFlowContainer topFlow;
+        private OsuColour colours;
+        private Box statusStrip;
+        private Container coverContainer;
+        private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow;
+        private OsuSpriteText name, status;
         private ScrollContainer participantsScroll;
+        private ParticipantInfo participantInfo;
 
         private Room room;
         public Room Room
@@ -48,13 +54,26 @@ namespace osu.Game.Screens.Multi.Components
                 if (value == room) return;
                 room = value;
 
-                nameBind.BindTo(Room.Name);
-                hostBind.BindTo(Room.Host);
-                statusBind.BindTo(Room.Status);
-                typeBind.BindTo(Room.Type);
-                beatmapBind.BindTo(Room.Beatmap);
-                maxParticipantsBind.BindTo(Room.MaxParticipants);
-                participantsBind.BindTo(Room.Participants);
+                nameBind.UnbindBindings();
+                hostBind.UnbindBindings();
+                statusBind.UnbindBindings();
+                typeBind.UnbindBindings();
+                beatmapBind.UnbindBindings();
+                maxParticipantsBind.UnbindBindings();
+                participantsBind.UnbindBindings();
+
+                if (Room != null)
+                {
+                    nameBind.BindTo(Room.Name);
+                    hostBind.BindTo(Room.Host);
+                    statusBind.BindTo(Room.Status);
+                    typeBind.BindTo(Room.Type);
+                    beatmapBind.BindTo(Room.Beatmap);
+                    maxParticipantsBind.BindTo(Room.MaxParticipants);
+                    participantsBind.BindTo(Room.Participants);
+                }
+
+                updateState();
             }
         }
 
@@ -67,12 +86,10 @@ namespace osu.Game.Screens.Multi.Components
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
-            Box statusStrip;
-            Container coverContainer;
-            FillFlowContainer participantsFlow;
+            this.colours = colours;
+
             ModeTypeInfo modeTypeInfo;
-            OsuSpriteText participants, participantsSlash, maxParticipants, name, status, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
-            ParticipantInfo participantInfo;
+            OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor;
 
             Children = new Drawable[]
             {
@@ -122,7 +139,7 @@ namespace osu.Game.Screens.Multi.Components
                                     Padding = new MarginPadding(20),
                                     Children = new Drawable[]
                                     {
-                                        new FillFlowContainer
+                                        participantNumbersFlow = new FillFlowContainer
                                         {
                                             Anchor = Anchor.TopRight,
                                             Origin = Anchor.TopRight,
@@ -180,6 +197,7 @@ namespace osu.Game.Screens.Multi.Components
                                     RelativeSizeAxes = Axes.X,
                                     AutoSizeAxes = Axes.Y,
                                     Direction = FillDirection.Vertical,
+                                    LayoutDuration = transition_duration,
                                     Padding = contentPadding,
                                     Spacing = new Vector2(0f, 5f),
                                     Children = new Drawable[]
@@ -189,7 +207,7 @@ namespace osu.Game.Screens.Multi.Components
                                             TextSize = 14,
                                             Font = @"Exo2.0-Bold",
                                         },
-                                        new FillFlowContainer
+                                        infoPanelFlow = new FillFlowContainer
                                         {
                                             AutoSizeAxes = Axes.X,
                                             Height = 30,
@@ -275,14 +293,7 @@ namespace osu.Game.Screens.Multi.Components
             nameBind.ValueChanged += n => name.Text = n;
             hostBind.ValueChanged += h => participantInfo.Host = h;
             typeBind.ValueChanged += t => modeTypeInfo.Type = t;
-
-            statusBind.ValueChanged += s =>
-            {
-                status.Text = s.Message;
-
-                foreach (Drawable d in new Drawable[] { statusStrip, status })
-                    d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
-            };
+            statusBind.ValueChanged += displayStatus;
 
             beatmapBind.ValueChanged += b =>
             {
@@ -293,14 +304,13 @@ namespace osu.Game.Screens.Multi.Components
                     coverContainer.FadeIn(transition_duration);
 
                     LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet)
-                        {
-                            RelativeSizeAxes = Axes.Both,
-                            Anchor = Anchor.Centre,
-                            Origin = Anchor.Centre,
-                            FillMode = FillMode.Fill,
-                            OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
-                        },
-                        coverContainer.Add);
+                    {
+                        RelativeSizeAxes = Axes.Both,
+                        Anchor = Anchor.Centre,
+                        Origin = Anchor.Centre,
+                        FillMode = FillMode.Fill,
+                        OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out),
+                    }, coverContainer.Add);
 
                     beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title);
                     beatmapDash.Text = @" - ";
@@ -341,14 +351,7 @@ namespace osu.Game.Screens.Multi.Components
                 participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u));
             };
 
-            // trigger incase a room was set before we were loaded
-            nameBind.TriggerChange();
-            hostBind.TriggerChange();
-            statusBind.TriggerChange();
-            typeBind.TriggerChange();
-            beatmapBind.TriggerChange();
-            maxParticipantsBind.TriggerChange();
-            participantsBind.TriggerChange();
+            updateState();
         }
 
         protected override void UpdateAfterChildren()
@@ -358,6 +361,33 @@ namespace osu.Game.Screens.Multi.Components
             participantsScroll.Height = DrawHeight - topFlow.DrawHeight;
         }
 
+        private void displayStatus(RoomStatus s)
+        {
+            status.Text = s.Message;
+
+            foreach (Drawable d in new Drawable[] { statusStrip, status })
+                d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
+        }
+
+        private void updateState()
+        {
+            if (Room == null)
+            {
+                foreach (Drawable d in new Drawable[] { coverContainer, participantsFlow, participantNumbersFlow, infoPanelFlow, name, participantInfo })
+                    d.FadeOut(transition_duration);
+
+                displayStatus(new RoomStatusNoneSelected());
+            }
+            else
+            {
+                foreach (Drawable d in new Drawable[] { participantsFlow, participantNumbersFlow, infoPanelFlow, name, participantInfo })
+                    d.FadeIn(transition_duration);
+
+                statusBind.TriggerChange();
+                beatmapBind.TriggerChange();
+            }
+        }
+
         private class UserTile : Container, IHasTooltip
         {
             private readonly User user;
@@ -386,5 +416,11 @@ namespace osu.Game.Screens.Multi.Components
                 };
             }
         }
+
+        private class RoomStatusNoneSelected : RoomStatus
+        {
+            public override string Message => @"No Room Selected";
+            public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8;
+        }
     }
 }

From 136c57b824d477e3af4370681e987358ed35b5b6 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 19 May 2018 02:27:33 -0300
Subject: [PATCH 173/177] Don't set size in ctor.

---
 osu.Game.Tests/Visual/TestCaseRoomInspector.cs     | 2 ++
 osu.Game/Screens/Multi/Components/RoomInspector.cs | 2 --
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index 48756c907b..06b9c4a6f9 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -71,6 +71,8 @@ namespace osu.Game.Tests.Visual
             {
                 Anchor = Anchor.Centre,
                 Origin = Anchor.Centre,
+                RelativeSizeAxes = Axes.Both,
+                Width = 0.5f,
             });
 
             AddStep(@"set room", () => inspector.Room = room);
diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index b3a6d90e39..3de1611b77 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -79,8 +79,6 @@ namespace osu.Game.Screens.Multi.Components
 
         public RoomInspector()
         {
-            Width = 520;
-            RelativeSizeAxes = Axes.Y;
         }
 
         [BackgroundDependencyLoader]

From 9cd0ec366e2d003e1f6c1923f414010a7208a821 Mon Sep 17 00:00:00 2001
From: DrabWeb <sethunity@gmail.com>
Date: Sat, 19 May 2018 02:51:51 -0300
Subject: [PATCH 174/177] Cleanup.

---
 .../Screens/Multi/Components/RoomInspector.cs | 25 +++++++++++--------
 1 file changed, 14 insertions(+), 11 deletions(-)

diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index 3de1611b77..ea12b5dbde 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -5,7 +5,6 @@ using System.Linq;
 using osu.Framework.Allocation;
 using osu.Framework.Configuration;
 using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Extensions.IEnumerableExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Colour;
 using osu.Framework.Graphics.Containers;
@@ -77,10 +76,6 @@ namespace osu.Game.Screens.Multi.Components
             }
         }
 
-        public RoomInspector()
-        {
-        }
-
         [BackgroundDependencyLoader]
         private void load(OsuColour colours, LocalisationEngine localisation)
         {
@@ -363,23 +358,31 @@ namespace osu.Game.Screens.Multi.Components
         {
             status.Text = s.Message;
 
-            foreach (Drawable d in new Drawable[] { statusStrip, status })
-                d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
+            Color4 c = s.GetAppropriateColour(colours);
+            statusStrip.FadeColour(c, transition_duration);
+            status.FadeColour(c, transition_duration);
         }
 
         private void updateState()
         {
             if (Room == null)
             {
-                foreach (Drawable d in new Drawable[] { coverContainer, participantsFlow, participantNumbersFlow, infoPanelFlow, name, participantInfo })
-                    d.FadeOut(transition_duration);
+                coverContainer.FadeOut(transition_duration);
+                participantsFlow.FadeOut(transition_duration);
+                participantNumbersFlow.FadeOut(transition_duration);
+                infoPanelFlow.FadeOut(transition_duration);
+                name.FadeOut(transition_duration);
+                participantInfo.FadeOut(transition_duration);
 
                 displayStatus(new RoomStatusNoneSelected());
             }
             else
             {
-                foreach (Drawable d in new Drawable[] { participantsFlow, participantNumbersFlow, infoPanelFlow, name, participantInfo })
-                    d.FadeIn(transition_duration);
+                participantsFlow.FadeIn(transition_duration);
+                participantNumbersFlow.FadeIn(transition_duration);
+                infoPanelFlow.FadeIn(transition_duration);
+                name.FadeIn(transition_duration);
+                participantInfo.FadeIn(transition_duration);
 
                 statusBind.TriggerChange();
                 beatmapBind.TriggerChange();

From aaca7e92b483f847fb8605f203088e76909effb3 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 21 May 2018 03:56:59 +0900
Subject: [PATCH 175/177] Avoid excessive property lookups

---
 .../Screens/Multi/Components/RoomInspector.cs    | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs
index ea12b5dbde..3bd054b042 100644
--- a/osu.Game/Screens/Multi/Components/RoomInspector.cs
+++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs
@@ -61,15 +61,15 @@ namespace osu.Game.Screens.Multi.Components
                 maxParticipantsBind.UnbindBindings();
                 participantsBind.UnbindBindings();
 
-                if (Room != null)
+                if (room != null)
                 {
-                    nameBind.BindTo(Room.Name);
-                    hostBind.BindTo(Room.Host);
-                    statusBind.BindTo(Room.Status);
-                    typeBind.BindTo(Room.Type);
-                    beatmapBind.BindTo(Room.Beatmap);
-                    maxParticipantsBind.BindTo(Room.MaxParticipants);
-                    participantsBind.BindTo(Room.Participants);
+                    nameBind.BindTo(room.Name);
+                    hostBind.BindTo(room.Host);
+                    statusBind.BindTo(room.Status);
+                    typeBind.BindTo(room.Type);
+                    beatmapBind.BindTo(room.Beatmap);
+                    maxParticipantsBind.BindTo(room.MaxParticipants);
+                    participantsBind.BindTo(room.Participants);
                 }
 
                 updateState();

From 46c6c1d07e6edd0dc778d22a9ecf4e87c686a0dd Mon Sep 17 00:00:00 2001
From: Joehu <jmadamba07@hotmail.com>
Date: Sun, 20 May 2018 20:25:39 -0700
Subject: [PATCH 176/177] Allow drag clicking footer and filter on song select

---
 osu.Game/Screens/Select/FilterControl.cs | 2 --
 osu.Game/Screens/Select/Footer.cs        | 2 --
 2 files changed, 4 deletions(-)

diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index ee458a13a4..f9f3db3827 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -190,7 +190,5 @@ namespace osu.Game.Screens.Select
         protected override bool OnMouseMove(InputState state) => true;
 
         protected override bool OnClick(InputState state) => true;
-
-        protected override bool OnDragStart(InputState state) => true;
     }
 }
diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs
index 363249ab63..8f07e0a763 100644
--- a/osu.Game/Screens/Select/Footer.cs
+++ b/osu.Game/Screens/Select/Footer.cs
@@ -141,7 +141,5 @@ namespace osu.Game.Screens.Select
         protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
 
         protected override bool OnClick(InputState state) => true;
-
-        protected override bool OnDragStart(InputState state) => true;
     }
 }

From 42519e3723e5abcde81c0c04005dae38a657aeb8 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Mon, 21 May 2018 14:45:44 +0900
Subject: [PATCH 177/177] Rewrite code for clarity

This also uses the AvailableRulesets list rather than private IDs
---
 .../Overlays/Toolbar/ToolbarModeSelector.cs   | 25 +++++++++----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
index eeaa15d58a..3078c44844 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs
@@ -76,15 +76,13 @@ namespace osu.Game.Overlays.Toolbar
                 modeButtons.Add(new ToolbarModeButton
                 {
                     Ruleset = r,
-                    Action = delegate
-                    {
-                        ruleset.Value = r;
-                    }
+                    Action = delegate { ruleset.Value = r; }
                 });
             }
 
             ruleset.ValueChanged += rulesetChanged;
             ruleset.DisabledChanged += disabledChanged;
+
             if (game != null)
                 ruleset.BindTo(game.Ruleset);
             else
@@ -94,17 +92,18 @@ namespace osu.Game.Overlays.Toolbar
         protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
         {
             base.OnKeyDown(state, args);
-            if (!state.Keyboard.ControlPressed || args.Repeat || (int)args.Key < 109 ||  (int)args.Key > 118) {
-                return false;
+
+            if (state.Keyboard.ControlPressed && !args.Repeat && args.Key >= Key.Number1 && args.Key <= Key.Number9)
+            {
+                int requested = args.Key - Key.Number1;
+
+                RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault();
+                if (found != null)
+                    ruleset.Value = found;
+                return true;
             }
 
-            RulesetInfo targetRuleset = rulesets.GetRuleset(args.Key == Key.Number0 ? 9 : (int)args.Key - 110);
-            if (targetRuleset == null || targetRuleset == ruleset.Value) {
-                return false;
-            }
-
-            ruleset.Value = targetRuleset;
-            return true;
+            return false;
         }
 
         public override bool HandleKeyboardInput => !ruleset.Disabled && base.HandleKeyboardInput;