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 01/62] 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 02/62] 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 b5a55a0dcea31751a0fc7ceebc96f53bad87e426 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 19 Apr 2018 18:14:21 +0900
Subject: [PATCH 03/62] 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 04/62] 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 05/62] 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 06/62] 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 07/62] 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 08/62] 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 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 09/62] 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 f3aa9269ff3e9b2cdcde32c4c552794af0d41148 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 09:17:12 +0900
Subject: [PATCH 10/62] 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 11/62] 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 12/62] 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 13/62] 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 14/62] 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 15/62] 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 16/62] 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 17/62] 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 18/62] 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 19/62] 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 20/62] 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 21/62] 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 22/62] 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 23/62] 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 24/62] 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 25/62] 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 5489976c20cf5d7e15600c8d2f2a53e93f5c3af7 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 7 May 2018 10:51:17 +0900
Subject: [PATCH 26/62] 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 27/62] 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 28/62] 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 29/62] 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 30/62] 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 31/62] 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 32/62] 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 33/62] 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 34/62] 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 35/62] 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 36/62] 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 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 37/62] 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 38/62] 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 39/62] 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 40/62] 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 41/62] 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 42/62] 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 2bab08c4373a96360d48a8ed183ff79c1385b3d7 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Fri, 11 May 2018 14:07:17 +0900
Subject: [PATCH 43/62] 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 44/62] 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 45/62] 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 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 46/62] 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 47/62] 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 48/62] 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 49/62] 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 50/62] 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 51/62] 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 52/62] 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 53/62] 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 54/62] 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 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 55/62] 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 56/62] 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 57/62] 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 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 58/62] 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 59/62] 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 60/62] 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 a81921118dac58379808e0c55a0b281c02ffdf5b Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Mon, 14 May 2018 11:52:22 +0900
Subject: [PATCH 61/62] 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 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 62/62] 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)