From 31e6a4fa5942a081f730e1ba2db676a045c9ccc5 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Sat, 25 May 2019 15:09:31 +0900
Subject: [PATCH 01/12] Add optional skin foreign key to databased settings

---
 osu.Game/Configuration/DatabasedSetting.cs    |   2 +
 .../20190525060824_SkinSettings.Designer.cs   | 498 ++++++++++++++++++
 .../Migrations/20190525060824_SkinSettings.cs |  45 ++
 .../Migrations/OsuDbContextModelSnapshot.cs   |  11 +
 osu.Game/Skinning/SkinInfo.cs                 |   3 +
 osu.Game/Skinning/SkinStore.cs                |   6 +
 6 files changed, 565 insertions(+)
 create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs
 create mode 100644 osu.Game/Migrations/20190525060824_SkinSettings.cs

diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs
index d56ac49358..035fc73f4f 100644
--- a/osu.Game/Configuration/DatabasedSetting.cs
+++ b/osu.Game/Configuration/DatabasedSetting.cs
@@ -15,6 +15,8 @@ namespace osu.Game.Configuration
 
         public int? Variant { get; set; }
 
+        public int? SkinInfoID { get; set; }
+
         [Column("Key")]
         public int IntKey
         {
diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs
new file mode 100644
index 0000000000..348c42adb9
--- /dev/null
+++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs
@@ -0,0 +1,498 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+    [DbContext(typeof(OsuDbContext))]
+    [Migration("20190525060824_SkinSettings")]
+    partial class SkinSettings
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<float>("ApproachRate");
+
+                    b.Property<float>("CircleSize");
+
+                    b.Property<float>("DrainRate");
+
+                    b.Property<float>("OverallDifficulty");
+
+                    b.Property<double>("SliderMultiplier");
+
+                    b.Property<double>("SliderTickRate");
+
+                    b.HasKey("ID");
+
+                    b.ToTable("BeatmapDifficulty");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("AudioLeadIn");
+
+                    b.Property<int>("BaseDifficultyID");
+
+                    b.Property<int>("BeatDivisor");
+
+                    b.Property<int>("BeatmapSetInfoID");
+
+                    b.Property<bool>("Countdown");
+
+                    b.Property<double>("DistanceSpacing");
+
+                    b.Property<int>("GridSize");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<bool>("Hidden");
+
+                    b.Property<bool>("LetterboxInBreaks");
+
+                    b.Property<string>("MD5Hash");
+
+                    b.Property<int?>("MetadataID");
+
+                    b.Property<int?>("OnlineBeatmapID");
+
+                    b.Property<string>("Path");
+
+                    b.Property<int>("RulesetID");
+
+                    b.Property<bool>("SpecialStyle");
+
+                    b.Property<float>("StackLeniency");
+
+                    b.Property<double>("StarDifficulty");
+
+                    b.Property<int>("Status");
+
+                    b.Property<string>("StoredBookmarks");
+
+                    b.Property<double>("TimelineZoom");
+
+                    b.Property<string>("Version");
+
+                    b.Property<bool>("WidescreenStoryboard");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BaseDifficultyID");
+
+                    b.HasIndex("BeatmapSetInfoID");
+
+                    b.HasIndex("Hash");
+
+                    b.HasIndex("MD5Hash");
+
+                    b.HasIndex("MetadataID");
+
+                    b.HasIndex("OnlineBeatmapID")
+                        .IsUnique();
+
+                    b.HasIndex("RulesetID");
+
+                    b.ToTable("BeatmapInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Artist");
+
+                    b.Property<string>("ArtistUnicode");
+
+                    b.Property<string>("AudioFile");
+
+                    b.Property<string>("AuthorString")
+                        .HasColumnName("Author");
+
+                    b.Property<string>("BackgroundFile");
+
+                    b.Property<int>("PreviewTime");
+
+                    b.Property<string>("Source");
+
+                    b.Property<string>("Tags");
+
+                    b.Property<string>("Title");
+
+                    b.Property<string>("TitleUnicode");
+
+                    b.HasKey("ID");
+
+                    b.ToTable("BeatmapMetadata");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("BeatmapSetInfoID");
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BeatmapSetInfoID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.ToTable("BeatmapSetFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int?>("MetadataID");
+
+                    b.Property<int?>("OnlineBeatmapSetID");
+
+                    b.Property<bool>("Protected");
+
+                    b.Property<int>("Status");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DeletePending");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.HasIndex("MetadataID");
+
+                    b.HasIndex("OnlineBeatmapSetID")
+                        .IsUnique();
+
+                    b.ToTable("BeatmapSetInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Key")
+                        .HasColumnName("Key");
+
+                    b.Property<int?>("RulesetID");
+
+                    b.Property<int?>("SkinInfoID");
+
+                    b.Property<string>("StringValue")
+                        .HasColumnName("Value");
+
+                    b.Property<int?>("Variant");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("SkinInfoID");
+
+                    b.HasIndex("RulesetID", "Variant");
+
+                    b.ToTable("Settings");
+                });
+
+            modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int>("ReferenceCount");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.HasIndex("ReferenceCount");
+
+                    b.ToTable("FileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("IntAction")
+                        .HasColumnName("Action");
+
+                    b.Property<string>("KeysString")
+                        .HasColumnName("Keys");
+
+                    b.Property<int?>("RulesetID");
+
+                    b.Property<int?>("Variant");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("IntAction");
+
+                    b.HasIndex("RulesetID", "Variant");
+
+                    b.ToTable("KeyBinding");
+                });
+
+            modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+                {
+                    b.Property<int?>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<bool>("Available");
+
+                    b.Property<string>("InstantiationInfo");
+
+                    b.Property<string>("Name");
+
+                    b.Property<string>("ShortName");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Available");
+
+                    b.HasIndex("ShortName")
+                        .IsUnique();
+
+                    b.ToTable("RulesetInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.Property<int?>("ScoreInfoID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.HasIndex("ScoreInfoID");
+
+                    b.ToTable("ScoreFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<double>("Accuracy")
+                        .HasColumnType("DECIMAL(1,4)");
+
+                    b.Property<int>("BeatmapInfoID");
+
+                    b.Property<int>("Combo");
+
+                    b.Property<DateTimeOffset>("Date");
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int>("MaxCombo");
+
+                    b.Property<string>("ModsJson")
+                        .HasColumnName("Mods");
+
+                    b.Property<long?>("OnlineScoreID");
+
+                    b.Property<double?>("PP");
+
+                    b.Property<int>("Rank");
+
+                    b.Property<int>("RulesetID");
+
+                    b.Property<string>("StatisticsJson")
+                        .HasColumnName("Statistics");
+
+                    b.Property<long>("TotalScore");
+
+                    b.Property<long?>("UserID")
+                        .HasColumnName("UserID");
+
+                    b.Property<string>("UserString")
+                        .HasColumnName("User");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BeatmapInfoID");
+
+                    b.HasIndex("OnlineScoreID")
+                        .IsUnique();
+
+                    b.HasIndex("RulesetID");
+
+                    b.ToTable("ScoreInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.Property<int>("SkinInfoID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.HasIndex("SkinInfoID");
+
+                    b.ToTable("SkinFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Creator");
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<string>("Name");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DeletePending");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.ToTable("SkinInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+                        .WithMany()
+                        .HasForeignKey("BaseDifficultyID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+                        .WithMany("Beatmaps")
+                        .HasForeignKey("BeatmapSetInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+                        .WithMany("Beatmaps")
+                        .HasForeignKey("MetadataID");
+
+                    b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+                        .WithMany()
+                        .HasForeignKey("RulesetID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("BeatmapSetInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+                        .WithMany("BeatmapSets")
+                        .HasForeignKey("MetadataID");
+                });
+
+            modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+                {
+                    b.HasOne("osu.Game.Skinning.SkinInfo")
+                        .WithMany("Settings")
+                        .HasForeignKey("SkinInfoID");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Scoring.ScoreInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("ScoreInfoID");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+                        .WithMany("Scores")
+                        .HasForeignKey("BeatmapInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+                        .WithMany()
+                        .HasForeignKey("RulesetID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Skinning.SkinInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("SkinInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs
new file mode 100644
index 0000000000..8bd429ca5c
--- /dev/null
+++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs
@@ -0,0 +1,45 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+    public partial class SkinSettings : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<int>(
+                name: "SkinInfoID",
+                table: "Settings",
+                nullable: true);
+
+            migrationBuilder.CreateIndex(
+                name: "IX_Settings_SkinInfoID",
+                table: "Settings",
+                column: "SkinInfoID");
+
+            // unsupported by sqlite
+
+            // migrationBuilder.AddForeignKey(
+            //     name: "FK_Settings_SkinInfo_SkinInfoID",
+            //     table: "Settings",
+            //     column: "SkinInfoID",
+            //     principalTable: "SkinInfo",
+            //     principalColumn: "ID",
+            //     onDelete: ReferentialAction.Restrict);
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropForeignKey(
+                name: "FK_Settings_SkinInfo_SkinInfoID",
+                table: "Settings");
+
+            migrationBuilder.DropIndex(
+                name: "IX_Settings_SkinInfoID",
+                table: "Settings");
+
+            migrationBuilder.DropColumn(
+                name: "SkinInfoID",
+                table: "Settings");
+        }
+    }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index 8430e00e4f..d03c2358b5 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -203,6 +203,8 @@ namespace osu.Game.Migrations
 
                     b.Property<int?>("RulesetID");
 
+                    b.Property<int?>("SkinInfoID");
+
                     b.Property<string>("StringValue")
                         .HasColumnName("Value");
 
@@ -210,6 +212,8 @@ namespace osu.Game.Migrations
 
                     b.HasKey("ID");
 
+                    b.HasIndex("SkinInfoID");
+
                     b.HasIndex("RulesetID", "Variant");
 
                     b.ToTable("Settings");
@@ -442,6 +446,13 @@ namespace osu.Game.Migrations
                         .HasForeignKey("MetadataID");
                 });
 
+            modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+                {
+                    b.HasOne("osu.Game.Skinning.SkinInfo")
+                        .WithMany("Settings")
+                        .HasForeignKey("SkinInfoID");
+                });
+
             modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
                 {
                     b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs
index 07318b473a..187ea910a7 100644
--- a/osu.Game/Skinning/SkinInfo.cs
+++ b/osu.Game/Skinning/SkinInfo.cs
@@ -3,6 +3,7 @@
 
 using System;
 using System.Collections.Generic;
+using osu.Game.Configuration;
 using osu.Game.Database;
 
 namespace osu.Game.Skinning
@@ -19,6 +20,8 @@ namespace osu.Game.Skinning
 
         public List<SkinFileInfo> Files { get; set; }
 
+        public List<DatabasedSetting> Settings { get; set; }
+
         public bool DeletePending { get; set; }
 
         public string FullName => $"\"{Name}\" by {Creator}";
diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs
index 31cadb0a24..153eeda130 100644
--- a/osu.Game/Skinning/SkinStore.cs
+++ b/osu.Game/Skinning/SkinStore.cs
@@ -1,6 +1,8 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
+using System.Linq;
+using Microsoft.EntityFrameworkCore;
 using osu.Framework.Platform;
 using osu.Game.Database;
 
@@ -12,5 +14,9 @@ namespace osu.Game.Skinning
             : base(contextFactory, storage)
         {
         }
+
+        protected override IQueryable<SkinInfo> AddIncludesForDeletion(IQueryable<SkinInfo> query) =>
+            base.AddIncludesForDeletion(query)
+                .Include(s => s.Settings); // don't include FileInfo. these are handled by the FileStore itself.
     }
 }

From 7d2a75b3502702aa035a7200fbd30b5885534199 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Tue, 4 Jun 2019 18:37:26 +0900
Subject: [PATCH 02/12] Dim music volume when holding to confirm

---
 osu.Game/Overlays/HoldToConfirmOverlay.cs | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs
index fb38ddcbd1..fdc6f096bc 100644
--- a/osu.Game/Overlays/HoldToConfirmOverlay.cs
+++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs
@@ -2,6 +2,8 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Bindables;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Shapes;
 using osu.Game.Graphics.Containers;
@@ -17,6 +19,11 @@ namespace osu.Game.Overlays
     {
         private Box overlay;
 
+        private readonly BindableDouble audioVolume = new BindableDouble(1);
+
+        [Resolved]
+        private AudioManager audio { get; set; }
+
         [BackgroundDependencyLoader]
         private void load()
         {
@@ -33,7 +40,19 @@ namespace osu.Game.Overlays
                 }
             };
 
-            Progress.ValueChanged += p => overlay.Alpha = (float)p.NewValue;
+            Progress.ValueChanged += p =>
+            {
+                audioVolume.Value = 1 - p.NewValue;
+                overlay.Alpha = (float)p.NewValue;
+            };
+
+            audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioVolume);
+        }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume);
+            base.Dispose(isDisposing);
         }
     }
 }

From a6dc5606bc588a52e03b13e9e62916617d95ecc2 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Wed, 5 Jun 2019 18:17:43 +0900
Subject: [PATCH 03/12] Allow beatmapsets to be sorted by date added

---
 osu.Game/Beatmaps/BeatmapManager.cs           |   1 +
 osu.Game/Beatmaps/BeatmapSetInfo.cs           |   3 +
 ...AddDateAddedColumnToBeatmapSet.Designer.cs | 489 ++++++++++++++++++
 ...05091246_AddDateAddedColumnToBeatmapSet.cs |  24 +
 .../Migrations/OsuDbContextModelSnapshot.cs   |   2 +
 .../Select/Carousel/CarouselBeatmapSet.cs     |   3 +
 6 files changed, 522 insertions(+)
 create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs
 create mode 100644 osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs

diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 0200dd44ac..b6fe7f88fa 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -342,6 +342,7 @@ namespace osu.Game.Beatmaps
                 OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
                 Beatmaps = new List<BeatmapInfo>(),
                 Metadata = beatmap.Metadata,
+                DateAdded = DateTimeOffset.UtcNow
             };
         }
 
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index e111f77ba1..390236e053 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -1,6 +1,7 @@
 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
 // See the LICENCE file in the repository root for full licence text.
 
+using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.Linq;
@@ -20,6 +21,8 @@ namespace osu.Game.Beatmaps
             set => onlineBeatmapSetID = value > 0 ? value : null;
         }
 
+        public DateTimeOffset DateAdded { get; set; }
+
         public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
 
         public BeatmapMetadata Metadata { get; set; }
diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs
new file mode 100644
index 0000000000..9477369aa0
--- /dev/null
+++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs
@@ -0,0 +1,489 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+    [DbContext(typeof(OsuDbContext))]
+    [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")]
+    partial class AddDateAddedColumnToBeatmapSet
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<float>("ApproachRate");
+
+                    b.Property<float>("CircleSize");
+
+                    b.Property<float>("DrainRate");
+
+                    b.Property<float>("OverallDifficulty");
+
+                    b.Property<double>("SliderMultiplier");
+
+                    b.Property<double>("SliderTickRate");
+
+                    b.HasKey("ID");
+
+                    b.ToTable("BeatmapDifficulty");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("AudioLeadIn");
+
+                    b.Property<int>("BaseDifficultyID");
+
+                    b.Property<int>("BeatDivisor");
+
+                    b.Property<int>("BeatmapSetInfoID");
+
+                    b.Property<bool>("Countdown");
+
+                    b.Property<double>("DistanceSpacing");
+
+                    b.Property<int>("GridSize");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<bool>("Hidden");
+
+                    b.Property<bool>("LetterboxInBreaks");
+
+                    b.Property<string>("MD5Hash");
+
+                    b.Property<int?>("MetadataID");
+
+                    b.Property<int?>("OnlineBeatmapID");
+
+                    b.Property<string>("Path");
+
+                    b.Property<int>("RulesetID");
+
+                    b.Property<bool>("SpecialStyle");
+
+                    b.Property<float>("StackLeniency");
+
+                    b.Property<double>("StarDifficulty");
+
+                    b.Property<int>("Status");
+
+                    b.Property<string>("StoredBookmarks");
+
+                    b.Property<double>("TimelineZoom");
+
+                    b.Property<string>("Version");
+
+                    b.Property<bool>("WidescreenStoryboard");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BaseDifficultyID");
+
+                    b.HasIndex("BeatmapSetInfoID");
+
+                    b.HasIndex("Hash");
+
+                    b.HasIndex("MD5Hash");
+
+                    b.HasIndex("MetadataID");
+
+                    b.HasIndex("OnlineBeatmapID")
+                        .IsUnique();
+
+                    b.HasIndex("RulesetID");
+
+                    b.ToTable("BeatmapInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Artist");
+
+                    b.Property<string>("ArtistUnicode");
+
+                    b.Property<string>("AudioFile");
+
+                    b.Property<string>("AuthorString")
+                        .HasColumnName("Author");
+
+                    b.Property<string>("BackgroundFile");
+
+                    b.Property<int>("PreviewTime");
+
+                    b.Property<string>("Source");
+
+                    b.Property<string>("Tags");
+
+                    b.Property<string>("Title");
+
+                    b.Property<string>("TitleUnicode");
+
+                    b.HasKey("ID");
+
+                    b.ToTable("BeatmapMetadata");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("BeatmapSetInfoID");
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BeatmapSetInfoID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.ToTable("BeatmapSetFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<DateTimeOffset>("DateAdded");
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int?>("MetadataID");
+
+                    b.Property<int?>("OnlineBeatmapSetID");
+
+                    b.Property<bool>("Protected");
+
+                    b.Property<int>("Status");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DeletePending");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.HasIndex("MetadataID");
+
+                    b.HasIndex("OnlineBeatmapSetID")
+                        .IsUnique();
+
+                    b.ToTable("BeatmapSetInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Key")
+                        .HasColumnName("Key");
+
+                    b.Property<int?>("RulesetID");
+
+                    b.Property<string>("StringValue")
+                        .HasColumnName("Value");
+
+                    b.Property<int?>("Variant");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("RulesetID", "Variant");
+
+                    b.ToTable("Settings");
+                });
+
+            modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int>("ReferenceCount");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.HasIndex("ReferenceCount");
+
+                    b.ToTable("FileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("IntAction")
+                        .HasColumnName("Action");
+
+                    b.Property<string>("KeysString")
+                        .HasColumnName("Keys");
+
+                    b.Property<int?>("RulesetID");
+
+                    b.Property<int?>("Variant");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("IntAction");
+
+                    b.HasIndex("RulesetID", "Variant");
+
+                    b.ToTable("KeyBinding");
+                });
+
+            modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+                {
+                    b.Property<int?>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<bool>("Available");
+
+                    b.Property<string>("InstantiationInfo");
+
+                    b.Property<string>("Name");
+
+                    b.Property<string>("ShortName");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("Available");
+
+                    b.HasIndex("ShortName")
+                        .IsUnique();
+
+                    b.ToTable("RulesetInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.Property<int?>("ScoreInfoID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.HasIndex("ScoreInfoID");
+
+                    b.ToTable("ScoreFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<double>("Accuracy")
+                        .HasColumnType("DECIMAL(1,4)");
+
+                    b.Property<int>("BeatmapInfoID");
+
+                    b.Property<int>("Combo");
+
+                    b.Property<DateTimeOffset>("Date");
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<int>("MaxCombo");
+
+                    b.Property<string>("ModsJson")
+                        .HasColumnName("Mods");
+
+                    b.Property<long?>("OnlineScoreID");
+
+                    b.Property<double?>("PP");
+
+                    b.Property<int>("Rank");
+
+                    b.Property<int>("RulesetID");
+
+                    b.Property<string>("StatisticsJson")
+                        .HasColumnName("Statistics");
+
+                    b.Property<long>("TotalScore");
+
+                    b.Property<long?>("UserID")
+                        .HasColumnName("UserID");
+
+                    b.Property<string>("UserString")
+                        .HasColumnName("User");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("BeatmapInfoID");
+
+                    b.HasIndex("OnlineScoreID")
+                        .IsUnique();
+
+                    b.HasIndex("RulesetID");
+
+                    b.ToTable("ScoreInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<int>("FileInfoID");
+
+                    b.Property<string>("Filename")
+                        .IsRequired();
+
+                    b.Property<int>("SkinInfoID");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("FileInfoID");
+
+                    b.HasIndex("SkinInfoID");
+
+                    b.ToTable("SkinFileInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+                {
+                    b.Property<int>("ID")
+                        .ValueGeneratedOnAdd();
+
+                    b.Property<string>("Creator");
+
+                    b.Property<bool>("DeletePending");
+
+                    b.Property<string>("Hash");
+
+                    b.Property<string>("Name");
+
+                    b.HasKey("ID");
+
+                    b.HasIndex("DeletePending");
+
+                    b.HasIndex("Hash")
+                        .IsUnique();
+
+                    b.ToTable("SkinInfo");
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+                        .WithMany()
+                        .HasForeignKey("BaseDifficultyID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+                        .WithMany("Beatmaps")
+                        .HasForeignKey("BeatmapSetInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+                        .WithMany("Beatmaps")
+                        .HasForeignKey("MetadataID");
+
+                    b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+                        .WithMany()
+                        .HasForeignKey("RulesetID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("BeatmapSetInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+                        .WithMany("BeatmapSets")
+                        .HasForeignKey("MetadataID");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Scoring.ScoreInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("ScoreInfoID");
+                });
+
+            modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+                {
+                    b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+                        .WithMany("Scores")
+                        .HasForeignKey("BeatmapInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+                        .WithMany()
+                        .HasForeignKey("RulesetID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+
+            modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+                {
+                    b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+                        .WithMany()
+                        .HasForeignKey("FileInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+
+                    b.HasOne("osu.Game.Skinning.SkinInfo")
+                        .WithMany("Files")
+                        .HasForeignKey("SkinInfoID")
+                        .OnDelete(DeleteBehavior.Cascade);
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs
new file mode 100644
index 0000000000..55dc18b6a3
--- /dev/null
+++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs
@@ -0,0 +1,24 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+    public partial class AddDateAddedColumnToBeatmapSet : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.AddColumn<DateTimeOffset>(
+                name: "DateAdded",
+                table: "BeatmapSetInfo",
+                nullable: false,
+                defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropColumn(
+                name: "DateAdded",
+                table: "BeatmapSetInfo");
+        }
+    }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index f942d357e8..a94b6df33a 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -166,6 +166,8 @@ namespace osu.Game.Migrations
                     b.Property<int>("ID")
                         .ValueGeneratedOnAdd();
 
+                    b.Property<DateTimeOffset>("DateAdded");
+
                     b.Property<bool>("DeletePending");
 
                     b.Property<string>("Hash");
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
index 5c334b126c..f1951e27ab 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
@@ -45,6 +45,9 @@ namespace osu.Game.Screens.Select.Carousel
                 case SortMode.Author:
                     return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase);
 
+                case SortMode.DateAdded:
+                    return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded);
+
                 case SortMode.Difficulty:
                     return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty);
             }

From 02283380c4b6ee648a741d695a35fa4cc5d6acf4 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 6 Jun 2019 13:33:30 +0900
Subject: [PATCH 04/12] Use manual migration

---
 .../Migrations/20190525060824_SkinSettings.cs | 41 +++++++++++--------
 1 file changed, 25 insertions(+), 16 deletions(-)

diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs
index 8bd429ca5c..99237419b7 100644
--- a/osu.Game/Migrations/20190525060824_SkinSettings.cs
+++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs
@@ -6,25 +6,34 @@ namespace osu.Game.Migrations
     {
         protected override void Up(MigrationBuilder migrationBuilder)
         {
-            migrationBuilder.AddColumn<int>(
-                name: "SkinInfoID",
-                table: "Settings",
-                nullable: true);
+            migrationBuilder.Sql(@"create table Settings_dg_tmp
+            (
+	            ID INTEGER not null
+		            constraint PK_Settings
+			            primary key autoincrement,
+	            Key TEXT not null,
+	            RulesetID INTEGER,
+	            Value TEXT,
+	            Variant INTEGER,
+	            SkinInfoID int
+		            constraint Settings_SkinInfo_ID_fk
+			            references SkinInfo
+				            on delete restrict
+            );
 
-            migrationBuilder.CreateIndex(
-                name: "IX_Settings_SkinInfoID",
-                table: "Settings",
-                column: "SkinInfoID");
+            insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings;
 
-            // unsupported by sqlite
+            drop table Settings;
 
-            // migrationBuilder.AddForeignKey(
-            //     name: "FK_Settings_SkinInfo_SkinInfoID",
-            //     table: "Settings",
-            //     column: "SkinInfoID",
-            //     principalTable: "SkinInfo",
-            //     principalColumn: "ID",
-            //     onDelete: ReferentialAction.Restrict);
+            alter table Settings_dg_tmp rename to Settings;
+
+            create index IX_Settings_RulesetID_Variant
+	            on Settings (RulesetID, Variant);
+
+            create index Settings_SkinInfoID_index
+	            on Settings (SkinInfoID);
+
+            ");
         }
 
         protected override void Down(MigrationBuilder migrationBuilder)

From ae438213a52d2a51586e7cd16e131cffa46bd052 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 6 Jun 2019 16:32:43 +0900
Subject: [PATCH 05/12] Remove secondary buffered container from slider body

---
 .../Objects/Drawables/Pieces/SliderBody.cs    | 28 ++-----------------
 1 file changed, 2 insertions(+), 26 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 25e1aebd18..33b3667c4f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -2,13 +2,10 @@
 // See the LICENCE file in the repository root for full licence text.
 
 using System.Collections.Generic;
-using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
 using osu.Framework.Graphics.Lines;
-using osu.Framework.Graphics.Primitives;
 using osuTK;
 using osuTK.Graphics;
-using osuTK.Graphics.ES30;
 
 namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
 {
@@ -19,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
         private readonly SliderPath path;
         protected Path Path => path;
 
-        private readonly BufferedContainer container;
-
         public float PathRadius
         {
             get => path.PathRadius;
@@ -44,8 +39,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
                     return;
 
                 path.AccentColour = value;
-
-                container.ForceRedraw();
             }
         }
 
@@ -61,8 +54,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
                     return;
 
                 path.BorderColour = value;
-
-                container.ForceRedraw();
             }
         }
 
@@ -78,23 +69,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
                     return;
 
                 path.BorderSize = value;
-
-                container.ForceRedraw();
             }
         }
 
-        public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
-
         protected SliderBody()
         {
-            InternalChild = container = new BufferedContainer
-            {
-                RelativeSizeAxes = Axes.Both,
-                CacheDrawnFrameBuffer = true,
-                Child = path = new SliderPath { Blending = BlendingMode.None }
-            };
-
-            container.Attach(RenderbufferInternalFormat.DepthComponent16);
+            InternalChild = path = new SliderPath();
         }
 
         public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
@@ -103,11 +83,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
         /// Sets the vertices of the path which should be drawn by this <see cref="SliderBody"/>.
         /// </summary>
         /// <param name="vertices">The vertices</param>
-        protected void SetVertices(IReadOnlyList<Vector2> vertices)
-        {
-            path.Vertices = vertices;
-            container.ForceRedraw();
-        }
+        protected void SetVertices(IReadOnlyList<Vector2> vertices) => path.Vertices = vertices;
 
         private class SliderPath : SmoothPath
         {

From c7d0fcd42ad4b171b0f7b5e1c273fb1bcc08607a Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 6 Jun 2019 16:33:14 +0900
Subject: [PATCH 06/12] Update drawnodes

---
 osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 3 ++-
 osu.Game/Graphics/Backgrounds/Triangles.cs     | 4 ++--
 osu.Game/Rulesets/Mods/ModFlashlight.cs        | 2 +-
 osu.Game/Screens/Menu/LogoVisualisation.cs     | 4 ++--
 4 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 1b8fa0de01..1bc22da8ac 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -210,7 +210,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
                         Vector2 pos = parts[i].Position;
                         float localTime = parts[i].Time;
 
-                        texture.DrawQuad(
+                        DrawQuad(
+                            texture,
                             new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
                             DrawColourInfo.Colour,
                             null,
diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs
index e2c7693700..29113e0e2f 100644
--- a/osu.Game/Graphics/Backgrounds/Triangles.cs
+++ b/osu.Game/Graphics/Backgrounds/Triangles.cs
@@ -214,7 +214,6 @@ namespace osu.Game.Graphics.Backgrounds
                 base.Draw(vertexAction);
 
                 shader.Bind();
-                texture.TextureGL.Bind();
 
                 Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy;
 
@@ -231,7 +230,8 @@ namespace osu.Game.Graphics.Backgrounds
                     ColourInfo colourInfo = DrawColourInfo.Colour;
                     colourInfo.ApplyChild(particle.Colour);
 
-                    texture.DrawTriangle(
+                    DrawTriangle(
+                        texture,
                         triangle,
                         colourInfo,
                         null,
diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs
index e174a25df3..405d21c711 100644
--- a/osu.Game/Rulesets/Mods/ModFlashlight.cs
+++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Mods
                     shader.GetUniform<Vector2>("flashlightSize").UpdateValue(ref flashlightSize);
                     shader.GetUniform<float>("flashlightDim").UpdateValue(ref flashlightDim);
 
-                    Texture.WhitePixel.DrawQuad(screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction);
+                    DrawQuad(Texture.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction);
 
                     shader.Unbind();
                 }
diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs
index 2925689d20..c6de5857c2 100644
--- a/osu.Game/Screens/Menu/LogoVisualisation.cs
+++ b/osu.Game/Screens/Menu/LogoVisualisation.cs
@@ -189,7 +189,6 @@ namespace osu.Game.Screens.Menu
                 base.Draw(vertexAction);
 
                 shader.Bind();
-                texture.TextureGL.Bind();
 
                 Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy;
 
@@ -224,7 +223,8 @@ namespace osu.Game.Screens.Menu
                                 Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix)
                             );
 
-                            texture.DrawQuad(
+                            DrawQuad(
+                                texture,
                                 rectangle,
                                 colourInfo,
                                 null,

From 4d035afcc6f93d808c28f4ebec36b26be2f1aee5 Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 6 Jun 2019 16:49:42 +0900
Subject: [PATCH 07/12] Add setting to bypass front-to-back

---
 osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
index b671d0e0fd..f063898a9f 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
@@ -31,6 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
                     LabelText = "Bypass caching (slow)",
                     Bindable = config.GetBindable<bool>(DebugSetting.BypassCaching)
                 },
+                new SettingsCheckbox
+                {
+                    LabelText = "Bypass front-to-back render pass",
+                    Bindable = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
+                }
             };
         }
     }

From ac9a3e54a60bf1e97977fb9a9fad2f6c4e5be28b Mon Sep 17 00:00:00 2001
From: David Zhao <nyquill@ppy.sh>
Date: Thu, 6 Jun 2019 18:07:19 +0900
Subject: [PATCH 08/12] Fix cursor issue with stopped gameplay clock

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

diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 1b8fa0de01..341975c167 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -54,7 +54,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
 
             for (int i = 0; i < max_sprites; i++)
             {
-                parts[i].InvalidationID = 0;
+                // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is ran on the draw node
+                parts[i].InvalidationID = 1;
                 parts[i].WasUpdated = true;
             }
         }

From 2a90af1d4e785e2d5cdf37c93243ed027257dbcf Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 6 Jun 2019 19:15:51 +0900
Subject: [PATCH 09/12] Update readme with direct download links

---
 README.md | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md
index abddb1faa1..91ea34e999 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@ This project is still heavily under development, but is in a state where users a
 
 We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
 
+Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog).
+
 ## Requirements
 
 - A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
@@ -20,17 +22,24 @@ We are accepting bug reports (please report with as much detail as possible). Fe
 
 ### Releases
 
-If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled.
+![](https://puu.sh/DCmvA/f6a74f5fbb.png)
 
-- Windows (x64) users should download and run `install.exe`.
-- macOS users (10.12 "Sierra" and higher) should download and run `osu.app.zip`.
-- iOS users can join the [TestFlight beta program](https://t.co/xQJmHkfC18).
+If you are not interested in developing the game, you can consume our [binary releases](https://github.com/ppy/osu/releases).
+
+**Latest build:***
+
+| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe)  | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) |
+| ------------- | ------------- |
+
+- **Linux** users are recommended to self-compile until we have official deployment in place.
+- **iOS** users can join the [TestFlight beta program](https://t.co/xQJmHkfC18) (note that due to high demand this is reulgarly full).
+- **Android** users can self-compile, and expect a public beta soon.
 
 If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
 
 ### Downloading the source code
 
-Clone the repository **including submodules**:
+Clone the repository:
 
 ```shell
 git clone https://github.com/ppy/osu
@@ -45,7 +54,7 @@ git pull
 
 ### Building
 
-Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided below.
+Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
 
 > Visual Studio Code users must run the `Restore` task before any build attempt.
 

From 6bf6e221491f95b1bb0b44e87cb22e0530ff948f Mon Sep 17 00:00:00 2001
From: smoogipoo <smoogipoo@smgi.me>
Date: Thu, 6 Jun 2019 20:33:03 +0900
Subject: [PATCH 10/12] Update framework

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

diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f84bb64fbf..55fa20188c 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -15,7 +15,7 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
     <PackageReference Include="ppy.osu.Game.Resources" Version="2019.518.0" />
-    <PackageReference Include="ppy.osu.Framework" Version="2019.604.1" />
+    <PackageReference Include="ppy.osu.Framework" Version="2019.606.1" />
     <PackageReference Include="SharpCompress" Version="0.23.0" />
     <PackageReference Include="NUnit" Version="3.12.0" />
     <PackageReference Include="SharpRaven" Version="2.4.0" />

From 8c1a62536cb15a559befbff2383259b687b39454 Mon Sep 17 00:00:00 2001
From: Dean Herbert <pe@ppy.sh>
Date: Thu, 6 Jun 2019 21:13:01 +0900
Subject: [PATCH 11/12] Update framework

---
 osu.iOS.props | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/osu.iOS.props b/osu.iOS.props
index fc047aa5f0..68f21df8ba 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -105,8 +105,8 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
     <PackageReference Include="ppy.osu.Game.Resources" Version="2019.128.0" />
-    <PackageReference Include="ppy.osu.Framework" Version="2019.523.0" />
-    <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.523.0" />
+    <PackageReference Include="ppy.osu.Framework" Version="2019.606.1" />
+    <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.606.1" />
     <PackageReference Include="SharpCompress" Version="0.22.0" />
     <PackageReference Include="NUnit" Version="3.11.0" />
     <PackageReference Include="SharpRaven" Version="2.4.0" />

From e2118299e93ac4e657c1994d09a7617ca262bbe5 Mon Sep 17 00:00:00 2001
From: David Zhao <nyquill@ppy.sh>
Date: Fri, 7 Jun 2019 10:36:36 +0900
Subject: [PATCH 12/12] update comment

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

diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 341975c167..888c77442f 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -54,7 +54,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
 
             for (int i = 0; i < max_sprites; i++)
             {
-                // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is ran on the draw node
+                // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node
+                // This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms
                 parts[i].InvalidationID = 1;
                 parts[i].WasUpdated = true;
             }