From 8e685a98d47a5f094c8ce0555508552b6caae5a9 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 26 Jul 2017 18:55:37 +0200 Subject: [PATCH 001/344] add RanksSection --- .../Tests/TestCaseUserRanks.cs | 100 +++++++++ .../osu.Desktop.VisualTests.csproj | 1 + .../Profile/Sections/Ranks/DrawablePlay.cs | 211 ++++++++++++++++++ .../Overlays/Profile/Sections/Ranks/Play.cs | 21 ++ .../Overlays/Profile/Sections/RanksSection.cs | 68 ++++++ osu.Game/osu.Game.csproj | 2 + 6 files changed, 403 insertions(+) create mode 100644 osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Ranks/Play.cs diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs new file mode 100644 index 0000000000..651c39ede4 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -0,0 +1,100 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Desktop.VisualTests.Tests +{ + public class TestCaseUserRanks : TestCase + { + public override string Description => "showing your latest achievements"; + + public TestCaseUserRanks() + { + RanksSection ranks; + + Add(new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + ranks = new RanksSection(), + } + }); + + AddStep("Add First Place", () => ranks.FirstPlacePlays = new[] + { + new Play + { + Rank = ScoreRank.A, + PerformancePoints = 666, + Accuracy = 0.735, + Date = DateTimeOffset.UtcNow, + Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime() }, + Beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "FREEDOM DiVE", + Artist = "xi" + }, + Version = "FOUR DIMENSIONS", + OnlineBeatmapID = 129891, + } + } + }); + + AddStep("Add Best Performances", () => + { + List plays = new List(); + Mod[] availableMods = { new OsuModHidden(), new OsuModFlashlight(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModPerfect() }; + List selectedMods = new List(availableMods); + for (int i = 0; i <= availableMods.Length; i++) + { + plays.Add(new Play + { + Rank = (ScoreRank) Enum.GetValues(typeof(ScoreRank)).GetValue(Enum.GetValues(typeof(ScoreRank)).Length - 1 - i), + PerformancePoints = (int)(Math.Pow(0.50, i) * 800), + Accuracy = Math.Pow(0.99, i), + Date = DateTimeOffset.UtcNow.AddDays(-Math.Pow(i, 2)), + Mods = selectedMods.ToList(), + Beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Highscore", + Artist = "Panda Eyes & Teminite" + }, + Version = "Game Over", + OnlineBeatmapID = 736215, + } + }); + if(i < availableMods.Length) + selectedMods.Remove(availableMods[i]); + } + ranks.BestPlays = plays; + }); + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 4974f0c0d1..00bcaca1e8 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -217,6 +217,7 @@ + diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs new file mode 100644 index 0000000000..66588462f3 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs @@ -0,0 +1,211 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Leaderboards; +using System.Linq; +using OpenTK.Graphics; +using System.Diagnostics; +using osu.Framework.Input; +using osu.Framework.Localisation; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class DrawablePlay : Container + { + private readonly FillFlowContainer stats; + private readonly FillFlowContainer metadata; + private readonly ModContainer modContainer; + private readonly Play play; + private readonly double weight; + + public DrawablePlay(Play play, double weight = -1) + { + this.play = play; + this.weight = weight; + + Children = new Drawable[] + { + new DrawableRank(play.Rank) + { + RelativeSizeAxes = Axes.Y, + Width = 60, + FillMode = FillMode.Fit, + }, + stats = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Vertical, + }, + metadata = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 70 }, + Direction = FillDirection.Vertical, + Child = new OsuSpriteText + { + Text = play.Date.LocalDateTime.ToShortDateString(), + TextSize = 11, + Colour = OsuColour.Gray(0xAA), + Depth = -1, + }, + }, + modContainer = new ModContainer + { + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 60, + Margin = new MarginPadding{ Right = 140 } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, LocalisationEngine locale) + { + stats.Add(new OsuSpriteText { + Text = play.PerformancePoints + "pp", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + TextSize = 18, + Font = "Exo2.0-BoldItalic", + }); + if(weight != -1) + { + stats.Add(new OsuSpriteText + { + Text = $"weighted: {(int)(play.PerformancePoints * weight)}pp ({weight.ToString("0%")})", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + }); + } + stats.Add(new OsuSpriteText { + Text = "accuracy: " + play.Accuracy.ToString("0.00%"), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colour.GrayA, + TextSize = 11, + Font = "Exo2.0-RegularItalic", + }); + + metadata.Add(new LinkContainer + { + AutoSizeAxes = Axes.Both, + Url = $"https://osu.ppy.sh/beatmaps/{play.Beatmap.OnlineBeatmapID}", + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = locale.GetUnicodePreference($"{play.Beatmap.Metadata.TitleUnicode ?? play.Beatmap.Metadata.Title} [{play.Beatmap.Version}] ", $"{play.Beatmap.Metadata.Title ?? play.Beatmap.Metadata.TitleUnicode} [{play.Beatmap.Version}] "), + TextSize = 15, + Font = "Exo2.0-SemiBoldItalic", + }, + new OsuSpriteText + { + Current = locale.GetUnicodePreference(play.Beatmap.Metadata.ArtistUnicode, play.Beatmap.Metadata.Artist), + TextSize = 12, + Padding = new MarginPadding { Top = 3 }, + Font = "Exo2.0-RegularItalic", + }, + }, + }, + }); + + foreach (Mod mod in play.Mods) + modContainer.Add(new ModIcon(mod.Icon, colour.Yellow)); + } + + private class ModContainer : FlowContainer + { + protected override IEnumerable ComputeLayoutPositions() + { + int count = FlowingChildren.Count(); + for (int i = 0; i < count; i++) + yield return new Vector2(DrawWidth * i * (count == 1 ? 0 : 1f / (count - 1)), 0); + } + } + + private class ModIcon : Container + { + public ModIcon(FontAwesome icon, Color4 colour) + { + AutoSizeAxes = Axes.Both; + + Children = new[] + { + new TextAwesome + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Icon = FontAwesome.fa_osu_mod_bg, + Colour = colour, + Shadow = true, + TextSize = 30, + UseFullGlyphHeight = false, + }, + new TextAwesome + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Icon = icon, + Colour = OsuColour.Gray(84), + TextSize = 18, + Position = new Vector2(0f, 2f), + UseFullGlyphHeight = false, + }, + }; + } + } + + private class LinkContainer : OsuClickableContainer + { + public string Url; + + private Color4 hoverColour; + + public LinkContainer() + { + Action = () => Process.Start(Url); + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs b/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs new file mode 100644 index 0000000000..a9cbbcbef4 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Database; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using System; +using System.Collections.Generic; + +namespace osu.Game.Overlays.Profile.Sections.Ranks +{ + public class Play + { + public ScoreRank Rank; + public BeatmapInfo Beatmap; + public DateTimeOffset Date; + public IEnumerable Mods; + public int PerformancePoints; + public double Accuracy; + } +} diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 5ea135fcac..f19de5356c 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,6 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Profile.Sections.Ranks; +using System; +using System.Collections.Generic; + namespace osu.Game.Overlays.Profile.Sections { public class RanksSection : ProfileSection @@ -8,5 +15,66 @@ namespace osu.Game.Overlays.Profile.Sections public override string Title => "Ranks"; public override string Identifier => "top_ranks"; + + private readonly FillFlowContainer best, firstPlace; + + public RanksSection() + { + Children = new Drawable[] + { + new OsuSpriteText { + TextSize = 15, + Text = "Best Performance", + Font = "Exo2.0-RegularItalic", + }, + best = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }, + new OsuSpriteText { + TextSize = 15, + Text = "First Place Ranks", + Font = "Exo2.0-RegularItalic", + }, + firstPlace = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }, + }; + } + + public IEnumerable BestPlays + { + set + { + int i = 0; + foreach (Play play in value) + { + best.Add(new DrawablePlay(play, Math.Pow(0.95, i)) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + i++; + } + } + } + + public IEnumerable FirstPlacePlays + { + set + { + foreach (Play play in value) + firstPlace.Add(new DrawablePlay(play) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + } + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6031304b26..b63ad6a271 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -98,6 +98,8 @@ + + From 7b8997cfc2ea6bd9f11d72c8dc49c84d9519d680 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 26 Jul 2017 19:42:34 +0200 Subject: [PATCH 002/344] CI stuff --- osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs | 2 -- osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs | 3 ++- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs index 651c39ede4..d3d0ae4577 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -15,8 +15,6 @@ using osu.Game.Rulesets.Scoring; using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Desktop.VisualTests.Tests { diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs index 66588462f3..5b3bbff3ca 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs @@ -16,6 +16,7 @@ using OpenTK.Graphics; using System.Diagnostics; using osu.Framework.Input; using osu.Framework.Localisation; +using System.Globalization; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"weighted: {(int)(play.PerformancePoints * weight)}pp ({weight.ToString("0%")})", + Text = $"weighted: {(int)(play.PerformancePoints * weight)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, From 0fc36065f4be114dac08559d7ee265d4dcda6118 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 00:31:52 +0200 Subject: [PATCH 003/344] replace `Play` with `Score` --- .../Tests/TestCaseUserRanks.cs | 14 +++++----- .../Profile/Sections/Ranks/DrawablePlay.cs | 27 ++++++++++--------- .../Overlays/Profile/Sections/Ranks/Play.cs | 21 --------------- .../Overlays/Profile/Sections/RanksSection.cs | 19 ++++++------- osu.Game/Rulesets/Scoring/Score.cs | 2 ++ osu.Game/osu.Game.csproj | 1 - 6 files changed, 32 insertions(+), 52 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/Ranks/Play.cs diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs index d3d0ae4577..9eacde0b97 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -41,12 +41,11 @@ namespace osu.Desktop.VisualTests.Tests } }); - AddStep("Add First Place", () => ranks.FirstPlacePlays = new[] + AddStep("Add First Place", () => ranks.FirstPlaceScores = new[] { - new Play + new Score { Rank = ScoreRank.A, - PerformancePoints = 666, Accuracy = 0.735, Date = DateTimeOffset.UtcNow, Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime() }, @@ -65,18 +64,17 @@ namespace osu.Desktop.VisualTests.Tests AddStep("Add Best Performances", () => { - List plays = new List(); + List scores = new List(); Mod[] availableMods = { new OsuModHidden(), new OsuModFlashlight(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModPerfect() }; List selectedMods = new List(availableMods); for (int i = 0; i <= availableMods.Length; i++) { - plays.Add(new Play + scores.Add(new Score { Rank = (ScoreRank) Enum.GetValues(typeof(ScoreRank)).GetValue(Enum.GetValues(typeof(ScoreRank)).Length - 1 - i), - PerformancePoints = (int)(Math.Pow(0.50, i) * 800), Accuracy = Math.Pow(0.99, i), Date = DateTimeOffset.UtcNow.AddDays(-Math.Pow(i, 2)), - Mods = selectedMods.ToList(), + Mods = selectedMods.ToArray(), Beatmap = new BeatmapInfo { Metadata = new BeatmapMetadata @@ -91,7 +89,7 @@ namespace osu.Desktop.VisualTests.Tests if(i < availableMods.Length) selectedMods.Remove(availableMods[i]); } - ranks.BestPlays = plays; + ranks.BestScores = scores; }); } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs index 5b3bbff3ca..f74b6792ad 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs @@ -17,25 +17,26 @@ using System.Diagnostics; using osu.Framework.Input; using osu.Framework.Localisation; using System.Globalization; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class DrawablePlay : Container + public class DrawableScore : Container { private readonly FillFlowContainer stats; private readonly FillFlowContainer metadata; private readonly ModContainer modContainer; - private readonly Play play; + private readonly Score score; private readonly double weight; - public DrawablePlay(Play play, double weight = -1) + public DrawableScore(Score score, double weight = -1) { - this.play = play; + this.score = score; this.weight = weight; Children = new Drawable[] { - new DrawableRank(play.Rank) + new DrawableRank(score.Rank) { RelativeSizeAxes = Axes.Y, Width = 60, @@ -59,7 +60,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Direction = FillDirection.Vertical, Child = new OsuSpriteText { - Text = play.Date.LocalDateTime.ToShortDateString(), + Text = score.Date.LocalDateTime.ToShortDateString(), TextSize = 11, Colour = OsuColour.Gray(0xAA), Depth = -1, @@ -80,7 +81,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private void load(OsuColour colour, LocalisationEngine locale) { stats.Add(new OsuSpriteText { - Text = play.PerformancePoints + "pp", + Text = score.PP + "pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 18, @@ -90,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"weighted: {(int)(play.PerformancePoints * weight)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", + Text = $"weighted: {(int)(score.PP * weight)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, @@ -99,7 +100,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }); } stats.Add(new OsuSpriteText { - Text = "accuracy: " + play.Accuracy.ToString("0.00%"), + Text = "accuracy: " + score.Accuracy.ToString("0.00%"), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, @@ -110,7 +111,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks metadata.Add(new LinkContainer { AutoSizeAxes = Axes.Both, - Url = $"https://osu.ppy.sh/beatmaps/{play.Beatmap.OnlineBeatmapID}", + Url = $"https://osu.ppy.sh/beatmaps/{score.Beatmap.OnlineBeatmapID}", Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -118,13 +119,13 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Current = locale.GetUnicodePreference($"{play.Beatmap.Metadata.TitleUnicode ?? play.Beatmap.Metadata.Title} [{play.Beatmap.Version}] ", $"{play.Beatmap.Metadata.Title ?? play.Beatmap.Metadata.TitleUnicode} [{play.Beatmap.Version}] "), + Current = locale.GetUnicodePreference($"{score.Beatmap.Metadata.TitleUnicode ?? score.Beatmap.Metadata.Title} [{score.Beatmap.Version}] ", $"{score.Beatmap.Metadata.Title ?? score.Beatmap.Metadata.TitleUnicode} [{score.Beatmap.Version}] "), TextSize = 15, Font = "Exo2.0-SemiBoldItalic", }, new OsuSpriteText { - Current = locale.GetUnicodePreference(play.Beatmap.Metadata.ArtistUnicode, play.Beatmap.Metadata.Artist), + Current = locale.GetUnicodePreference(score.Beatmap.Metadata.ArtistUnicode, score.Beatmap.Metadata.Artist), TextSize = 12, Padding = new MarginPadding { Top = 3 }, Font = "Exo2.0-RegularItalic", @@ -133,7 +134,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }, }); - foreach (Mod mod in play.Mods) + foreach (Mod mod in score.Mods) modContainer.Add(new ModIcon(mod.Icon, colour.Yellow)); } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs b/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs deleted file mode 100644 index a9cbbcbef4..0000000000 --- a/osu.Game/Overlays/Profile/Sections/Ranks/Play.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Database; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using System; -using System.Collections.Generic; - -namespace osu.Game.Overlays.Profile.Sections.Ranks -{ - public class Play - { - public ScoreRank Rank; - public BeatmapInfo Beatmap; - public DateTimeOffset Date; - public IEnumerable Mods; - public int PerformancePoints; - public double Accuracy; - } -} diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index f19de5356c..846f0e0e47 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Scoring; using System; using System.Collections.Generic; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Profile.Sections public override string Identifier => "top_ranks"; - private readonly FillFlowContainer best, firstPlace; + private readonly FillFlowContainer best, firstPlace; public RanksSection() { @@ -27,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections Text = "Best Performance", Font = "Exo2.0-RegularItalic", }, - best = new FillFlowContainer + best = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -38,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections Text = "First Place Ranks", Font = "Exo2.0-RegularItalic", }, - firstPlace = new FillFlowContainer + firstPlace = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -47,14 +48,14 @@ namespace osu.Game.Overlays.Profile.Sections }; } - public IEnumerable BestPlays + public IEnumerable BestScores { set { int i = 0; - foreach (Play play in value) + foreach (Score score in value) { - best.Add(new DrawablePlay(play, Math.Pow(0.95, i)) + best.Add(new DrawableScore(score, Math.Pow(0.95, i)) { RelativeSizeAxes = Axes.X, Height = 60, @@ -64,12 +65,12 @@ namespace osu.Game.Overlays.Profile.Sections } } - public IEnumerable FirstPlacePlays + public IEnumerable FirstPlaceScores { set { - foreach (Play play in value) - firstPlace.Add(new DrawablePlay(play) + foreach (Score score in value) + firstPlace.Add(new DrawableScore(score) { RelativeSizeAxes = Axes.X, Height = 60, diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 2cca0f54af..d77baaa8f3 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Scoring public double Health { get; set; } = 1; + public double PP { get; set; } + [JsonProperty(@"max_combo")] public int MaxCombo { get; set; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b63ad6a271..6888ecc220 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -99,7 +99,6 @@ - From 88f206cfe40bd9055299ce51e0eabff27d69c58c Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 00:34:16 +0200 Subject: [PATCH 004/344] rename file --- .../Sections/Ranks/{DrawablePlay.cs => DrawableScore.cs} | 0 osu.Game/osu.Game.csproj | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename osu.Game/Overlays/Profile/Sections/Ranks/{DrawablePlay.cs => DrawableScore.cs} (100%) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs similarity index 100% rename from osu.Game/Overlays/Profile/Sections/Ranks/DrawablePlay.cs rename to osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6888ecc220..859b52596f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -98,7 +98,7 @@ - + From 33ce8737cc726cbdb58eb822250ae99664e08440 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 00:47:23 +0200 Subject: [PATCH 005/344] fix merge --- osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs index 9eacde0b97..1ae6281928 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; -using osu.Game.Database; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Ranks; @@ -47,6 +47,7 @@ namespace osu.Desktop.VisualTests.Tests { Rank = ScoreRank.A, Accuracy = 0.735, + PP = 666, Date = DateTimeOffset.UtcNow, Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime() }, Beatmap = new BeatmapInfo @@ -73,6 +74,7 @@ namespace osu.Desktop.VisualTests.Tests { Rank = (ScoreRank) Enum.GetValues(typeof(ScoreRank)).GetValue(Enum.GetValues(typeof(ScoreRank)).Length - 1 - i), Accuracy = Math.Pow(0.99, i), + PP = Math.Pow(0.5, i) * 800, Date = DateTimeOffset.UtcNow.AddDays(-Math.Pow(i, 2)), Mods = selectedMods.ToArray(), Beatmap = new BeatmapInfo From c02165c82072c4f1d68a8988224dd62c2cade688 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 00:52:32 +0200 Subject: [PATCH 006/344] remove unused usings --- osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs index 1ae6281928..54e6b660f5 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -8,13 +8,11 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; -using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using System; using System.Collections.Generic; -using System.Linq; namespace osu.Desktop.VisualTests.Tests { From cca49d6ed5936dea16370167d42674a9692f6a23 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 15:06:46 +0200 Subject: [PATCH 007/344] some renaming, a show more button and a placeholder if no scores exist --- .../Tests/TestCaseUserRanks.cs | 4 +- .../Overlays/Profile/Sections/RanksSection.cs | 164 +++++++++++++++--- 2 files changed, 143 insertions(+), 25 deletions(-) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs index 54e6b660f5..607a6b9a91 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs @@ -39,7 +39,7 @@ namespace osu.Desktop.VisualTests.Tests } }); - AddStep("Add First Place", () => ranks.FirstPlaceScores = new[] + AddStep("Add First Place", () => ranks.ScoresFirst = new[] { new Score { @@ -89,7 +89,7 @@ namespace osu.Desktop.VisualTests.Tests if(i < availableMods.Length) selectedMods.Remove(availableMods[i]); } - ranks.BestScores = scores; + ranks.ScoresBest = scores.ToArray(); }); } } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 846f0e0e47..b0f5e2080a 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,13 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK.Graphics; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Scoring; using System; -using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections { @@ -17,64 +21,178 @@ namespace osu.Game.Overlays.Profile.Sections public override string Identifier => "top_ranks"; - private readonly FillFlowContainer best, firstPlace; + private readonly ScoreFlowContainer best, first; + private readonly OsuSpriteText bestMissing, firstMissing; public RanksSection() { Children = new Drawable[] { - new OsuSpriteText { + new OsuSpriteText + { TextSize = 15, Text = "Best Performance", Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, }, - best = new FillFlowContainer + best = new ScoreFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, }, - new OsuSpriteText { + bestMissing = new OsuSpriteText + { + TextSize = 14, + Text = "No awesome performance records yet. :(", + }, + new OsuSpriteText + { TextSize = 15, Text = "First Place Ranks", Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 20, Bottom = 10 }, }, - firstPlace = new FillFlowContainer + first = new ScoreFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, + }, + firstMissing = new OsuSpriteText + { + TextSize = 14, + Text = "No awesome performance records yet. :(", }, }; } - public IEnumerable BestScores + public Score[] ScoresBest { set { - int i = 0; - foreach (Score score in value) + best.Clear(); + if (value.Length == 0) { - best.Add(new DrawableScore(score, Math.Pow(0.95, i)) - { - RelativeSizeAxes = Axes.X, - Height = 60, - }); - i++; + bestMissing.Show(); } + else + { + bestMissing.Hide(); + int i = 0; + foreach (Score score in value) + { + best.Add(new DrawableScore(score, Math.Pow(0.95, i)) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + i++; + } + } + best.ShowMore(); } } - public IEnumerable FirstPlaceScores + public Score[] ScoresFirst { set { - foreach (Score score in value) - firstPlace.Add(new DrawableScore(score) + first.Clear(); + if (value.Length == 0) + { + firstMissing.Show(); + } + else + { + firstMissing.Hide(); + foreach (Score score in value) + first.Add(new DrawableScore(score) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + } + first.ShowMore(); + } + } + + private class ScoreFlowContainer : Container + { + private readonly FillFlowContainer scores; + private readonly OsuClickableContainer showMoreText; + + protected override Container Content => scores; + + private int shownScores; + + public ScoreFlowContainer() + { + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 60, - }); + scores = new FillFlowContainer() + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }, + showMoreText = new ShowMoreContainer + { + Action = ShowMore, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Child = new OsuSpriteText + { + TextSize = 14, + Text = "show more", + } + } + }, + }; + } + + public override void Clear(bool disposeChildren) + { + base.Clear(disposeChildren); + shownScores = 0; + showMoreText.Show(); + } + + public void ShowMore() + { + shownScores = Math.Min(Children.Count, shownScores + 5); + int i = 0; + foreach(DrawableScore score in Children) + score.FadeTo(i++ < shownScores ? 1 : 0); + showMoreText.FadeTo(shownScores == Children.Count ? 0 : 1); + } + + private class ShowMoreContainer : OsuClickableContainer + { + private Color4 hoverColour; + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } } } } From 02a22e3f774eb8e9400da1ff28ecf13ae7c1d202 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sat, 29 Jul 2017 15:16:31 +0200 Subject: [PATCH 008/344] remove empty argument list --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index b0f5e2080a..a99aceb893 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Profile.Sections Direction = FillDirection.Vertical, Children = new Drawable[] { - scores = new FillFlowContainer() + scores = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From 1c2329f111bf750d98baab189f58b0c3e76cde88 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Tue, 8 Aug 2017 23:11:46 +0200 Subject: [PATCH 009/344] generalize the hover code --- .../Graphics/Containers/OsuHoverContainer.cs | 32 ++++++++++++++++ osu.Game/Overlays/Profile/ProfileHeader.cs | 38 +++++++++---------- osu.Game/osu.Game.csproj | 1 + 3 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 osu.Game/Graphics/Containers/OsuHoverContainer.cs diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs new file mode 100644 index 0000000000..b4ce162086 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -0,0 +1,32 @@ + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; + +namespace osu.Game.Graphics.Containers +{ + + public class OsuHoverContainer : OsuClickableContainer + { + private Color4 hoverColour; + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 93044315cc..891487cfa7 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using System.Diagnostics; using System.Globalization; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile { @@ -509,34 +510,29 @@ namespace osu.Game.Overlays.Profile public class LinkText : OsuSpriteText { - public override bool HandleInput => Url != null; + private readonly OsuHoverContainer content; - public string Url; + public override bool HandleInput => content.Action != null; - private Color4 hoverColour; + protected override Container Content => content ?? (Container)this; - protected override bool OnHover(InputState state) + protected override IEnumerable FlowingChildren => Children; + + public string Url { - this.FadeColour(hoverColour, 500, Easing.OutQuint); - return base.OnHover(state); + set + { + if(value != null) + content.Action = () => Process.Start(value); + } } - protected override void OnHoverLost(InputState state) + public LinkText() { - this.FadeColour(Color4.White, 500, Easing.OutQuint); - base.OnHoverLost(state); - } - - protected override bool OnClick(InputState state) - { - Process.Start(Url); - return true; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.Yellow; + AddInternal(content = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + }); } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8312a1a319..8cbaee3072 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -92,6 +92,7 @@ + From 9f005488f7dca56b135340419ba892862a542066 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 01:43:52 +0200 Subject: [PATCH 010/344] make it work again after merge --- .../Visual}/TestCaseUserRanks.cs | 0 osu.Desktop.Tests/osu.Desktop.Tests.csproj | 1 + osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj | 1 - osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 8 ++------ 4 files changed, 3 insertions(+), 7 deletions(-) rename {osu.Desktop.VisualTests/Tests => osu.Desktop.Tests/Visual}/TestCaseUserRanks.cs (100%) diff --git a/osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs similarity index 100% rename from osu.Desktop.VisualTests/Tests/TestCaseUserRanks.cs rename to osu.Desktop.Tests/Visual/TestCaseUserRanks.cs diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index aa862498d1..9210b5eb3a 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -109,6 +109,7 @@ + diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 64fe3d4b95..8bba59207f 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -186,7 +186,6 @@ - diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index f74b6792ad..9279bd0156 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -156,25 +156,21 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Children = new[] { - new TextAwesome + new SpriteIcon { Origin = Anchor.Centre, Anchor = Anchor.Centre, Icon = FontAwesome.fa_osu_mod_bg, Colour = colour, Shadow = true, - TextSize = 30, - UseFullGlyphHeight = false, }, - new TextAwesome + new SpriteIcon { Origin = Anchor.Centre, Anchor = Anchor.Centre, Icon = icon, Colour = OsuColour.Gray(84), - TextSize = 18, Position = new Vector2(0f, 2f), - UseFullGlyphHeight = false, }, }; } From 274ebbd1f7f1ff197c8212f7ca6655fa33f7befe Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 18:45:37 +0200 Subject: [PATCH 011/344] remove duplicated code and "simplify" ShowMore logic --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 2 +- .../Profile/Sections/Ranks/DrawableScore.cs | 71 ++++--------------- .../Overlays/Profile/Sections/RanksSection.cs | 44 ++---------- 3 files changed, 19 insertions(+), 98 deletions(-) diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs index 607a6b9a91..d8e3449b1e 100644 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs @@ -47,7 +47,7 @@ namespace osu.Desktop.VisualTests.Tests Accuracy = 0.735, PP = 666, Date = DateTimeOffset.UtcNow, - Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime() }, + Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime(), new OsuModEasy() }, Beatmap = new BeatmapInfo { Metadata = new BeatmapMetadata diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 9279bd0156..9318798ec3 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -12,12 +12,11 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Leaderboards; using System.Linq; -using OpenTK.Graphics; using System.Diagnostics; -using osu.Framework.Input; using osu.Framework.Localisation; using System.Globalization; using osu.Game.Rulesets.Scoring; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -72,7 +71,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Width = 60, - Margin = new MarginPadding{ Right = 140 } + Margin = new MarginPadding{ Right = 150 } } }; } @@ -108,10 +107,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Font = "Exo2.0-RegularItalic", }); - metadata.Add(new LinkContainer + metadata.Add(new OsuHoverContainer { AutoSizeAxes = Axes.Both, - Url = $"https://osu.ppy.sh/beatmaps/{score.Beatmap.OnlineBeatmapID}", + Action = () => Process.Start($"https://osu.ppy.sh/beatmaps/{score.Beatmap.OnlineBeatmapID}"), Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -135,7 +134,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }); foreach (Mod mod in score.Mods) - modContainer.Add(new ModIcon(mod.Icon, colour.Yellow)); + modContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.5f), + }); } private class ModContainer : FlowContainer @@ -148,62 +151,14 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - private class ModIcon : Container + private class ModIcon : Rulesets.UI.ModIcon, IHasTooltip { - public ModIcon(FontAwesome icon, Color4 colour) + public ModIcon(Mod mod) : base(mod) { - AutoSizeAxes = Axes.Both; - - Children = new[] - { - new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Icon = FontAwesome.fa_osu_mod_bg, - Colour = colour, - Shadow = true, - }, - new SpriteIcon - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Icon = icon, - Colour = OsuColour.Gray(84), - Position = new Vector2(0f, 2f), - }, - }; - } - } - - private class LinkContainer : OsuClickableContainer - { - public string Url; - - private Color4 hoverColour; - - public LinkContainer() - { - Action = () => Process.Start(Url); + TooltipText = mod.Name; } - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 500, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(Color4.White, 500, Easing.OutQuint); - base.OnHoverLost(state); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.Yellow; - } + public string TooltipText { get; } } } } diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index a99aceb893..e9cc0bc00c 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,17 +1,14 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Scoring; using System; +using System.Linq; namespace osu.Game.Overlays.Profile.Sections { @@ -84,6 +81,7 @@ namespace osu.Game.Overlays.Profile.Sections { RelativeSizeAxes = Axes.X, Height = 60, + Alpha = 0, }); i++; } @@ -109,6 +107,7 @@ namespace osu.Game.Overlays.Profile.Sections { RelativeSizeAxes = Axes.X, Height = 60, + Alpha = 0, }); } first.ShowMore(); @@ -122,8 +121,6 @@ namespace osu.Game.Overlays.Profile.Sections protected override Container Content => scores; - private int shownScores; - public ScoreFlowContainer() { InternalChild = new FillFlowContainer @@ -139,7 +136,7 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, }, - showMoreText = new ShowMoreContainer + showMoreText = new OsuHoverContainer { Action = ShowMore, AutoSizeAxes = Axes.Both, @@ -159,41 +156,10 @@ namespace osu.Game.Overlays.Profile.Sections public override void Clear(bool disposeChildren) { base.Clear(disposeChildren); - shownScores = 0; showMoreText.Show(); } - public void ShowMore() - { - shownScores = Math.Min(Children.Count, shownScores + 5); - int i = 0; - foreach(DrawableScore score in Children) - score.FadeTo(i++ < shownScores ? 1 : 0); - showMoreText.FadeTo(shownScores == Children.Count ? 0 : 1); - } - - private class ShowMoreContainer : OsuClickableContainer - { - private Color4 hoverColour; - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 500, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(Color4.White, 500, Easing.OutQuint); - base.OnHoverLost(state); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoverColour = colours.Yellow; - } - } + public void ShowMore() => showMoreText.Alpha = Children.Where(d => !d.IsPresent).Where((d, i) => (d.Alpha = (i < 5 ? 1 : 0)) == 0).Any() ? 1 : 0; } } } From 8631c469fcc69eb4886caf69574c45495632e2af Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 18:50:44 +0200 Subject: [PATCH 012/344] add license header --- osu.Game/Graphics/Containers/OsuHoverContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index b4ce162086..efac292c76 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,4 +1,6 @@ - +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; From c2c9095b0235c2284ad91925f80a709a21972014 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 18:57:55 +0200 Subject: [PATCH 013/344] fix CI issues --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 1 - osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs index d8e3449b1e..a561559fdc 100644 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Scoring; using System; using System.Collections.Generic; -namespace osu.Desktop.VisualTests.Tests +namespace osu.Desktop.Tests.Visual { public class TestCaseUserRanks : TestCase { diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f3e8dc5562..b6d691e549 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index e9cc0bc00c..6c3814a75f 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -159,7 +159,7 @@ namespace osu.Game.Overlays.Profile.Sections showMoreText.Show(); } - public void ShowMore() => showMoreText.Alpha = Children.Where(d => !d.IsPresent).Where((d, i) => (d.Alpha = (i < 5 ? 1 : 0)) == 0).Any() ? 1 : 0; + public void ShowMore() => showMoreText.Alpha = Children.Where(d => !d.IsPresent).Where((d, i) => (d.Alpha = i < 5 ? 1 : 0) == 0).Any() ? 1 : 0; } } } From c877a5a8b73b7c59d41038850c4d8169c0fe45ca Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 19:20:41 +0200 Subject: [PATCH 014/344] update TestCase --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 46 ++++++++++--------- .../Graphics/Containers/OsuHoverContainer.cs | 1 - .../Overlays/Profile/Sections/RanksSection.cs | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs index a561559fdc..278f3408bf 100644 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs @@ -39,28 +39,6 @@ namespace osu.Desktop.Tests.Visual } }); - AddStep("Add First Place", () => ranks.ScoresFirst = new[] - { - new Score - { - Rank = ScoreRank.A, - Accuracy = 0.735, - PP = 666, - Date = DateTimeOffset.UtcNow, - Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime(), new OsuModEasy() }, - Beatmap = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "FREEDOM DiVE", - Artist = "xi" - }, - Version = "FOUR DIMENSIONS", - OnlineBeatmapID = 129891, - } - } - }); - AddStep("Add Best Performances", () => { List scores = new List(); @@ -91,6 +69,30 @@ namespace osu.Desktop.Tests.Visual } ranks.ScoresBest = scores.ToArray(); }); + + AddStep("Add First Place", () => ranks.ScoresFirst = new[] + { + new Score + { + Rank = ScoreRank.A, + Accuracy = 0.735, + PP = 666, + Date = DateTimeOffset.UtcNow, + Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime(), new OsuModEasy() }, + Beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "FREEDOM DiVE", + Artist = "xi" + }, + Version = "FOUR DIMENSIONS", + OnlineBeatmapID = 129891, + } + } + }); + + AddStep("Show More", ((RanksSection.ScoreFlowContainer)ranks.Children[1]).ShowMore); } } } diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index efac292c76..3f82ad2179 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Input; namespace osu.Game.Graphics.Containers { - public class OsuHoverContainer : OsuClickableContainer { private Color4 hoverColour; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 6c3814a75f..e7c32f76e4 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Profile.Sections } } - private class ScoreFlowContainer : Container + public class ScoreFlowContainer : Container { private readonly FillFlowContainer scores; private readonly OsuClickableContainer showMoreText; From bb8374b4fef83d7b98df2d6e11f407d9c87ce78a Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 19:47:51 +0200 Subject: [PATCH 015/344] override LoadComplete instead of using the constructor --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs index 278f3408bf..867e62859d 100644 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs @@ -20,8 +20,9 @@ namespace osu.Desktop.Tests.Visual { public override string Description => "showing your latest achievements"; - public TestCaseUserRanks() + protected override void LoadComplete() { + base.LoadComplete(); RanksSection ranks; Add(new Container From acc9b20b0f32ade24a06b62438f275820ddde69c Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 22:37:05 +0200 Subject: [PATCH 016/344] move another thing to LoadComplete instead of the constructor --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index e7c32f76e4..2d8da5b1f0 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -116,12 +116,12 @@ namespace osu.Game.Overlays.Profile.Sections public class ScoreFlowContainer : Container { - private readonly FillFlowContainer scores; - private readonly OsuClickableContainer showMoreText; + private FillFlowContainer scores; + private OsuClickableContainer showMoreText; protected override Container Content => scores; - public ScoreFlowContainer() + protected override void LoadComplete() { InternalChild = new FillFlowContainer { From 15e4e487e2aa1ee321ac855adbc5b7820f9271c2 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Wed, 9 Aug 2017 22:58:06 +0200 Subject: [PATCH 017/344] I hope this works --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 3 +- .../Overlays/Profile/Sections/RanksSection.cs | 29 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs index 867e62859d..41f3a37e94 100644 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs @@ -16,13 +16,14 @@ using System.Collections.Generic; namespace osu.Desktop.Tests.Visual { - public class TestCaseUserRanks : TestCase + internal class TestCaseUserRanks : TestCase { public override string Description => "showing your latest achievements"; protected override void LoadComplete() { base.LoadComplete(); + RanksSection ranks; Add(new Container diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 2d8da5b1f0..f6590140ec 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -136,19 +136,6 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, }, - showMoreText = new OsuHoverContainer - { - Action = ShowMore, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Child = new OsuSpriteText - { - TextSize = 14, - Text = "show more", - } - } }, }; } @@ -156,7 +143,21 @@ namespace osu.Game.Overlays.Profile.Sections public override void Clear(bool disposeChildren) { base.Clear(disposeChildren); - showMoreText.Show(); + if (showMoreText == null) + ((FillFlowContainer)InternalChild).Add(showMoreText = new OsuHoverContainer + { + Action = ShowMore, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Child = new OsuSpriteText + { + TextSize = 14, + Text = "show more", + } + }); + else + showMoreText.Show(); } public void ShowMore() => showMoreText.Alpha = Children.Where(d => !d.IsPresent).Where((d, i) => (d.Alpha = i < 5 ? 1 : 0) == 0).Any() ? 1 : 0; From 98b847b025ce1c9e8199c1635dce6ece2efd7905 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Sep 2017 20:05:43 +0900 Subject: [PATCH 018/344] Add API retrieval support --- .../Online/API/Requests/GetScoresRequest.cs | 23 ++++++- .../API/Requests/GetUserScoresRequest.cs | 28 +++++++++ osu.Game/Overlays/Profile/ProfileSection.cs | 10 +++ .../Profile/Sections/Ranks/DrawableScore.cs | 14 +++-- .../Overlays/Profile/Sections/RanksSection.cs | 62 +++++++++++++++++-- osu.Game/Overlays/UserProfileOverlay.cs | 2 + osu.Game/Rulesets/Scoring/Score.cs | 4 +- osu.Game/osu.Game.csproj | 1 + 8 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Online/API/Requests/GetUserScoresRequest.cs diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index ef9ee85d25..dfe1310325 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; @@ -73,6 +74,9 @@ namespace osu.Game.Online.API.Requests set { Replay = value; } } + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + [JsonProperty(@"score_id")] private long onlineScoreID { @@ -85,6 +89,18 @@ namespace osu.Game.Online.API.Requests set { Date = value; } } + [JsonProperty(@"beatmap")] + private BeatmapInfo beatmap + { + set { Beatmap = value; } + } + + [JsonProperty(@"beatmapset")] + private BeatmapMetadata metadata + { + set { Beatmap.Metadata = value; } + } + [JsonProperty(@"statistics")] private Dictionary jsonStats { @@ -122,7 +138,12 @@ namespace osu.Game.Online.API.Requests public void ApplyBeatmap(BeatmapInfo beatmap) { Beatmap = beatmap; - Ruleset = beatmap.Ruleset; + ApplyRuleset(beatmap.Ruleset); + } + + public void ApplyRuleset(RulesetInfo ruleset) + { + Ruleset = ruleset; // Evaluate the mod string Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs new file mode 100644 index 0000000000..20597aecc1 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserScoresRequest : APIRequest> + { + private readonly long userId; + private readonly ScoreType type; + + public GetUserScoresRequest(long userId, ScoreType type) + { + this.userId = userId; + this.type = type; + } + + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}"; + } + + public enum ScoreType + { + Best, + Firsts, + Recent + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 2b5084e321..df7c0e117f 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Users; using OpenTK.Graphics; namespace osu.Game.Overlays.Profile @@ -18,8 +19,17 @@ namespace osu.Game.Overlays.Profile public abstract string Identifier { get; } private readonly FillFlowContainer content; + protected override Container Content => content; + public virtual User User + { + get { return user; } + set { user = value; } + } + + private User user; + protected ProfileSection() { Direction = FillDirection.Vertical; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 9318798ec3..d92ebf6dca 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -79,26 +79,28 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(OsuColour colour, LocalisationEngine locale) { - stats.Add(new OsuSpriteText { - Text = score.PP + "pp", + stats.Add(new OsuSpriteText + { + Text = $"{score.PP ?? 0}pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 18, Font = "Exo2.0-BoldItalic", }); - if(weight != -1) + if (weight != -1) { stats.Add(new OsuSpriteText { - Text = $"weighted: {(int)(score.PP * weight)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", + Text = $"weighted: {(int)(score?.PP * weight ?? 0)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, TextSize = 11, Font = "Exo2.0-RegularItalic", - }); + }); } - stats.Add(new OsuSpriteText { + stats.Add(new OsuSpriteText + { Text = "accuracy: " + score.Accuracy.ToString("0.00%"), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index f6590140ec..4f9470bd6e 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -8,7 +8,13 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Scoring; using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections { @@ -21,6 +27,9 @@ namespace osu.Game.Overlays.Profile.Sections private readonly ScoreFlowContainer best, first; private readonly OsuSpriteText bestMissing, firstMissing; + private APIAccess api; + private RulesetStore rulesets; + public RanksSection() { Children = new Drawable[] @@ -62,12 +71,57 @@ namespace osu.Game.Overlays.Profile.Sections }; } - public Score[] ScoresBest + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + } + + public override User User + { + get + { + return base.User; + } + + set + { + base.User = value; + + // fetch online ranks + foreach (ScoreType m in new[] { ScoreType.Best, ScoreType.Firsts }) + { + ScoreType thisType = m; + var req = new GetUserScoresRequest(User.Id, m); + req.Success += scores => + { + foreach (var s in scores) + s.ApplyRuleset(rulesets.GetRuleset(s.OnlineRulesetID)); + + switch (thisType) + { + case ScoreType.Best: + ScoresBest = scores; + break; + case ScoreType.Firsts: + ScoresFirst = scores; + break; + } + }; + + Schedule(() => { api.Queue(req); }); + } + } + } + + + public IEnumerable ScoresBest { set { best.Clear(); - if (value.Length == 0) + if (!value.Any()) { bestMissing.Show(); } @@ -90,12 +144,12 @@ namespace osu.Game.Overlays.Profile.Sections } } - public Score[] ScoresFirst + public IEnumerable ScoresFirst { set { first.Clear(); - if (value.Length == 0) + if (!value.Any()) { firstMissing.Show(); } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index f03ef3f1ed..034d956366 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -174,6 +174,8 @@ namespace osu.Game.Overlays var sec = sections.FirstOrDefault(s => s.Identifier == id); if (sec != null) { + sec.User = user; + sectionsContainer.Add(sec); tabs.AddItem(sec); } diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 69ed9197d7..2af6509f09 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Scoring public double Health { get; set; } = 1; - public double PP { get; set; } + public double? PP { get; set; } public int MaxCombo { get; set; } @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Scoring public RulesetInfo Ruleset { get; set; } - public Mod[] Mods { get; set; } + public Mod[] Mods { get; set; } = { }; public User User; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4046c0f9a1..a18087b856 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -81,6 +81,7 @@ + From 740e766201ccedccfa191a80a51ebc2c01a62352 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 15 Sep 2017 19:39:55 +0300 Subject: [PATCH 019/344] BreakOverlay and LetterboxOverlay implementation --- .../Visual/TestCaseBreakOverlay.cs | 74 +++++++++++++++++++ osu.Desktop.Tests/osu.Desktop.Tests.csproj | 1 + osu.Game/Beatmaps/Timing/BreakPeriod.cs | 6 +- osu.Game/Screens/Play/BreakOverlay.cs | 64 ++++++++++++++++ osu.Game/Screens/Play/LetterboxOverlay.cs | 63 ++++++++++++++++ osu.Game/Screens/Play/Player.cs | 14 ++-- osu.Game/osu.Game.csproj | 2 + 7 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs create mode 100644 osu.Game/Screens/Play/BreakOverlay.cs create mode 100644 osu.Game/Screens/Play/LetterboxOverlay.cs diff --git a/osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs new file mode 100644 index 0000000000..2c77b1ca39 --- /dev/null +++ b/osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Beatmaps.Timing; +using osu.Game.Screens.Play; +using System.Collections.Generic; + +namespace osu.Desktop.Tests.Visual +{ + internal class TestCaseBreakOverlay : OsuTestCase + { + public override string Description => @"Tests breaks behavior"; + + private readonly BreakOverlay breakOverlay; + + public TestCaseBreakOverlay() + { + Clock = new FramedClock(); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + breakOverlay = new BreakOverlay(true) + }; + + AddStep("Add 2s break", () => startBreak(2000)); + AddStep("Add 5s break", () => startBreak(5000)); + AddStep("Add 2 breaks (2s each)", startMultipleBreaks); + } + + private void startBreak(double duration) + { + breakOverlay.Breaks = new List + { + new BreakPeriod + { + StartTime = Clock.CurrentTime, + EndTime = Clock.CurrentTime + duration, + } + }; + + breakOverlay.InitializeBreaks(); + } + + private void startMultipleBreaks() + { + double currentTime = Clock.CurrentTime; + + breakOverlay.Breaks = new List + { + new BreakPeriod + { + StartTime = currentTime, + EndTime = currentTime + 2000, + }, + new BreakPeriod + { + StartTime = currentTime + 4000, + EndTime = currentTime + 6000, + } + }; + + breakOverlay.InitializeBreaks(); + } + } +} \ No newline at end of file diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj index f894b25f06..5882578b79 100644 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ b/osu.Desktop.Tests/osu.Desktop.Tests.csproj @@ -72,6 +72,7 @@ + diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index fb307b7144..0cf4a0c65b 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -8,7 +8,7 @@ namespace osu.Game.Beatmaps.Timing /// /// The minimum duration required for a break to have any effect. /// - private const double min_break_duration = 650; + public const double MIN_BREAK_DURATION = 650; /// /// The break start time. @@ -28,6 +28,6 @@ namespace osu.Game.Beatmaps.Timing /// /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// - public bool HasEffect => Duration >= min_break_duration; + public bool HasEffect => Duration >= MIN_BREAK_DURATION; } -} +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs new file mode 100644 index 0000000000..2cbf6fd3c8 --- /dev/null +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using System.Collections.Generic; +using osu.Game.Beatmaps.Timing; + +namespace osu.Game.Screens.Play +{ + public class BreakOverlay : VisibilityContainer + { + private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; + + public List Breaks; + + private readonly bool letterboxing; + private readonly LetterboxOverlay letterboxOverlay; + + public BreakOverlay(bool letterboxing) + { + this.letterboxing = letterboxing; + + RelativeSizeAxes = Axes.Both; + Child = letterboxOverlay = new LetterboxOverlay(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InitializeBreaks(); + } + + public void InitializeBreaks() + { + if (Breaks != null) + { + foreach (var b in Breaks) + { + if (b.HasEffect) + { + using (BeginAbsoluteSequence(b.StartTime, true)) + { + Show(); + + using (BeginDelayedSequence(b.Duration, true)) + Hide(); + } + } + } + } + } + + protected override void PopIn() + { + if (letterboxing) letterboxOverlay.FadeIn(fade_duration); + } + + protected override void PopOut() + { + if (letterboxing) letterboxOverlay.FadeOut(fade_duration); + } + } +} diff --git a/osu.Game/Screens/Play/LetterboxOverlay.cs b/osu.Game/Screens/Play/LetterboxOverlay.cs new file mode 100644 index 0000000000..6672b947e7 --- /dev/null +++ b/osu.Game/Screens/Play/LetterboxOverlay.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Play +{ + public class LetterboxOverlay : Container + { + private const int letterbox_height = 350; + + private Color4 transparentBlack => new Color4(0, 0, 0, 0); + + public LetterboxOverlay() + { + RelativeSizeAxes = Axes.Both; + Alpha = 0; + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + RelativeSizeAxes = Axes.X, + Height = letterbox_height, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black, + TopRight = Color4.Black, + BottomLeft = transparentBlack, + BottomRight = transparentBlack, + } + } + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = letterbox_height, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = transparentBlack, + TopRight = transparentBlack, + BottomLeft = Color4.Black, + BottomRight = Color4.Black, + } + } + } + }; + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 593abb7d26..f7b5da97e9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,21 +161,25 @@ namespace osu.Game.Screens.Play }, Children = new Drawable[] { - new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, new Container { RelativeSizeAxes = Axes.Both, Clock = offsetClock, - Children = new Drawable[] - { - RulesetContainer, - } + Child = RulesetContainer, }, hudOverlay = new HUDOverlay { Anchor = Anchor.Centre, Origin = Anchor.Centre }, + new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = beatmap.Breaks, + Clock = decoupledClock + }, + new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, } }, failOverlay = new FailOverlay diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 92bcaf90f0..19b32669f5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -81,6 +81,8 @@ + + From be1e868a2a6570a8c33dc7643c89280e13fd2f9c Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 17 Sep 2017 22:39:34 +0200 Subject: [PATCH 020/344] add previews to osu!direct --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 34 +++++++ osu.Game/Overlays/Direct/DirectListPanel.cs | 83 +++++++++++++--- osu.Game/Overlays/Direct/DirectPanel.cs | 7 ++ osu.Game/Overlays/Direct/PlayButton.cs | 105 ++++++++++++++++++++ osu.Game/Overlays/DirectOverlay.cs | 18 ++++ osu.Game/osu.Game.csproj | 1 + 6 files changed, 232 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Overlays/Direct/PlayButton.cs diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 3a9e75bd38..b638e21c4b 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -13,6 +13,8 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Framework.Input; +using osu.Framework.Audio; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Direct { @@ -22,6 +24,11 @@ namespace osu.Game.Overlays.Direct private const float vertical_padding = 5; private FillFlowContainer bottomPanel; + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + public override Bindable PreviewPlaying { get; } = new Bindable(); public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { @@ -88,6 +95,15 @@ namespace osu.Game.Overlays.Direct { RelativeSizeAxes = Axes.Both, }, + progressBar = new Box + { + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Both, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -170,7 +186,25 @@ namespace osu.Game.Overlays.Direct new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, + playButton = new PlayButton(PreviewPlaying) + { + Margin = new MarginPadding { Top = 5, Left = 10 }, + Size = new Vector2(30), + Alpha = 0, + TrackURL = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", + }, }); + + PreviewPlaying.ValueChanged += newValue => playButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += newValue => progressBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); + } + + protected override void Update() + { + base.Update(); + + if (PreviewPlaying && playButton.Track != null) + progressBar.Width = (float)(playButton.Track.CurrentTime / playButton.Track.Length); } protected override bool OnClick(InputState state) diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index b3502b0827..7f233f2113 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -15,6 +15,8 @@ using osu.Framework.Input; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Framework.Configuration; +using osu.Framework.Audio; namespace osu.Game.Overlays.Direct { @@ -30,8 +32,14 @@ namespace osu.Game.Overlays.Direct Height = height; } + private PlayButton playButton; + private Box progressBar; + + protected override PlayButton PlayButton => playButton; + public override Bindable PreviewPlaying { get; } = new Bindable(); + [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + private void load(LocalisationEngine localisation, OsuColour colours) { Content.CornerRadius = 5; @@ -50,29 +58,51 @@ namespace osu.Game.Overlays.Direct { new FillFlowContainer { + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, + Direction = FillDirection.Horizontal, + LayoutEasing = Easing.OutQuint, + LayoutDuration = 120, + Spacing = new Vector2(10, 0), Children = new Drawable[] { - new OsuSpriteText + playButton = new PlayButton(PreviewPlaying) { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", - }, - new OsuSpriteText - { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), - Font = @"Exo2.0-BoldItalic", + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Size = new Vector2(height / 2), + FillMode = FillMode.Fit, + Alpha = 0, + TrackURL = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", }, new FillFlowContainer { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + TextSize = 18, + Font = @"Exo2.0-BoldItalic", + }, + new OsuSpriteText + { + Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Font = @"Exo2.0-BoldItalic", + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, }, - }, + } }, new FillFlowContainer { @@ -128,7 +158,28 @@ namespace osu.Game.Overlays.Direct }, }, }, + progressBar = new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Y, + Size = new Vector2(0, 3), + Alpha = 0, + Colour = colours.Yellow, + }, }); + + PreviewPlaying.ValueChanged += newValue => playButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += newValue => progressBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); + } + + protected override void Update() + { + base.Update(); + + if (PreviewPlaying && playButton.Track != null) + progressBar.Width = (float)(playButton.Track.CurrentTime / playButton.Track.Length); } private class DownloadButton : OsuClickableContainer diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 6f1f581d0b..24cd8dc54e 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -20,6 +20,7 @@ using osu.Game.Online.API; using osu.Framework.Logging; using osu.Game.Overlays.Notifications; using osu.Game.Online.API.Requests; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Direct { @@ -38,6 +39,9 @@ namespace osu.Game.Overlays.Direct private BeatmapManager beatmaps; private NotificationOverlay notifications; + public abstract Bindable PreviewPlaying { get; } + protected abstract PlayButton PlayButton { get; } + protected override Container Content => content; protected DirectPanel(BeatmapSetInfo setInfo) @@ -106,6 +110,7 @@ namespace osu.Game.Overlays.Direct { content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint); content.MoveToY(-4, hover_transition_time, Easing.OutQuint); + PlayButton.FadeIn(120, Easing.InOutQuint); return base.OnHover(state); } @@ -114,6 +119,8 @@ namespace osu.Game.Overlays.Direct { content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); content.MoveToY(0, hover_transition_time, Easing.OutQuint); + if (!PreviewPlaying) + PlayButton.FadeOut(120, Easing.InOutQuint); base.OnHoverLost(state); } diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs new file mode 100644 index 0000000000..8cfe1a30cb --- /dev/null +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using System.Threading.Tasks; + +namespace osu.Game.Overlays.Direct +{ + public class PlayButton : Container + { + public string TrackURL; + + public Bindable Playing; + + public Track Track; + private Bindable gameBeatmap; + private AudioManager audio; + + private Color4 hoverColour; + private readonly SpriteIcon icon; + + public PlayButton(Bindable playing) + { + Playing = playing; + Add(icon = new SpriteIcon() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_play, + }); + + Playing.ValueChanged += newValue => icon.Icon = newValue ? (Track == null ? FontAwesome.fa_spinner : FontAwesome.fa_pause) : FontAwesome.fa_play; + + Playing.ValueChanged += newValue => + { + if (newValue) + Track?.Start(); + else + Track?.Stop(); + }; + + Playing.ValueChanged += newValue => icon.FadeColour(newValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colour, OsuGameBase game, AudioManager audio) + { + hoverColour = colour.Yellow; + gameBeatmap = game.Beatmap; + this.audio = audio; + } + + private Task loadTask; + + protected override bool OnClick(InputState state) + { + gameBeatmap.Value.Track.Stop(); + + Playing.Value = !Playing.Value; + + if (loadTask == null) + { + icon.Spin(2000, RotationDirection.Clockwise); + + loadTask = Task.Run(() => + { + Track = audio.Track.Get(TrackURL); + Track.Looping = true; + if (Playing) + Track.Start(); + + icon.ClearTransforms(); + icon.Rotation = 0; + Playing.TriggerChange(); + }); + } + + return true; + } + + protected override bool OnHover(InputState state) + { + icon.FadeColour(hoverColour, 120, Easing.InOutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if(!Playing) + icon.FadeColour(Color4.White, 120, Easing.InOutQuint); + base.OnHoverLost(state); + } + } +} diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 9c07e1087f..9bb2afe127 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -32,6 +32,7 @@ namespace osu.Game.Overlays private readonly FillFlowContainer resultCountsContainer; private readonly OsuSpriteText resultCountsText; private FillFlowContainer panels; + private DirectPanel playing; protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); @@ -201,6 +202,12 @@ namespace osu.Game.Overlays panels.FadeOut(200); panels.Expire(); panels = null; + + if (playing != null) + { + playing.PreviewPlaying.Value = false; + playing = null; + } } if (BeatmapSets == null) return; @@ -223,6 +230,17 @@ namespace osu.Game.Overlays }) }; + foreach (DirectPanel panel in newPanels.Children) + panel.PreviewPlaying.ValueChanged += newValue => + { + if (newValue) + { + if (playing != null && playing != panel) + playing.PreviewPlaying.Value = false; + playing = panel; + } + }; + LoadComponentAsync(newPanels, p => { if (panels != null) ScrollFlow.Remove(panels); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 65ec7d31b3..f75db14f52 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -105,6 +105,7 @@ + From 3e8ae93b340093b89724ecc34c95e2e0a9676929 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Sun, 17 Sep 2017 22:54:23 +0200 Subject: [PATCH 021/344] appveyor --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 3 +-- osu.Game/Overlays/Direct/DirectListPanel.cs | 3 +-- osu.Game/Overlays/Direct/PlayButton.cs | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index b638e21c4b..24ccd8b7eb 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Framework.Input; -using osu.Framework.Audio; using osu.Framework.Configuration; namespace osu.Game.Overlays.Direct @@ -191,7 +190,7 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = 5, Left = 10 }, Size = new Vector2(30), Alpha = 0, - TrackURL = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", + TrackUrl = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", }, }); diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 7f233f2113..7112e927bd 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Framework.Configuration; -using osu.Framework.Audio; namespace osu.Game.Overlays.Direct { @@ -74,7 +73,7 @@ namespace osu.Game.Overlays.Direct Size = new Vector2(height / 2), FillMode = FillMode.Fit, Alpha = 0, - TrackURL = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", + TrackUrl = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", }, new FillFlowContainer { diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 8cfe1a30cb..b0d011a81c 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Direct { public class PlayButton : Container { - public string TrackURL; + public string TrackUrl; public Bindable Playing; @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Direct public PlayButton(Bindable playing) { Playing = playing; - Add(icon = new SpriteIcon() + Add(icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Direct loadTask = Task.Run(() => { - Track = audio.Track.Get(TrackURL); + Track = audio.Track.Get(TrackUrl); Track.Looping = true; if (Playing) Track.Start(); From c59d398aa527ae0beeacab2f44351fa9ec8477f6 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 19 Sep 2017 17:26:17 +0300 Subject: [PATCH 022/344] Fix includes --- osu.Game/osu.Game.csproj | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9301002958..5af2e18753 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -277,22 +277,6 @@ - - - - - - - - - - - - - - - - From 85b990c088c0eada760f850ef9706b72ad13def9 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 19 Sep 2017 17:28:10 +0300 Subject: [PATCH 023/344] Remove useless file --- osu.Desktop.Tests/osu.Desktop.Tests.csproj | 179 --------------------- 1 file changed, 179 deletions(-) delete mode 100644 osu.Desktop.Tests/osu.Desktop.Tests.csproj diff --git a/osu.Desktop.Tests/osu.Desktop.Tests.csproj b/osu.Desktop.Tests/osu.Desktop.Tests.csproj deleted file mode 100644 index 5882578b79..0000000000 --- a/osu.Desktop.Tests/osu.Desktop.Tests.csproj +++ /dev/null @@ -1,179 +0,0 @@ - - - - - Debug - AnyCPU - {230AC4F3-7783-49FB-9AEC-B83CDA3B9F3D} - Library - Properties - osu.Desktop.Tests - osu.Desktop.Tests - v4.6.1 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - 6 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - True - - - $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll - True - - - $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll - True - - - False - $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll - - - - $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll - - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll - - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {65DC628F-A640-4111-AB35-3A5652BC1E17} - osu.Framework.Desktop - - - {007b2356-ab6f-4bd9-96d5-116fc2dce69a} - osu.Framework.Testing - - - {C76BF5B3-985E-4D39-95FE-97C9C879B83A} - osu.Framework - - - {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} - osu.Game.Resources - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - - - {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} - osu.Game - - - - - osu.licenseheader - - - - - - - - - - - - - - \ No newline at end of file From 4cf88c72bfece5cfca8dd9b728fd3aa1fd7ffa65 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 19 Sep 2017 17:37:34 +0300 Subject: [PATCH 024/344] Move testcase to the correct project --- .../Tests}/Visual/TestCaseBreakOverlay.cs | 2 +- osu.Game/osu.Game.csproj | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename {osu.Desktop.Tests => osu.Game/Tests}/Visual/TestCaseBreakOverlay.cs (95%) diff --git a/osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs similarity index 95% rename from osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs rename to osu.Game/Tests/Visual/TestCaseBreakOverlay.cs index 2c77b1ca39..5bf558f79c 100644 --- a/osu.Desktop.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; using System.Collections.Generic; -namespace osu.Desktop.Tests.Visual +namespace osu.Game.Tests.Visual { internal class TestCaseBreakOverlay : OsuTestCase { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5af2e18753..fef9683cc8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -728,6 +728,7 @@ + From 0f04d8c6a7dd2584542eb40e538636e96f0f582a Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 15:58:28 +0300 Subject: [PATCH 025/344] Add remaining time container --- osu.Game/Screens/Play/BreakOverlay.cs | 38 +++++++++++++++---- osu.Game/Tests/Visual/TestCaseBreakOverlay.cs | 10 +---- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 2cbf6fd3c8..5162aa8007 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -5,24 +5,45 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using System.Collections.Generic; using osu.Game.Beatmaps.Timing; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; namespace osu.Game.Screens.Play { - public class BreakOverlay : VisibilityContainer + public class BreakOverlay : Container { private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; + private const int remaining_time_container_max_size = 500; public List Breaks; private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; + private readonly Container remainingTimeContainer; public BreakOverlay(bool letterboxing) { this.letterboxing = letterboxing; RelativeSizeAxes = Axes.Both; - Child = letterboxOverlay = new LetterboxOverlay(); + Children = new Drawable[] + { + letterboxOverlay = new LetterboxOverlay(), + remainingTimeContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0, 8), + CornerRadius = 4, + Masking = true, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + } + }; } protected override void LoadComplete() @@ -41,22 +62,25 @@ namespace osu.Game.Screens.Play { using (BeginAbsoluteSequence(b.StartTime, true)) { - Show(); + onBreakIn(b); using (BeginDelayedSequence(b.Duration, true)) - Hide(); + onBreakOut(); } } } } } - protected override void PopIn() + private void onBreakIn(BreakPeriod b) { - if (letterboxing) letterboxOverlay.FadeIn(fade_duration); + if (letterboxing) + letterboxOverlay.FadeIn(fade_duration); + + remainingTimeContainer.ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint).Then().ResizeWidthTo(0, b.Duration); } - protected override void PopOut() + private void onBreakOut() { if (letterboxing) letterboxOverlay.FadeOut(fade_duration); } diff --git a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs index 5bf558f79c..1f59a96de2 100644 --- a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs @@ -21,15 +21,7 @@ namespace osu.Game.Tests.Visual { Clock = new FramedClock(); - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - breakOverlay = new BreakOverlay(true) - }; + Child = breakOverlay = new BreakOverlay(true); AddStep("Add 2s break", () => startBreak(2000)); AddStep("Add 5s break", () => startBreak(5000)); From eb93706c26baec129a0f33d5833e30611ac636aa Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 16:03:31 +0300 Subject: [PATCH 026/344] Remove useless usings --- osu.Game/Tests/Visual/TestCaseBreakOverlay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs index 1f59a96de2..2f7a4a2425 100644 --- a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs @@ -1,9 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; From 8d7db52200ec08817b46cb70596e5d80f085fd74 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 19:45:38 +0300 Subject: [PATCH 027/344] Add remaining time counter --- osu.Game/Screens/Play/BreakOverlay.cs | 86 +++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 5162aa8007..fe7edf36ff 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -8,6 +8,9 @@ using osu.Game.Beatmaps.Timing; using OpenTK; using osu.Framework.Graphics.Shapes; using OpenTK.Graphics; +using osu.Game.Graphics.Sprites; +using System; +using osu.Framework.Timing; namespace osu.Game.Screens.Play { @@ -18,9 +21,19 @@ namespace osu.Game.Screens.Play public List Breaks; + public override IFrameBasedClock Clock + { + set + { + base.Clock = remainingTimeCounter.Clock = value; + } + get { return base.Clock; } + } + private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; - private readonly Container remainingTimeContainer; + private readonly Container remainingTimeBox; + private readonly RemainingTimeCounter remainingTimeCounter; public BreakOverlay(bool letterboxing) { @@ -30,7 +43,7 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { letterboxOverlay = new LetterboxOverlay(), - remainingTimeContainer = new Container + remainingTimeBox = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -42,6 +55,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Colour = Color4.White, } + }, + remainingTimeCounter = new RemainingTimeCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = 25 }, } }; } @@ -77,12 +96,71 @@ namespace osu.Game.Screens.Play if (letterboxing) letterboxOverlay.FadeIn(fade_duration); - remainingTimeContainer.ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint).Then().ResizeWidthTo(0, b.Duration); + remainingTimeBox + .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) + .Then() + .ResizeWidthTo(0, b.Duration); + + Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); + remainingTimeCounter.FadeIn(fade_duration); } private void onBreakOut() { - if (letterboxing) letterboxOverlay.FadeOut(fade_duration); + if (letterboxing) + letterboxOverlay.FadeOut(fade_duration); + + remainingTimeCounter.FadeOut(fade_duration); + } + + private class RemainingTimeCounter : Container + { + private readonly OsuSpriteText counter; + + private int? previousSecond; + + private double remainingTime; + + private bool isCounting; + + public RemainingTimeCounter() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Child = counter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 35, + Font = "Venera", + }; + } + + public void StartCounting(double remainingTime) + { + this.remainingTime = remainingTime; + isCounting = true; + } + + protected override void Update() + { + base.Update(); + + if (isCounting) + { + var currentTime = Clock.CurrentTime; + if (currentTime < remainingTime) + { + int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0) + 1; + if (currentSecond != previousSecond) + { + counter.Text = currentSecond.ToString(); + previousSecond = currentSecond; + } + } + else isCounting = false; + } + } } } } From 4699a4460821d8c318de574b395abf77a35d136e Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 20:50:14 +0300 Subject: [PATCH 028/344] Add info container --- osu.Game/Screens/Play/BreakOverlay.cs | 100 +++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index fe7edf36ff..87dd86b90c 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -11,13 +11,16 @@ using OpenTK.Graphics; using osu.Game.Graphics.Sprites; using System; using osu.Framework.Timing; +using osu.Framework.Allocation; +using osu.Game.Graphics; namespace osu.Game.Screens.Play { public class BreakOverlay : Container { private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private const int remaining_time_container_max_size = 500; + private const int remaining_time_container_max_size = 450; + private const int element_margin = 25; public List Breaks; @@ -34,6 +37,7 @@ namespace osu.Game.Screens.Play private readonly LetterboxOverlay letterboxOverlay; private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; + private readonly InfoContainer info; public BreakOverlay(bool letterboxing) { @@ -50,17 +54,19 @@ namespace osu.Game.Screens.Play Size = new Vector2(0, 8), CornerRadius = 4, Masking = true, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } + Child = new Box { RelativeSizeAxes = Axes.Both } }, remainingTimeCounter = new RemainingTimeCounter { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, - Margin = new MarginPadding { Bottom = 25 }, + Margin = new MarginPadding { Bottom = element_margin }, + }, + info = new InfoContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = element_margin }, } }; } @@ -103,6 +109,8 @@ namespace osu.Game.Screens.Play Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); remainingTimeCounter.FadeIn(fade_duration); + + info.FadeIn(fade_duration); } private void onBreakOut() @@ -111,6 +119,82 @@ namespace osu.Game.Screens.Play letterboxOverlay.FadeOut(fade_duration); remainingTimeCounter.FadeOut(fade_duration); + info.FadeOut(fade_duration); + } + + private class InfoContainer : FillFlowContainer + { + public InfoContainer() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Direction = FillDirection.Vertical; + Spacing = new Vector2(5); + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"current progress".ToUpper(), + TextSize = 15, + Font = "Exo2.0-Black", + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Children = new InfoLine[] + { + new InfoLine(@"Accuracy", @"88.54%"), + new InfoLine(@"Rank", @"#6584"), + new InfoLine(@"Grade", @"A"), + }, + } + }; + } + + private class InfoLine : Container + { + private const int margin = 2; + + private readonly OsuSpriteText text; + private readonly OsuSpriteText valueText; + + public InfoLine(string name, string value) + { + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Text = name, + TextSize = 17, + Margin = new MarginPadding { Right = margin } + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Text = value, + TextSize = 17, + Font = "Exo2.0-Bold", + Margin = new MarginPadding { Left = margin } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Yellow; + valueText.Colour = colours.YellowLight; + } + } } private class RemainingTimeCounter : Container @@ -131,7 +215,7 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 35, + TextSize = 33, Font = "Venera", }; } From 581689a84d37ef44d26a91590adb45faf95e71ff Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 20:58:20 +0300 Subject: [PATCH 029/344] CI fixes --- osu.Game/Screens/Play/BreakOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index 87dd86b90c..da7ba12a6e 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps.Timing; using OpenTK; using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; using osu.Game.Graphics.Sprites; using System; using osu.Framework.Timing; @@ -146,7 +145,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new InfoLine[] + Children = new [] { new InfoLine(@"Accuracy", @"88.54%"), new InfoLine(@"Rank", @"#6584"), From 18a714df74a8e8ef75cf50b8d64a292a269626c1 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 20 Sep 2017 22:33:07 +0300 Subject: [PATCH 030/344] Move every class to it's own file --- osu.Game/Screens/Play/BreakOverlay.cs | 249 ------------------ .../Play/BreaksOverlay/BreakOverlay.cs | 120 +++++++++ .../Play/BreaksOverlay/InfoContainer.cs | 87 ++++++ .../BreaksOverlay/RemainingTimeCounter.cs | 60 +++++ osu.Game/Screens/Play/Player.cs | 1 + osu.Game/Tests/Visual/TestCaseBreakOverlay.cs | 4 +- osu.Game/osu.Game.csproj | 4 +- 7 files changed, 274 insertions(+), 251 deletions(-) delete mode 100644 osu.Game/Screens/Play/BreakOverlay.cs create mode 100644 osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs create mode 100644 osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs create mode 100644 osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs deleted file mode 100644 index da7ba12a6e..0000000000 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using System.Collections.Generic; -using osu.Game.Beatmaps.Timing; -using OpenTK; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; -using System; -using osu.Framework.Timing; -using osu.Framework.Allocation; -using osu.Game.Graphics; - -namespace osu.Game.Screens.Play -{ - public class BreakOverlay : Container - { - private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private const int remaining_time_container_max_size = 450; - private const int element_margin = 25; - - public List Breaks; - - public override IFrameBasedClock Clock - { - set - { - base.Clock = remainingTimeCounter.Clock = value; - } - get { return base.Clock; } - } - - private readonly bool letterboxing; - private readonly LetterboxOverlay letterboxOverlay; - private readonly Container remainingTimeBox; - private readonly RemainingTimeCounter remainingTimeCounter; - private readonly InfoContainer info; - - public BreakOverlay(bool letterboxing) - { - this.letterboxing = letterboxing; - - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] - { - letterboxOverlay = new LetterboxOverlay(), - remainingTimeBox = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0, 8), - CornerRadius = 4, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }, - remainingTimeCounter = new RemainingTimeCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, - Margin = new MarginPadding { Bottom = element_margin }, - }, - info = new InfoContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = element_margin }, - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - InitializeBreaks(); - } - - public void InitializeBreaks() - { - if (Breaks != null) - { - foreach (var b in Breaks) - { - if (b.HasEffect) - { - using (BeginAbsoluteSequence(b.StartTime, true)) - { - onBreakIn(b); - - using (BeginDelayedSequence(b.Duration, true)) - onBreakOut(); - } - } - } - } - } - - private void onBreakIn(BreakPeriod b) - { - if (letterboxing) - letterboxOverlay.FadeIn(fade_duration); - - remainingTimeBox - .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) - .Then() - .ResizeWidthTo(0, b.Duration); - - Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); - remainingTimeCounter.FadeIn(fade_duration); - - info.FadeIn(fade_duration); - } - - private void onBreakOut() - { - if (letterboxing) - letterboxOverlay.FadeOut(fade_duration); - - remainingTimeCounter.FadeOut(fade_duration); - info.FadeOut(fade_duration); - } - - private class InfoContainer : FillFlowContainer - { - public InfoContainer() - { - AutoSizeAxes = Axes.Both; - Alpha = 0; - Direction = FillDirection.Vertical; - Spacing = new Vector2(5); - Children = new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = @"current progress".ToUpper(), - TextSize = 15, - Font = "Exo2.0-Black", - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Direction = FillDirection.Vertical, - Children = new [] - { - new InfoLine(@"Accuracy", @"88.54%"), - new InfoLine(@"Rank", @"#6584"), - new InfoLine(@"Grade", @"A"), - }, - } - }; - } - - private class InfoLine : Container - { - private const int margin = 2; - - private readonly OsuSpriteText text; - private readonly OsuSpriteText valueText; - - public InfoLine(string name, string value) - { - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - Text = name, - TextSize = 17, - Margin = new MarginPadding { Right = margin } - }, - valueText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - Text = value, - TextSize = 17, - Font = "Exo2.0-Bold", - Margin = new MarginPadding { Left = margin } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = colours.Yellow; - valueText.Colour = colours.YellowLight; - } - } - } - - private class RemainingTimeCounter : Container - { - private readonly OsuSpriteText counter; - - private int? previousSecond; - - private double remainingTime; - - private bool isCounting; - - public RemainingTimeCounter() - { - AutoSizeAxes = Axes.Both; - Alpha = 0; - Child = counter = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - TextSize = 33, - Font = "Venera", - }; - } - - public void StartCounting(double remainingTime) - { - this.remainingTime = remainingTime; - isCounting = true; - } - - protected override void Update() - { - base.Update(); - - if (isCounting) - { - var currentTime = Clock.CurrentTime; - if (currentTime < remainingTime) - { - int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0) + 1; - if (currentSecond != previousSecond) - { - counter.Text = currentSecond.ToString(); - previousSecond = currentSecond; - } - } - else isCounting = false; - } - } - } - } -} diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs new file mode 100644 index 0000000000..aec96c10ac --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -0,0 +1,120 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Game.Beatmaps.Timing; +using System.Collections.Generic; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class BreakOverlay : Container + { + private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; + private const int remaining_time_container_max_size = 450; + private const int element_margin = 25; + + public List Breaks; + + public override IFrameBasedClock Clock + { + set + { + base.Clock = remainingTimeCounter.Clock = value; + } + get { return base.Clock; } + } + + private readonly bool letterboxing; + private readonly LetterboxOverlay letterboxOverlay; + private readonly Container remainingTimeBox; + private readonly RemainingTimeCounter remainingTimeCounter; + private readonly InfoContainer info; + + public BreakOverlay(bool letterboxing) + { + this.letterboxing = letterboxing; + + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] + { + letterboxOverlay = new LetterboxOverlay(), + remainingTimeBox = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0, 8), + CornerRadius = 4, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + remainingTimeCounter = new RemainingTimeCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Margin = new MarginPadding { Bottom = element_margin }, + }, + info = new InfoContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = element_margin }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InitializeBreaks(); + } + + public void InitializeBreaks() + { + if (Breaks != null) + { + foreach (var b in Breaks) + { + if (b.HasEffect) + { + using (BeginAbsoluteSequence(b.StartTime, true)) + { + onBreakIn(b); + + using (BeginDelayedSequence(b.Duration, true)) + onBreakOut(); + } + } + } + } + } + + private void onBreakIn(BreakPeriod b) + { + if (letterboxing) + letterboxOverlay.FadeIn(fade_duration); + + remainingTimeBox + .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) + .Then() + .ResizeWidthTo(0, b.Duration); + + Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); + remainingTimeCounter.FadeIn(fade_duration); + + info.FadeIn(fade_duration); + } + + private void onBreakOut() + { + if (letterboxing) + letterboxOverlay.FadeOut(fade_duration); + + remainingTimeCounter.FadeOut(fade_duration); + info.FadeOut(fade_duration); + } + } +} diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs new file mode 100644 index 0000000000..75fcfcba0e --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -0,0 +1,87 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class InfoContainer : FillFlowContainer + { + public InfoContainer() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Direction = FillDirection.Vertical; + Spacing = new Vector2(5); + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = @"current progress".ToUpper(), + TextSize = 15, + Font = "Exo2.0-Black", + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Children = new [] + { + new InfoLine(@"Accuracy", @"-"), + new InfoLine(@"Rank", @"-"), + new InfoLine(@"Grade", @"-"), + }, + } + }; + } + + private class InfoLine : Container + { + private const int margin = 2; + + private readonly OsuSpriteText text; + private readonly OsuSpriteText valueText; + + public InfoLine(string name, string value) + { + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Text = name, + TextSize = 17, + Margin = new MarginPadding { Right = margin } + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Text = value, + TextSize = 17, + Font = "Exo2.0-Bold", + Margin = new MarginPadding { Left = margin } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Yellow; + valueText.Colour = colours.YellowLight; + } + } + } +} diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs new file mode 100644 index 0000000000..fbf3e10688 --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Framework.Graphics; +using System; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class RemainingTimeCounter : Container + { + private readonly OsuSpriteText counter; + + private int? previousSecond; + + private double remainingTime; + + private bool isCounting; + + public RemainingTimeCounter() + { + AutoSizeAxes = Axes.Both; + Alpha = 0; + Child = counter = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 33, + Font = "Venera", + }; + } + + public void StartCounting(double remainingTime) + { + this.remainingTime = remainingTime; + isCounting = true; + } + + protected override void Update() + { + base.Update(); + + if (isCounting) + { + var currentTime = Clock.CurrentTime; + if (currentTime < remainingTime) + { + int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0) + 1; + if (currentSecond != previousSecond) + { + counter.Text = currentSecond.ToString(); + previousSecond = currentSecond; + } + } + else isCounting = false; + } + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index f7b5da97e9..86aa00875c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,6 +24,7 @@ using osu.Game.Screens.Ranking; using osu.Framework.Audio.Sample; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Screens.Play.BreaksOverlay; namespace osu.Game.Screens.Play { diff --git a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs index 2f7a4a2425..055a2bc5c4 100644 --- a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs @@ -3,7 +3,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.BreaksOverlay; using System.Collections.Generic; namespace osu.Game.Tests.Visual @@ -22,6 +22,8 @@ namespace osu.Game.Tests.Visual AddStep("Add 2s break", () => startBreak(2000)); AddStep("Add 5s break", () => startBreak(5000)); + AddStep("Add 10s break", () => startBreak(10000)); + AddStep("Add 15s break", () => startBreak(15000)); AddStep("Add 2 breaks (2s each)", startMultipleBreaks); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fef9683cc8..406867c656 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -275,7 +275,9 @@ - + + + From c79568135a44d1889c6a2f204bf6f12589049427 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 21 Sep 2017 01:44:30 +0300 Subject: [PATCH 031/344] Add arrows --- .../Screens/Play/BreaksOverlay/BlurredIcon.cs | 40 ++++++++++ .../Play/BreaksOverlay/BreakOverlay.cs | 60 +++++++++++++-- .../Screens/Play/BreaksOverlay/GlowingIcon.cs | 74 +++++++++++++++++++ .../{ => BreaksOverlay}/LetterboxOverlay.cs | 4 +- osu.Game/osu.Game.csproj | 4 +- 5 files changed, 173 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs create mode 100644 osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs rename osu.Game/Screens/Play/{ => BreaksOverlay}/LetterboxOverlay.cs (95%) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs new file mode 100644 index 0000000000..582bd818bd --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class BlurredIcon : BufferedContainer + { + private const int icon_size = 130; + + private readonly GlowingIcon icon; + + public FontAwesome Icon + { + set { icon.Icon = value; } + get { return icon.Icon; } + } + + public BlurredIcon() + { + Anchor = Anchor.CentreLeft; + RelativePositionAxes = Axes.X; + Size = new Vector2(icon_size * 1.7f); + Masking = true; + BlurSigma = new Vector2(20); + Alpha = 0.6f; + CacheDrawnFrameBuffer = true; + Child = icon = new GlowingIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(icon_size), + }; + } + } +} diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index aec96c10ac..7e245a33d9 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -14,8 +14,11 @@ namespace osu.Game.Screens.Play.BreaksOverlay public class BreakOverlay : Container { private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private const int remaining_time_container_max_size = 450; - private const int element_margin = 25; + private const float remaining_time_container_max_size = 0.35f; + private const int vertical_margin = 25; + private const float glowing_x_offset = 0.13f; + private const float glowing_x_final = 0.22f; + private const float blurred_x_offset = 0.2f; public List Breaks; @@ -34,6 +37,12 @@ namespace osu.Game.Screens.Play.BreaksOverlay private readonly RemainingTimeCounter remainingTimeCounter; private readonly InfoContainer info; + private readonly GlowingIcon leftGlowingIcon; + private readonly GlowingIcon rightGlowingIcon; + + private readonly BlurredIcon leftBlurredIcon; + private readonly BlurredIcon rightBlurredIcon; + public BreakOverlay(bool letterboxing) { this.letterboxing = letterboxing; @@ -41,11 +50,16 @@ namespace osu.Game.Screens.Play.BreaksOverlay RelativeSizeAxes = Axes.Both; Children = new Drawable[] { - letterboxOverlay = new LetterboxOverlay(), + letterboxOverlay = new LetterboxOverlay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, remainingTimeBox = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, Size = new Vector2(0, 8), CornerRadius = 4, Masking = true, @@ -55,13 +69,35 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, - Margin = new MarginPadding { Bottom = element_margin }, + Margin = new MarginPadding { Bottom = vertical_margin }, }, info = new InfoContainer { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = element_margin }, + Margin = new MarginPadding { Top = vertical_margin }, + }, + leftGlowingIcon = new GlowingIcon + { + Origin = Anchor.CentreRight, + Icon = Graphics.FontAwesome.fa_chevron_left, + Size = new Vector2(60), + }, + rightGlowingIcon = new GlowingIcon + { + Origin = Anchor.CentreLeft, + Icon = Graphics.FontAwesome.fa_chevron_right, + Size = new Vector2(60), + }, + leftBlurredIcon = new BlurredIcon + { + Origin = Anchor.CentreRight, + Icon = Graphics.FontAwesome.fa_chevron_left, + }, + rightBlurredIcon = new BlurredIcon + { + Origin = Anchor.CentreLeft, + Icon = Graphics.FontAwesome.fa_chevron_right, } }; } @@ -102,10 +138,16 @@ namespace osu.Game.Screens.Play.BreaksOverlay .Then() .ResizeWidthTo(0, b.Duration); - Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); + Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime); remainingTimeCounter.FadeIn(fade_duration); info.FadeIn(fade_duration); + + leftGlowingIcon.MoveToX(1 - glowing_x_final, fade_duration, Easing.OutQuint); + rightGlowingIcon.MoveToX(glowing_x_final, fade_duration, Easing.OutQuint); + + leftBlurredIcon.MoveToX(1, fade_duration, Easing.OutQuint); + rightBlurredIcon.MoveToX(0, fade_duration, Easing.OutQuint); } private void onBreakOut() @@ -115,6 +157,12 @@ namespace osu.Game.Screens.Play.BreaksOverlay remainingTimeCounter.FadeOut(fade_duration); info.FadeOut(fade_duration); + + leftGlowingIcon.MoveToX(1 + glowing_x_offset, fade_duration, Easing.OutQuint); + rightGlowingIcon.MoveToX(-glowing_x_offset, fade_duration, Easing.OutQuint); + + leftBlurredIcon.MoveToX(1 + blurred_x_offset, fade_duration, Easing.OutQuint); + rightBlurredIcon.MoveToX(-blurred_x_offset, fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs new file mode 100644 index 0000000000..fe08a81c5a --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class GlowingIcon : Container + { + private readonly SpriteIcon icon; + private readonly SpriteIcon glow; + private readonly BufferedContainer glowContainer; + + public FontAwesome Icon + { + set { icon.Icon = glow.Icon = value; } + get { return icon.Icon; } + } + + public override Vector2 Size + { + set + { + glow.Size = icon.Size = value; + glowContainer.Size = value * 1.5f; + } + get + { + return glow.Size; + } + } + + public GlowingIcon() + { + Anchor = Anchor.CentreLeft; + RelativePositionAxes = Axes.X; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + glowContainer = new BufferedContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + BlurSigma = new Vector2(10), + CacheDrawnFrameBuffer = true, + Child = glow = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + }, + }, + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + glow.Colour = colours.Blue; + } + } +} diff --git a/osu.Game/Screens/Play/LetterboxOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs similarity index 95% rename from osu.Game/Screens/Play/LetterboxOverlay.cs rename to osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs index 6672b947e7..c95b15ef3a 100644 --- a/osu.Game/Screens/Play/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.BreaksOverlay { public class LetterboxOverlay : Container { @@ -60,4 +60,4 @@ namespace osu.Game.Screens.Play }; } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 406867c656..904e7800b8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -275,10 +275,12 @@ + + + - From e051bcc6dfd1895bbbca61dac269a5e59e1cf2bd Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 21 Sep 2017 01:51:40 +0300 Subject: [PATCH 032/344] Fix wrong arrows position on startup --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 7e245a33d9..d603298262 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -82,22 +82,26 @@ namespace osu.Game.Screens.Play.BreaksOverlay Origin = Anchor.CentreRight, Icon = Graphics.FontAwesome.fa_chevron_left, Size = new Vector2(60), + X = 1 + glowing_x_offset, }, rightGlowingIcon = new GlowingIcon { Origin = Anchor.CentreLeft, Icon = Graphics.FontAwesome.fa_chevron_right, Size = new Vector2(60), + X = -glowing_x_offset, }, leftBlurredIcon = new BlurredIcon { Origin = Anchor.CentreRight, Icon = Graphics.FontAwesome.fa_chevron_left, + X = 1 + blurred_x_offset, }, rightBlurredIcon = new BlurredIcon { Origin = Anchor.CentreLeft, Icon = Graphics.FontAwesome.fa_chevron_right, + X = -blurred_x_offset, } }; } From 9667270336263be0894280dd0becb12186b4e602 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 21 Sep 2017 01:56:50 +0300 Subject: [PATCH 033/344] Remove using --- osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs index fe08a81c5a..c92b0121c9 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs @@ -2,7 +2,6 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; From 22ab2c5e5dfd6e26e5050e2a220a23072c451224 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 21 Sep 2017 22:54:46 +0300 Subject: [PATCH 034/344] Apply suggested changes --- .../Play/BreaksOverlay/BreakOverlay.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index d603298262..970e8c7d49 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -114,20 +114,20 @@ namespace osu.Game.Screens.Play.BreaksOverlay public void InitializeBreaks() { - if (Breaks != null) - { - foreach (var b in Breaks) - { - if (b.HasEffect) - { - using (BeginAbsoluteSequence(b.StartTime, true)) - { - onBreakIn(b); + if (Breaks == null) + return; - using (BeginDelayedSequence(b.Duration, true)) - onBreakOut(); - } - } + foreach (var b in Breaks) + { + if (!b.HasEffect) + continue; + + using (BeginAbsoluteSequence(b.StartTime, true)) + { + onBreakIn(b); + + using (BeginDelayedSequence(b.Duration, true)) + onBreakOut(); } } } From 5383e33f3d5616431fa03ebed2598e60a0317f8e Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 21 Sep 2017 22:58:49 +0300 Subject: [PATCH 035/344] Remove useless clock assignment --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 970e8c7d49..29b24f1f87 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; using osu.Game.Beatmaps.Timing; using System.Collections.Generic; @@ -22,15 +21,6 @@ namespace osu.Game.Screens.Play.BreaksOverlay public List Breaks; - public override IFrameBasedClock Clock - { - set - { - base.Clock = remainingTimeCounter.Clock = value; - } - get { return base.Clock; } - } - private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; private readonly Container remainingTimeBox; From 72141935e84f8edd224c94224d1d18f917ff123d Mon Sep 17 00:00:00 2001 From: Jorolf Date: Thu, 21 Sep 2017 22:07:23 +0200 Subject: [PATCH 036/344] make pagination work and remove duplication in RanksSection --- osu.Desktop.Tests/Visual/TestCaseUserRanks.cs | 100 -------- osu.Game/Beatmaps/BeatmapInfo.cs | 2 + .../API/Requests/GetUserScoresRequest.cs | 6 +- .../Profile/Sections/Ranks/DrawableScore.cs | 5 +- .../Overlays/Profile/Sections/RanksSection.cs | 236 ++++++++---------- osu.Game/Tests/Visual/TestCaseUserRanks.cs | 75 +----- osu.Game/osu.Game.csproj | 2 - 7 files changed, 121 insertions(+), 305 deletions(-) delete mode 100644 osu.Desktop.Tests/Visual/TestCaseUserRanks.cs diff --git a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs b/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs deleted file mode 100644 index 41f3a37e94..0000000000 --- a/osu.Desktop.Tests/Visual/TestCaseUserRanks.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Overlays.Profile.Sections; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using System; -using System.Collections.Generic; - -namespace osu.Desktop.Tests.Visual -{ - internal class TestCaseUserRanks : TestCase - { - public override string Description => "showing your latest achievements"; - - protected override void LoadComplete() - { - base.LoadComplete(); - - RanksSection ranks; - - Add(new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, - ranks = new RanksSection(), - } - }); - - AddStep("Add Best Performances", () => - { - List scores = new List(); - Mod[] availableMods = { new OsuModHidden(), new OsuModFlashlight(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModPerfect() }; - List selectedMods = new List(availableMods); - for (int i = 0; i <= availableMods.Length; i++) - { - scores.Add(new Score - { - Rank = (ScoreRank) Enum.GetValues(typeof(ScoreRank)).GetValue(Enum.GetValues(typeof(ScoreRank)).Length - 1 - i), - Accuracy = Math.Pow(0.99, i), - PP = Math.Pow(0.5, i) * 800, - Date = DateTimeOffset.UtcNow.AddDays(-Math.Pow(i, 2)), - Mods = selectedMods.ToArray(), - Beatmap = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Highscore", - Artist = "Panda Eyes & Teminite" - }, - Version = "Game Over", - OnlineBeatmapID = 736215, - } - }); - if(i < availableMods.Length) - selectedMods.Remove(availableMods[i]); - } - ranks.ScoresBest = scores.ToArray(); - }); - - AddStep("Add First Place", () => ranks.ScoresFirst = new[] - { - new Score - { - Rank = ScoreRank.A, - Accuracy = 0.735, - PP = 666, - Date = DateTimeOffset.UtcNow, - Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime(), new OsuModEasy() }, - Beatmap = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "FREEDOM DiVE", - Artist = "xi" - }, - Version = "FOUR DIMENSIONS", - OnlineBeatmapID = 129891, - } - } - }); - - AddStep("Show More", ((RanksSection.ScoreFlowContainer)ranks.Children[1]).ShowMore); - } - } -} diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 0776669811..5775299ffb 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -19,8 +19,10 @@ namespace osu.Game.Beatmaps //TODO: should be in database public int BeatmapVersion; + [JsonProperty("id")] public int? OnlineBeatmapID { get; set; } + [JsonProperty("beatmapset_id")] public int? OnlineBeatmapSetID { get; set; } [ForeignKey(typeof(BeatmapSetInfo))] diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 20597aecc1..98db234196 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -9,14 +9,16 @@ namespace osu.Game.Online.API.Requests { private readonly long userId; private readonly ScoreType type; + private readonly int offset; - public GetUserScoresRequest(long userId, ScoreType type) + public GetUserScoresRequest(long userId, ScoreType type, int offset = 0) { this.userId = userId; this.type = type; + this.offset = offset; } - protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}"; + protected override string Target => $@"users/{userId}/scores/{type.ToString().ToLower()}?offset={offset}"; } public enum ScoreType diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index d92ebf6dca..27df3fd0fa 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -17,6 +17,7 @@ using osu.Framework.Localisation; using System.Globalization; using osu.Game.Rulesets.Scoring; using osu.Framework.Graphics.Cursor; +using System; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -81,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"{score.PP ?? 0}pp", + Text = $"{Math.Round(score.PP ?? 0)}pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 18, @@ -91,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"weighted: {(int)(score?.PP * weight ?? 0)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", + Text = $"weighted: {Math.Round(score.PP * weight ?? 0)}pp ({weight.ToString("0%", CultureInfo.CurrentCulture)})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 4f9470bd6e..b6c56f5cb1 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -15,6 +15,8 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Users; +using osu.Game.Graphics.UserInterface; +using OpenTK; namespace osu.Game.Overlays.Profile.Sections { @@ -24,8 +26,7 @@ namespace osu.Game.Overlays.Profile.Sections public override string Identifier => "top_ranks"; - private readonly ScoreFlowContainer best, first; - private readonly OsuSpriteText bestMissing, firstMissing; + private readonly ScoreContainer best, first; private APIAccess api; private RulesetStore rulesets; @@ -34,40 +35,8 @@ namespace osu.Game.Overlays.Profile.Sections { Children = new Drawable[] { - new OsuSpriteText - { - TextSize = 15, - Text = "Best Performance", - Font = "Exo2.0-RegularItalic", - Margin = new MarginPadding { Top = 10, Bottom = 10 }, - }, - best = new ScoreFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, - bestMissing = new OsuSpriteText - { - TextSize = 14, - Text = "No awesome performance records yet. :(", - }, - new OsuSpriteText - { - TextSize = 15, - Text = "First Place Ranks", - Font = "Exo2.0-RegularItalic", - Margin = new MarginPadding { Top = 20, Bottom = 10 }, - }, - first = new ScoreFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, - firstMissing = new OsuSpriteText - { - TextSize = 14, - Text = "No awesome performance records yet. :(", - }, + best = new ScoreContainer(ScoreType.Best, "Best Performance", true), + first = new ScoreContainer(ScoreType.Firsts, "First Place Ranks"), }; } @@ -88,119 +57,67 @@ namespace osu.Game.Overlays.Profile.Sections set { base.User = value; - - // fetch online ranks - foreach (ScoreType m in new[] { ScoreType.Best, ScoreType.Firsts }) - { - ScoreType thisType = m; - var req = new GetUserScoresRequest(User.Id, m); - req.Success += scores => - { - foreach (var s in scores) - s.ApplyRuleset(rulesets.GetRuleset(s.OnlineRulesetID)); - - switch (thisType) - { - case ScoreType.Best: - ScoresBest = scores; - break; - case ScoreType.Firsts: - ScoresFirst = scores; - break; - } - }; - - Schedule(() => { api.Queue(req); }); - } + best.User = value; + first.User = value; } } - - public IEnumerable ScoresBest + private class ScoreContainer : FillFlowContainer { - set + private readonly FillFlowContainer scoreContainer; + private readonly OsuSpriteText missing; + private readonly OsuHoverContainer showMoreButton; + private readonly LoadingAnimation showMoreLoading; + + private ScoreType type; + private int visiblePages; + private User user; + private readonly bool includeWeigth; + + private RulesetStore rulesets; + private APIAccess api; + + public User User { - best.Clear(); - if (!value.Any()) + set { - bestMissing.Show(); + user = value; + visiblePages = 0; + scoreContainer.Clear(); + showMoreButton.Hide(); + missing.Show(); + showMore(); } - else - { - bestMissing.Hide(); - int i = 0; - foreach (Score score in value) - { - best.Add(new DrawableScore(score, Math.Pow(0.95, i)) - { - RelativeSizeAxes = Axes.X, - Height = 60, - Alpha = 0, - }); - i++; - } - } - best.ShowMore(); } - } - public IEnumerable ScoresFirst - { - set + public ScoreContainer(ScoreType type, string header, bool includeWeigth = false) { - first.Clear(); - if (!value.Any()) - { - firstMissing.Show(); - } - else - { - firstMissing.Hide(); - foreach (Score score in value) - first.Add(new DrawableScore(score) - { - RelativeSizeAxes = Axes.X, - Height = 60, - Alpha = 0, - }); - } - first.ShowMore(); - } - } + this.type = type; + this.includeWeigth = includeWeigth; - public class ScoreFlowContainer : Container - { - private FillFlowContainer scores; - private OsuClickableContainer showMoreText; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; - protected override Container Content => scores; - - protected override void LoadComplete() - { - InternalChild = new FillFlowContainer + Children = new Drawable[] { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new OsuSpriteText { - scores = new FillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - }, + TextSize = 15, + Text = header, + Font = "Exo2.0-RegularItalic", + Margin = new MarginPadding { Top = 10, Bottom = 10 }, }, - }; - } - - public override void Clear(bool disposeChildren) - { - base.Clear(disposeChildren); - if (showMoreText == null) - ((FillFlowContainer)InternalChild).Add(showMoreText = new OsuHoverContainer + scoreContainer = new FillFlowContainer { - Action = ShowMore, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }, + showMoreButton = new OsuHoverContainer + { + Alpha = 0, + Action = showMore, AutoSizeAxes = Axes.Both, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -209,12 +126,57 @@ namespace osu.Game.Overlays.Profile.Sections TextSize = 14, Text = "show more", } - }); - else - showMoreText.Show(); + }, + showMoreLoading = new LoadingAnimation + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(14), + }, + missing = new OsuSpriteText + { + TextSize = 14, + Text = "No awesome performance records yet. :(", + }, + }; } - public void ShowMore() => showMoreText.Alpha = Children.Where(d => !d.IsPresent).Where((d, i) => (d.Alpha = i < 5 ? 1 : 0) == 0).Any() ? 1 : 0; + [BackgroundDependencyLoader] + private void load(APIAccess api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + } + + private void showMore() + { + var req = new GetUserScoresRequest(user.Id, type, visiblePages++ * 5); + + showMoreLoading.Show(); + showMoreButton.Hide(); + + req.Success += scores => + { + foreach (var s in scores) + s.ApplyRuleset(rulesets.GetRuleset(s.OnlineRulesetID)); + + showMoreButton.FadeTo(scores.Count == 5 ? 1 : 0); + showMoreLoading.Hide(); + + if (scores.Any()) + { + missing.Hide(); + foreach (Score score in scores) + scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : -1) + { + RelativeSizeAxes = Axes.X, + Height = 60, + }); + } + }; + + Schedule(() => { api.Queue(req); }); + } } } } diff --git a/osu.Game/Tests/Visual/TestCaseUserRanks.cs b/osu.Game/Tests/Visual/TestCaseUserRanks.cs index 41f3a37e94..e164426a4e 100644 --- a/osu.Game/Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game/Tests/Visual/TestCaseUserRanks.cs @@ -8,28 +8,28 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; +using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Users; using System; using System.Collections.Generic; -namespace osu.Desktop.Tests.Visual +namespace osu.Game.Tests.Visual { - internal class TestCaseUserRanks : TestCase + internal class TestCaseUserRanks : OsuTestCase { public override string Description => "showing your latest achievements"; - protected override void LoadComplete() - { - base.LoadComplete(); + public override IReadOnlyList RequiredTypes => new Type[] { typeof(DrawableScore), typeof(RanksSection) }; + public TestCaseUserRanks() + { RanksSection ranks; Add(new Container { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Box @@ -37,64 +37,15 @@ namespace osu.Desktop.Tests.Visual RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - ranks = new RanksSection(), - } - }); - - AddStep("Add Best Performances", () => - { - List scores = new List(); - Mod[] availableMods = { new OsuModHidden(), new OsuModFlashlight(), new OsuModHardRock(), new OsuModDoubleTime(), new OsuModPerfect() }; - List selectedMods = new List(availableMods); - for (int i = 0; i <= availableMods.Length; i++) - { - scores.Add(new Score + new ScrollContainer { - Rank = (ScoreRank) Enum.GetValues(typeof(ScoreRank)).GetValue(Enum.GetValues(typeof(ScoreRank)).Length - 1 - i), - Accuracy = Math.Pow(0.99, i), - PP = Math.Pow(0.5, i) * 800, - Date = DateTimeOffset.UtcNow.AddDays(-Math.Pow(i, 2)), - Mods = selectedMods.ToArray(), - Beatmap = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "Highscore", - Artist = "Panda Eyes & Teminite" - }, - Version = "Game Over", - OnlineBeatmapID = 736215, - } - }); - if(i < availableMods.Length) - selectedMods.Remove(availableMods[i]); - } - ranks.ScoresBest = scores.ToArray(); - }); - - AddStep("Add First Place", () => ranks.ScoresFirst = new[] - { - new Score - { - Rank = ScoreRank.A, - Accuracy = 0.735, - PP = 666, - Date = DateTimeOffset.UtcNow, - Mods = new Mod[] { new ModAutoplay(), new ModDoubleTime(), new OsuModEasy() }, - Beatmap = new BeatmapInfo - { - Metadata = new BeatmapMetadata - { - Title = "FREEDOM DiVE", - Artist = "xi" - }, - Version = "FOUR DIMENSIONS", - OnlineBeatmapID = 129891, - } + RelativeSizeAxes = Axes.Both, + Child = ranks = new RanksSection(), + }, } }); - AddStep("Show More", ((RanksSection.ScoreFlowContainer)ranks.Children[1]).ShowMore); + AddStep("Show cookiezi", () => ranks.User = new User { Id = 124493 }); } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7a78ed8dd4..a20a5ee13f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -298,7 +298,6 @@ - @@ -318,7 +317,6 @@ - From 14b8e9fd775914861f1f48c8ced7f71d63876797 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Thu, 21 Sep 2017 22:15:42 +0200 Subject: [PATCH 037/344] remove some redundant stuff --- .../Overlays/Profile/Sections/RanksSection.cs | 17 +++-------------- osu.Game/Tests/Visual/TestCaseUserRanks.cs | 6 +----- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index b6c56f5cb1..d5ee379a5b 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -8,15 +8,14 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Scoring; using System; -using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Graphics.UserInterface; using OpenTK; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections { @@ -28,9 +27,6 @@ namespace osu.Game.Overlays.Profile.Sections private readonly ScoreContainer best, first; - private APIAccess api; - private RulesetStore rulesets; - public RanksSection() { Children = new Drawable[] @@ -40,13 +36,6 @@ namespace osu.Game.Overlays.Profile.Sections }; } - [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) - { - this.api = api; - this.rulesets = rulesets; - } - public override User User { get @@ -69,7 +58,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly OsuHoverContainer showMoreButton; private readonly LoadingAnimation showMoreLoading; - private ScoreType type; + private readonly ScoreType type; private int visiblePages; private User user; private readonly bool includeWeigth; @@ -166,7 +155,7 @@ namespace osu.Game.Overlays.Profile.Sections if (scores.Any()) { missing.Hide(); - foreach (Score score in scores) + foreach (OnlineScore score in scores) scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : -1) { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Tests/Visual/TestCaseUserRanks.cs b/osu.Game/Tests/Visual/TestCaseUserRanks.cs index e164426a4e..9667897a7d 100644 --- a/osu.Game/Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game/Tests/Visual/TestCaseUserRanks.cs @@ -4,13 +4,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Users; using System; using System.Collections.Generic; @@ -21,7 +17,7 @@ namespace osu.Game.Tests.Visual { public override string Description => "showing your latest achievements"; - public override IReadOnlyList RequiredTypes => new Type[] { typeof(DrawableScore), typeof(RanksSection) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableScore), typeof(RanksSection) }; public TestCaseUserRanks() { From 9ee824ee6661dffaf01396f47f43d270b8d8fc28 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Thu, 21 Sep 2017 22:28:15 +0200 Subject: [PATCH 038/344] some more unused stuff --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index d5ee379a5b..d7df239003 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Scoring; using System; using System.Linq; using osu.Game.Online.API; From 56bde648393fb8870d1d755d9b531d972c862a82 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 01:16:05 +0300 Subject: [PATCH 039/344] Add arrows overlay --- .../Play/BreaksOverlay/ArrowsOverlay.cs | 84 +++++++++++++++++++ .../Screens/Play/BreaksOverlay/BlurredIcon.cs | 22 +++-- .../Play/BreaksOverlay/BreakOverlay.cs | 51 ++--------- .../{GlowingIcon.cs => GlowIcon.cs} | 48 +++++------ osu.Game/osu.Game.csproj | 3 +- 5 files changed, 129 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs rename osu.Game/Screens/Play/BreaksOverlay/{GlowingIcon.cs => GlowIcon.cs} (65%) diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs new file mode 100644 index 0000000000..715cc4fc0f --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using OpenTK; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class ArrowsOverlay : Container + { + private const int glowing_size = 60; + private const float glowing_final_offset = 0.25f; + private const float glowing_offscreen_offset = 0.6f; + + private const int blurred_size = 130; + private const float blurred_final_offset = 0.35f; + private const float blurred_offscreen_offset = 0.7f; + + private readonly GlowIcon leftGlowIcon; + private readonly GlowIcon rightGlowIcon; + + private readonly BlurredIcon leftBlurredIcon; + private readonly BlurredIcon rightBlurredIcon; + + public ArrowsOverlay() + { + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] + { + leftGlowIcon = new GlowIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + X = - glowing_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_right, + Size = new Vector2(glowing_size), + }, + rightGlowIcon = new GlowIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + X = glowing_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_left, + Size = new Vector2(glowing_size), + }, + leftBlurredIcon = new BlurredIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + X = - blurred_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_right, + Size = new Vector2(blurred_size), + }, + rightBlurredIcon = new BlurredIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + X = blurred_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_left, + Size = new Vector2(blurred_size), + }, + }; + } + + public void Show(double fadeDuration) + { + leftGlowIcon.MoveToX(-glowing_final_offset, fadeDuration, Easing.OutQuint); + rightGlowIcon.MoveToX(glowing_final_offset, fadeDuration, Easing.OutQuint); + + leftBlurredIcon.MoveToX(-blurred_final_offset, fadeDuration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_final_offset, fadeDuration, Easing.OutQuint); + } + + public void Hide(double fadeDuration) + { + leftGlowIcon.MoveToX(-glowing_offscreen_offset, fadeDuration, Easing.OutQuint); + rightGlowIcon.MoveToX(glowing_offscreen_offset, fadeDuration, Easing.OutQuint); + + leftBlurredIcon.MoveToX(-blurred_offscreen_offset, fadeDuration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_offscreen_offset, fadeDuration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs index 582bd818bd..25e6eef1fd 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs @@ -10,9 +10,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public class BlurredIcon : BufferedContainer { - private const int icon_size = 130; + private const int blur_sigma = 20; - private readonly GlowingIcon icon; + private readonly GlowIcon icon; public FontAwesome Icon { @@ -20,20 +20,26 @@ namespace osu.Game.Screens.Play.BreaksOverlay get { return icon.Icon; } } + public override Vector2 Size + { + set + { + icon.Size = value; + base.Size = value + new Vector2(blur_sigma * 2); + } + get { return icon.Size; } + } + public BlurredIcon() { - Anchor = Anchor.CentreLeft; RelativePositionAxes = Axes.X; - Size = new Vector2(icon_size * 1.7f); - Masking = true; - BlurSigma = new Vector2(20); + BlurSigma = new Vector2(blur_sigma); Alpha = 0.6f; CacheDrawnFrameBuffer = true; - Child = icon = new GlowingIcon + Child = icon = new GlowIcon { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Size = new Vector2(icon_size), }; } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 29b24f1f87..c0088ce816 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -15,9 +15,6 @@ namespace osu.Game.Screens.Play.BreaksOverlay private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; private const float remaining_time_container_max_size = 0.35f; private const int vertical_margin = 25; - private const float glowing_x_offset = 0.13f; - private const float glowing_x_final = 0.22f; - private const float blurred_x_offset = 0.2f; public List Breaks; @@ -26,12 +23,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay private readonly Container remainingTimeBox; private readonly RemainingTimeCounter remainingTimeCounter; private readonly InfoContainer info; - - private readonly GlowingIcon leftGlowingIcon; - private readonly GlowingIcon rightGlowingIcon; - - private readonly BlurredIcon leftBlurredIcon; - private readonly BlurredIcon rightBlurredIcon; + private readonly ArrowsOverlay arrowsOverlay; public BreakOverlay(bool letterboxing) { @@ -67,31 +59,10 @@ namespace osu.Game.Screens.Play.BreaksOverlay Origin = Anchor.TopCentre, Margin = new MarginPadding { Top = vertical_margin }, }, - leftGlowingIcon = new GlowingIcon + arrowsOverlay = new ArrowsOverlay { - Origin = Anchor.CentreRight, - Icon = Graphics.FontAwesome.fa_chevron_left, - Size = new Vector2(60), - X = 1 + glowing_x_offset, - }, - rightGlowingIcon = new GlowingIcon - { - Origin = Anchor.CentreLeft, - Icon = Graphics.FontAwesome.fa_chevron_right, - Size = new Vector2(60), - X = -glowing_x_offset, - }, - leftBlurredIcon = new BlurredIcon - { - Origin = Anchor.CentreRight, - Icon = Graphics.FontAwesome.fa_chevron_left, - X = 1 + blurred_x_offset, - }, - rightBlurredIcon = new BlurredIcon - { - Origin = Anchor.CentreLeft, - Icon = Graphics.FontAwesome.fa_chevron_right, - X = -blurred_x_offset, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } }; } @@ -136,12 +107,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay remainingTimeCounter.FadeIn(fade_duration); info.FadeIn(fade_duration); - - leftGlowingIcon.MoveToX(1 - glowing_x_final, fade_duration, Easing.OutQuint); - rightGlowingIcon.MoveToX(glowing_x_final, fade_duration, Easing.OutQuint); - - leftBlurredIcon.MoveToX(1, fade_duration, Easing.OutQuint); - rightBlurredIcon.MoveToX(0, fade_duration, Easing.OutQuint); + arrowsOverlay.Show(fade_duration); } private void onBreakOut() @@ -151,12 +117,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay remainingTimeCounter.FadeOut(fade_duration); info.FadeOut(fade_duration); - - leftGlowingIcon.MoveToX(1 + glowing_x_offset, fade_duration, Easing.OutQuint); - rightGlowingIcon.MoveToX(-glowing_x_offset, fade_duration, Easing.OutQuint); - - leftBlurredIcon.MoveToX(1 + blurred_x_offset, fade_duration, Easing.OutQuint); - rightBlurredIcon.MoveToX(-blurred_x_offset, fade_duration, Easing.OutQuint); + arrowsOverlay.Hide(fade_duration); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs similarity index 65% rename from osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs rename to osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs index c92b0121c9..91e794ff6b 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/GlowingIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs @@ -1,42 +1,40 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Graphics; using OpenTK; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; namespace osu.Game.Screens.Play.BreaksOverlay { - public class GlowingIcon : Container + public class GlowIcon : Container { - private readonly SpriteIcon icon; - private readonly SpriteIcon glow; - private readonly BufferedContainer glowContainer; + private const int blur_sigma = 5; - public FontAwesome Icon - { - set { icon.Icon = glow.Icon = value; } - get { return icon.Icon; } - } + private readonly SpriteIcon spriteIcon; + private readonly SpriteIcon glowIcon; + private readonly BufferedContainer glowContainer; public override Vector2 Size { set { - glow.Size = icon.Size = value; - glowContainer.Size = value * 1.5f; - } - get - { - return glow.Size; + spriteIcon.Size = glowIcon.Size = value; + glowContainer.Size = value + new Vector2(blur_sigma * 2); } + get { return spriteIcon.Size; } } - public GlowingIcon() + public FontAwesome Icon + { + set { spriteIcon.Icon = glowIcon.Icon = value; } + get { return spriteIcon.Icon; } + } + + public GlowIcon() { - Anchor = Anchor.CentreLeft; RelativePositionAxes = Axes.X; AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -45,17 +43,17 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Masking = true, - BlurSigma = new Vector2(10), + BlurSigma = new Vector2(blur_sigma), CacheDrawnFrameBuffer = true, - Child = glow = new SpriteIcon + Child = glowIcon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, Shadow = false, }, + }, - icon = new SpriteIcon + spriteIcon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -67,7 +65,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay [BackgroundDependencyLoader] private void load(OsuColour colours) { - glow.Colour = colours.Blue; + glowIcon.Colour = colours.BlueLight; } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 904e7800b8..dd823f6144 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -275,9 +275,10 @@ + - + From d58e5a613015beb9ffb9e1da672ab2bcdaf5db7d Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 20:43:51 +0300 Subject: [PATCH 040/344] Arrows improvements --- .../Play/BreaksOverlay/ArrowsOverlay.cs | 50 +++++++++++-------- .../Screens/Play/BreaksOverlay/BlurredIcon.cs | 22 +++++--- .../Play/BreaksOverlay/BreakOverlay.cs | 2 +- .../Screens/Play/BreaksOverlay/GlowIcon.cs | 33 ++++++------ 4 files changed, 57 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs index 715cc4fc0f..8ba801f204 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs @@ -9,13 +9,15 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public class ArrowsOverlay : Container { - private const int glowing_size = 60; - private const float glowing_final_offset = 0.25f; - private const float glowing_offscreen_offset = 0.6f; + private const int glow_icon_size = 65; + private const int glow_icon_blur_sigma = 8; + private const float glow_icon_final_offset = 0.2f; + private const float glow_icon_offscreen_offset = 0.6f; - private const int blurred_size = 130; - private const float blurred_final_offset = 0.35f; - private const float blurred_offscreen_offset = 0.7f; + private const int blurred_icon_blur_sigma = 20; + private const int blurred_icon_size = 130; + private const float blurred_icon_final_offset = 0.35f; + private const float blurred_icon_offscreen_offset = 0.7f; private readonly GlowIcon leftGlowIcon; private readonly GlowIcon rightGlowIcon; @@ -32,53 +34,57 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, - X = - glowing_offscreen_offset, + X = - glow_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_right, - Size = new Vector2(glowing_size), + BlurSigma = new Vector2(glow_icon_blur_sigma), + Size = new Vector2(glow_icon_size), }, rightGlowIcon = new GlowIcon { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - X = glowing_offscreen_offset, + X = glow_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_left, - Size = new Vector2(glowing_size), + BlurSigma = new Vector2(glow_icon_blur_sigma), + Size = new Vector2(glow_icon_size), }, leftBlurredIcon = new BlurredIcon { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, - X = - blurred_offscreen_offset, + X = - blurred_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_right, - Size = new Vector2(blurred_size), + BlurSigma = new Vector2(blurred_icon_blur_sigma), + Size = new Vector2(blurred_icon_size), }, rightBlurredIcon = new BlurredIcon { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - X = blurred_offscreen_offset, + X = blurred_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_left, - Size = new Vector2(blurred_size), + BlurSigma = new Vector2(blurred_icon_blur_sigma), + Size = new Vector2(blurred_icon_size), }, }; } public void Show(double fadeDuration) { - leftGlowIcon.MoveToX(-glowing_final_offset, fadeDuration, Easing.OutQuint); - rightGlowIcon.MoveToX(glowing_final_offset, fadeDuration, Easing.OutQuint); + leftGlowIcon.MoveToX(-glow_icon_final_offset, fadeDuration, Easing.OutQuint); + rightGlowIcon.MoveToX(glow_icon_final_offset, fadeDuration, Easing.OutQuint); - leftBlurredIcon.MoveToX(-blurred_final_offset, fadeDuration, Easing.OutQuint); - rightBlurredIcon.MoveToX(blurred_final_offset, fadeDuration, Easing.OutQuint); + leftBlurredIcon.MoveToX(-blurred_icon_final_offset, fadeDuration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_icon_final_offset, fadeDuration, Easing.OutQuint); } public void Hide(double fadeDuration) { - leftGlowIcon.MoveToX(-glowing_offscreen_offset, fadeDuration, Easing.OutQuint); - rightGlowIcon.MoveToX(glowing_offscreen_offset, fadeDuration, Easing.OutQuint); + leftGlowIcon.MoveToX(-glow_icon_offscreen_offset, fadeDuration, Easing.OutQuint); + rightGlowIcon.MoveToX(glow_icon_offscreen_offset, fadeDuration, Easing.OutQuint); - leftBlurredIcon.MoveToX(-blurred_offscreen_offset, fadeDuration, Easing.OutQuint); - rightBlurredIcon.MoveToX(blurred_offscreen_offset, fadeDuration, Easing.OutQuint); + leftBlurredIcon.MoveToX(-blurred_icon_offscreen_offset, fadeDuration, Easing.OutQuint); + rightBlurredIcon.MoveToX(blurred_icon_offscreen_offset, fadeDuration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs index 25e6eef1fd..9e69d9d076 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs @@ -5,14 +5,13 @@ using OpenTK; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Framework.Allocation; namespace osu.Game.Screens.Play.BreaksOverlay { public class BlurredIcon : BufferedContainer { - private const int blur_sigma = 20; - - private readonly GlowIcon icon; + private readonly SpriteIcon icon; public FontAwesome Icon { @@ -25,22 +24,29 @@ namespace osu.Game.Screens.Play.BreaksOverlay set { icon.Size = value; - base.Size = value + new Vector2(blur_sigma * 2); + base.Size = value + BlurSigma * 2.5f; + ForceRedraw(); } - get { return icon.Size; } + get { return base.Size; } } public BlurredIcon() { RelativePositionAxes = Axes.X; - BlurSigma = new Vector2(blur_sigma); - Alpha = 0.6f; + Alpha = 0.7f; CacheDrawnFrameBuffer = true; - Child = icon = new GlowIcon + Child = icon = new SpriteIcon { Origin = Anchor.Centre, Anchor = Anchor.Centre, + Shadow = false, }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index c0088ce816..1264feff3d 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay public class BreakOverlay : Container { private const double fade_duration = BreakPeriod.MIN_BREAK_DURATION / 2; - private const float remaining_time_container_max_size = 0.35f; + private const float remaining_time_container_max_size = 0.3f; private const int vertical_margin = 25; public List Breaks; diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs index 91e794ff6b..81c2445324 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs @@ -11,25 +11,29 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public class GlowIcon : Container { - private const int blur_sigma = 5; - private readonly SpriteIcon spriteIcon; - private readonly SpriteIcon glowIcon; - private readonly BufferedContainer glowContainer; + private readonly BlurredIcon blurredIcon; public override Vector2 Size { set { - spriteIcon.Size = glowIcon.Size = value; - glowContainer.Size = value + new Vector2(blur_sigma * 2); + blurredIcon.Size = value; + spriteIcon.Size = value - new Vector2(10); //Make it a bit smaller to make blur more visible + blurredIcon.ForceRedraw(); } - get { return spriteIcon.Size; } + get { return base.Size; } + } + + public Vector2 BlurSigma + { + set { blurredIcon.BlurSigma = value; } + get { return blurredIcon.BlurSigma; } } public FontAwesome Icon { - set { spriteIcon.Icon = glowIcon.Icon = value; } + set { spriteIcon.Icon = blurredIcon.Icon = value; } get { return spriteIcon.Icon; } } @@ -39,19 +43,10 @@ namespace osu.Game.Screens.Play.BreaksOverlay AutoSizeAxes = Axes.Both; Children = new Drawable[] { - glowContainer = new BufferedContainer + blurredIcon = new BlurredIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - BlurSigma = new Vector2(blur_sigma), - CacheDrawnFrameBuffer = true, - Child = glowIcon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }, - }, spriteIcon = new SpriteIcon { @@ -65,7 +60,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay [BackgroundDependencyLoader] private void load(OsuColour colours) { - glowIcon.Colour = colours.BlueLight; + blurredIcon.Colour = colours.Blue; } } } From 6fe2b64abdc0a7ab551e79e557932851e54cacab Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 20:50:00 +0300 Subject: [PATCH 041/344] Start breakOut animation a bit earlier --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 1264feff3d..27b7dc5408 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { onBreakIn(b); - using (BeginDelayedSequence(b.Duration, true)) + using (BeginDelayedSequence(b.Duration - fade_duration, true)) onBreakOut(); } } From 92eb8e4fa9700640ab322a0e8d850ae37255c5a0 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 21:00:45 +0300 Subject: [PATCH 042/344] Move blurred icons to a parallax container --- .../Play/BreaksOverlay/ArrowsOverlay.cs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs index 8ba801f204..9178daac54 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using OpenTK; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -48,24 +49,31 @@ namespace osu.Game.Screens.Play.BreaksOverlay BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, - leftBlurredIcon = new BlurredIcon + new ParallaxContainer { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - X = - blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, - BlurSigma = new Vector2(blurred_icon_blur_sigma), - Size = new Vector2(blurred_icon_size), - }, - rightBlurredIcon = new BlurredIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - X = blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, - BlurSigma = new Vector2(blurred_icon_blur_sigma), - Size = new Vector2(blurred_icon_size), - }, + ParallaxAmount = -0.02f, + Children = new Drawable[] + { + leftBlurredIcon = new BlurredIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + X = - blurred_icon_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_right, + BlurSigma = new Vector2(blurred_icon_blur_sigma), + Size = new Vector2(blurred_icon_size), + }, + rightBlurredIcon = new BlurredIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + X = blurred_icon_offscreen_offset, + Icon = Graphics.FontAwesome.fa_chevron_left, + BlurSigma = new Vector2(blurred_icon_blur_sigma), + Size = new Vector2(blurred_icon_size), + }, + } + } }; } From d73b40768e50426ed44349da7a6ad8d5f13fb143 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 21:12:58 +0300 Subject: [PATCH 043/344] More arrow adjustments to match the design --- osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs | 8 +++++--- osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs | 1 - osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs index 9178daac54..87dc8caf13 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs @@ -10,9 +10,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public class ArrowsOverlay : Container { - private const int glow_icon_size = 65; - private const int glow_icon_blur_sigma = 8; - private const float glow_icon_final_offset = 0.2f; + private const int glow_icon_size = 60; + private const int glow_icon_blur_sigma = 10; + private const float glow_icon_final_offset = 0.22f; private const float glow_icon_offscreen_offset = 0.6f; private const int blurred_icon_blur_sigma = 20; @@ -58,6 +58,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, + Alpha = 0.7f, X = - blurred_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_right, BlurSigma = new Vector2(blurred_icon_blur_sigma), @@ -67,6 +68,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, + Alpha = 0.7f, X = blurred_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_left, BlurSigma = new Vector2(blurred_icon_blur_sigma), diff --git a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs index 9e69d9d076..f16e7c7b96 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BlurredIcon.cs @@ -33,7 +33,6 @@ namespace osu.Game.Screens.Play.BreaksOverlay public BlurredIcon() { RelativePositionAxes = Axes.X; - Alpha = 0.7f; CacheDrawnFrameBuffer = true; Child = icon = new SpriteIcon { diff --git a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs index 81c2445324..b27eef632c 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/GlowIcon.cs @@ -18,8 +18,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { set { - blurredIcon.Size = value; - spriteIcon.Size = value - new Vector2(10); //Make it a bit smaller to make blur more visible + blurredIcon.Size = spriteIcon.Size = value; blurredIcon.ForceRedraw(); } get { return base.Size; } From ced620421913f255f20d271414f8c9206c3240a5 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Fri, 22 Sep 2017 22:10:05 +0300 Subject: [PATCH 044/344] oops --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 2 +- osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 27b7dc5408..bc0a0a2242 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay remainingTimeBox .ResizeWidthTo(remaining_time_container_max_size, fade_duration, Easing.OutQuint) .Then() - .ResizeWidthTo(0, b.Duration); + .ResizeWidthTo(0, b.Duration - fade_duration); Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime); remainingTimeCounter.FadeIn(fade_duration); diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs index fbf3e10688..ab6ee5ea5c 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay var currentTime = Clock.CurrentTime; if (currentTime < remainingTime) { - int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0) + 1; + int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0); if (currentSecond != previousSecond) { counter.Text = currentSecond.ToString(); From 2da3ea00b6a0bb550e2ab3a642632e6e363d6c01 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 23 Sep 2017 16:42:18 +0300 Subject: [PATCH 045/344] Bind break overlay to accuracy --- .../Play/BreaksOverlay/BreakOverlay.cs | 6 ++ .../Play/BreaksOverlay/InfoContainer.cs | 56 ++---------- .../Screens/Play/BreaksOverlay/InfoLine.cs | 89 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 5 +- osu.Game/osu.Game.csproj | 1 + 5 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index bc0a0a2242..e9dd112557 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Scoring; using System.Collections.Generic; namespace osu.Game.Screens.Play.BreaksOverlay @@ -119,5 +120,10 @@ namespace osu.Game.Screens.Play.BreaksOverlay info.FadeOut(fade_duration); arrowsOverlay.Hide(fade_duration); } + + public void BindProcessor(ScoreProcessor processor) + { + info.AccuracyDisplay.Current.BindTo(processor.Accuracy); + } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs index 75fcfcba0e..dc85fabbe0 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -2,16 +2,18 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.BreaksOverlay { public class InfoContainer : FillFlowContainer { + public PercentageInfoLine AccuracyDisplay; + public InfoLine RankDisplay; + public InfoLine GradeDisplay; + public InfoContainer() { AutoSizeAxes = Axes.Both; @@ -28,60 +30,20 @@ namespace osu.Game.Screens.Play.BreaksOverlay TextSize = 15, Font = "Exo2.0-Black", }, - new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new [] + Children = new Drawable[] { - new InfoLine(@"Accuracy", @"-"), - new InfoLine(@"Rank", @"-"), - new InfoLine(@"Grade", @"-"), + AccuracyDisplay = new PercentageInfoLine(@"Accuracy"), + RankDisplay = new InfoLine(@"Rank", @"#"), + GradeDisplay = new InfoLine(@"Grade"), }, } }; } - - private class InfoLine : Container - { - private const int margin = 2; - - private readonly OsuSpriteText text; - private readonly OsuSpriteText valueText; - - public InfoLine(string name, string value) - { - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - text = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - Text = name, - TextSize = 17, - Margin = new MarginPadding { Right = margin } - }, - valueText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - Text = value, - TextSize = 17, - Font = "Exo2.0-Bold", - Margin = new MarginPadding { Left = margin } - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = colours.Yellow; - valueText.Colour = colours.YellowLight; - } - } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs new file mode 100644 index 0000000000..0af766b321 --- /dev/null +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Play.BreaksOverlay +{ + public class InfoLine : Container + where T : struct + { + private const int margin = 2; + + public Bindable Current = new Bindable(); + + private readonly OsuSpriteText text; + private readonly OsuSpriteText valueText; + + private readonly string prefix; + + public InfoLine(string name, string prefix = @"") + { + this.prefix = prefix; + + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + Text = name, + TextSize = 17, + Margin = new MarginPadding { Right = margin } + }, + valueText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + Text = prefix + @"-", + TextSize = 17, + Font = "Exo2.0-Bold", + Margin = new MarginPadding { Left = margin } + } + }; + + Current.ValueChanged += currentValueChanged; + } + + private void currentValueChanged(T newValue) + { + valueText.Text = prefix + Format(newValue); + } + + protected virtual string Format(T count) => count.ToString(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + text.Colour = colours.Yellow; + valueText.Colour = colours.YellowLight; + } + } + + public class PercentageInfoLine : InfoLine + { + public PercentageInfoLine(string name, string prefix = "") : base(name, prefix) + { + } + + protected override string Format(double count) => $@"{count:P2}"; + } + + public enum Grade + { + SSplus, + SS, + Splus, + S, + A, + B, + C, + F + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 86aa00875c..d8ddee0ac3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Play #endregion + private BreakOverlay breakOverlay; private HUDOverlay hudOverlay; private FailOverlay failOverlay; @@ -173,7 +174,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks) + breakOverlay = new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -211,6 +212,8 @@ namespace osu.Game.Screens.Play hudOverlay.ModDisplay.Current.BindTo(working.Mods); + breakOverlay.BindProcessor(scoreProcessor); + // Bind ScoreProcessor to ourselves scoreProcessor.AllJudged += onCompletion; scoreProcessor.Failed += onFail; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dd823f6144..2cbbc42061 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -280,6 +280,7 @@ + From a69bef8ec075d52e721ac3f0e89b2ee2e3ff4b11 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 23 Sep 2017 16:51:31 +0300 Subject: [PATCH 046/344] Use existing enum instead of my own --- osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs | 5 +++-- osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs | 12 ------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs index dc85fabbe0..f6c47d757f 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -5,6 +5,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public PercentageInfoLine AccuracyDisplay; public InfoLine RankDisplay; - public InfoLine GradeDisplay; + public InfoLine GradeDisplay; public InfoContainer() { @@ -40,7 +41,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { AccuracyDisplay = new PercentageInfoLine(@"Accuracy"), RankDisplay = new InfoLine(@"Rank", @"#"), - GradeDisplay = new InfoLine(@"Grade"), + GradeDisplay = new InfoLine(@"Grade"), }, } }; diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs index 0af766b321..089dad6c38 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs @@ -74,16 +74,4 @@ namespace osu.Game.Screens.Play.BreaksOverlay protected override string Format(double count) => $@"{count:P2}"; } - - public enum Grade - { - SSplus, - SS, - Splus, - S, - A, - B, - C, - F - } } From 0615f375e1b206a55eb36fe0f396127cb0a10be2 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 23 Sep 2017 19:52:44 +0300 Subject: [PATCH 047/344] Show current grade --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 1 + osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs | 5 ++--- osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs | 10 ++++++++++ 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index deb87e92d8..e8dd87a6a6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Scoring Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; } - private ScoreRank rankFrom(double acc) + public static ScoreRank RankFrom(double acc) { if (acc == 1) return ScoreRank.X; @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Scoring score.Combo = Combo; score.MaxCombo = HighestCombo; score.Accuracy = Accuracy; - score.Rank = rankFrom(Accuracy); + score.Rank = RankFrom(Accuracy); score.Date = DateTimeOffset.Now; score.Health = Health; } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index e9dd112557..33fcebf3bb 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay public void BindProcessor(ScoreProcessor processor) { info.AccuracyDisplay.Current.BindTo(processor.Accuracy); + info.GradeDisplay.Current.BindTo(processor.Accuracy); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs index f6c47d757f..4cff9372a3 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -13,7 +12,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public PercentageInfoLine AccuracyDisplay; public InfoLine RankDisplay; - public InfoLine GradeDisplay; + public GradeInfoLine GradeDisplay; public InfoContainer() { @@ -41,7 +40,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { AccuracyDisplay = new PercentageInfoLine(@"Accuracy"), RankDisplay = new InfoLine(@"Rank", @"#"), - GradeDisplay = new InfoLine(@"Grade"), + GradeDisplay = new GradeInfoLine(@"Grade"), }, } }; diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs index 089dad6c38..bf486e2480 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -74,4 +75,13 @@ namespace osu.Game.Screens.Play.BreaksOverlay protected override string Format(double count) => $@"{count:P2}"; } + + public class GradeInfoLine : InfoLine + { + public GradeInfoLine(string name, string prefix = "") : base(name, prefix) + { + } + + protected override string Format(double count) => $@"{ScoreProcessor.RankFrom(count)}"; + } } From 94269e119e3838914c5ab5c9f7ec4d65594d7553 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 23 Sep 2017 19:59:34 +0300 Subject: [PATCH 048/344] Reset text only if it has been changed --- osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs index bf486e2480..f74329ceb3 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs @@ -54,7 +54,12 @@ namespace osu.Game.Screens.Play.BreaksOverlay private void currentValueChanged(T newValue) { - valueText.Text = prefix + Format(newValue); + var newText = prefix + Format(newValue); + + if (valueText.Text == newText) + return; + + valueText.Text = newText; } protected virtual string Format(T count) => count.ToString(); From 1f2a82b7ab3b3dba01bf1a44e3fe5e4b15e31db8 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Tue, 26 Sep 2017 12:21:00 +0200 Subject: [PATCH 049/344] make PreviewPlaying readonly instead of abstract --- osu.Game/Overlays/Direct/DirectGridPanel.cs | 1 - osu.Game/Overlays/Direct/DirectListPanel.cs | 1 - osu.Game/Overlays/Direct/DirectPanel.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 24ccd8b7eb..3c51235fad 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -27,7 +27,6 @@ namespace osu.Game.Overlays.Direct private Box progressBar; protected override PlayButton PlayButton => playButton; - public override Bindable PreviewPlaying { get; } = new Bindable(); public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 7112e927bd..3aa8c8a7c2 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -35,7 +35,6 @@ namespace osu.Game.Overlays.Direct private Box progressBar; protected override PlayButton PlayButton => playButton; - public override Bindable PreviewPlaying { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(LocalisationEngine localisation, OsuColour colours) diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 24cd8dc54e..65808c32a1 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Direct private BeatmapManager beatmaps; private NotificationOverlay notifications; - public abstract Bindable PreviewPlaying { get; } + public readonly Bindable PreviewPlaying = new Bindable(); protected abstract PlayButton PlayButton { get; } protected override Container Content => content; From c696f7457883c11e8c771096e6bb36a1efd11ec6 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 27 Sep 2017 01:10:48 +0300 Subject: [PATCH 050/344] Fix broken testcase and apply suggested changes --- .../Screens/Play/BreaksOverlay/ArrowsOverlay.cs | 4 ++-- .../Screens/Play/BreaksOverlay/BreakOverlay.cs | 2 +- .../Screens/Play/BreaksOverlay/InfoContainer.cs | 8 ++++---- .../Play/BreaksOverlay/LetterboxOverlay.cs | 16 ++++++++-------- .../Play/BreaksOverlay/RemainingTimeCounter.cs | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs index 87dc8caf13..9d729826de 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/ArrowsOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, - X = - glow_icon_offscreen_offset, + X = -glow_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_right, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), @@ -59,7 +59,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay Anchor = Anchor.Centre, Origin = Anchor.CentreRight, Alpha = 0.7f, - X = - blurred_icon_offscreen_offset, + X = -blurred_icon_offscreen_offset, Icon = Graphics.FontAwesome.fa_chevron_right, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 33fcebf3bb..90321df9d6 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay .Then() .ResizeWidthTo(0, b.Duration - fade_duration); - Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime); + Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); remainingTimeCounter.FadeIn(fade_duration); info.FadeIn(fade_duration); diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs index 4cff9372a3..8d2bec06aa 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = @"current progress".ToUpper(), + Text = "current progress".ToUpper(), TextSize = 15, Font = "Exo2.0-Black", }, @@ -38,9 +38,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay Direction = FillDirection.Vertical, Children = new Drawable[] { - AccuracyDisplay = new PercentageInfoLine(@"Accuracy"), - RankDisplay = new InfoLine(@"Rank", @"#"), - GradeDisplay = new GradeInfoLine(@"Grade"), + AccuracyDisplay = new PercentageInfoLine("Accuracy"), + RankDisplay = new InfoLine("Rank", @"#"), + GradeDisplay = new GradeInfoLine("Grade"), }, } }; diff --git a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs index c95b15ef3a..1581c45c56 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs @@ -11,9 +11,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public class LetterboxOverlay : Container { - private const int letterbox_height = 350; + private const int height = 350; - private Color4 transparentBlack => new Color4(0, 0, 0, 0); + private static readonly Color4 transparent_black = new Color4(0, 0, 0, 0); public LetterboxOverlay() { @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay Anchor = Anchor.TopLeft, Origin = Anchor.TopLeft, RelativeSizeAxes = Axes.X, - Height = letterbox_height, + Height = height, Child = new Box { RelativeSizeAxes = Axes.Both, @@ -34,8 +34,8 @@ namespace osu.Game.Screens.Play.BreaksOverlay { TopLeft = Color4.Black, TopRight = Color4.Black, - BottomLeft = transparentBlack, - BottomRight = transparentBlack, + BottomLeft = transparent_black, + BottomRight = transparent_black, } } }, @@ -44,14 +44,14 @@ namespace osu.Game.Screens.Play.BreaksOverlay Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.X, - Height = letterbox_height, + Height = height, Child = new Box { RelativeSizeAxes = Axes.Both, Colour = new ColourInfo { - TopLeft = transparentBlack, - TopRight = transparentBlack, + TopLeft = transparent_black, + TopRight = transparent_black, BottomLeft = Color4.Black, BottomRight = Color4.Black, } diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs index ab6ee5ea5c..e89c1e292c 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay private int? previousSecond; - private double remainingTime; + private double endTime; private bool isCounting; @@ -31,9 +31,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay }; } - public void StartCounting(double remainingTime) + public void StartCounting(double endTime) { - this.remainingTime = remainingTime; + this.endTime = endTime; isCounting = true; } @@ -44,9 +44,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay if (isCounting) { var currentTime = Clock.CurrentTime; - if (currentTime < remainingTime) + if (currentTime < endTime) { - int currentSecond = (int)Math.Floor((remainingTime - Clock.CurrentTime) / 1000.0); + int currentSecond = (int)Math.Floor((endTime - Clock.CurrentTime) / 1000.0); if (currentSecond != previousSecond) { counter.Text = currentSecond.ToString(); From 708632bca84655c894e24da82a225112a9727f14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 10:06:36 +0800 Subject: [PATCH 051/344] Remove second unnecessary colour set --- .../Edit/Components/Timelines/Summary/Parts/MarkerPart.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index d4a1177c4f..290412d170 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -99,9 +99,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Red; } } } From eae29820c069324333e37024315e7699ecc4da1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 11:06:33 +0800 Subject: [PATCH 052/344] Fix marker being cleared each beatmap change --- .../Components/Timelines/Summary/Parts/BookmarkPart.cs | 1 + .../Edit/Components/Timelines/Summary/Parts/BreakPart.cs | 1 + .../Components/Timelines/Summary/Parts/ControlPointPart.cs | 2 ++ .../Edit/Components/Timelines/Summary/Parts/MarkerPart.cs | 7 ++++++- .../Components/Timelines/Summary/Parts/TimelinePart.cs | 2 +- 5 files changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 8afec62a08..1793cb4334 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -15,6 +15,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { protected override void LoadBeatmap(WorkingBeatmap beatmap) { + base.LoadBeatmap(beatmap); foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) Add(new BookmarkVisualisation(bookmark)); } 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 721825270b..004491d489 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -16,6 +16,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) 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 e7f4f03f9b..d230578e13 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { protected override void LoadBeatmap(WorkingBeatmap beatmap) { + base.LoadBeatmap(beatmap); + ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; cpi.TimingPoints.ForEach(addTimingPoint); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 290412d170..228d32cee4 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts @@ -61,10 +62,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override void Update() { base.Update(); - marker.X = (float)(Beatmap.Value?.Track.CurrentTime ?? 0); } + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + // block base call so we don't clear our marker (can be reused on beatmap change). + } + private class MarkerVisualisation : CompositeDrawable { public MarkerVisualisation() diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index f5d4124b19..8071aa9c59 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -25,7 +25,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Beatmap.ValueChanged += b => { - timeline.Clear(); timeline.RelativeChildSize = new Vector2((float)Math.Max(1, b.Track.Length), 1); LoadBeatmap(b); }; @@ -35,6 +34,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected virtual void LoadBeatmap(WorkingBeatmap beatmap) { + timeline.Clear(); } } } From 7ad21d9a6d09ba31c90041264b60f5e61a7ab79d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 11:07:23 +0800 Subject: [PATCH 053/344] Simplify marker part construction --- .../Components/Timelines/Summary/Parts/MarkerPart.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 228d32cee4..2e7eaf99f8 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -17,17 +17,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// internal class MarkerPart : TimelinePart { - private readonly Drawable marker; - - public MarkerPart() - { - Add(marker = new MarkerVisualisation()); - } + private Drawable marker; [BackgroundDependencyLoader] private void load(OsuColour colours) { - marker.Colour = colours.Red; + Add(marker = new MarkerVisualisation + { + Colour = colours.Red + }); } protected override bool OnDragStart(InputState state) => true; From d5ed218488aac139909012bb0343a69edf357421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 11:07:43 +0800 Subject: [PATCH 054/344] Fix timeline sizes being updated potentially before the track has a length --- .../Timelines/Summary/Parts/TimelinePart.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 8071aa9c59..75651640d5 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -25,11 +25,31 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Beatmap.ValueChanged += b => { - timeline.RelativeChildSize = new Vector2((float)Math.Max(1, b.Track.Length), 1); + updateRelativeChildSize(); LoadBeatmap(b); }; } + private void updateRelativeChildSize() + { + if (!Beatmap.Value.TrackLoaded) + { + timeline.RelativeChildSize = Vector2.One; + return; + } + + var track = Beatmap.Value.Track; + + if (!track.IsLoaded) + { + // the track may not be loaded completely (only has a length once it is). + Schedule(updateRelativeChildSize); + return; + } + + timeline.RelativeChildSize = new Vector2((float)Math.Max(1, track.Length), 1); + } + protected void Add(Drawable visualisation) => timeline.Add(visualisation); protected virtual void LoadBeatmap(WorkingBeatmap beatmap) From 3018d32b13ec02cfb531ea38b9f237927105c564 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 11:22:47 +0800 Subject: [PATCH 055/344] Close BeatmapSetOverlay when clicking outside of it --- osu.Game/Overlays/BeatmapSetOverlay.cs | 3 +++ osu.Game/Overlays/UserProfileOverlay.cs | 1 + 2 files changed, 4 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 8e28ad33c5..7a4c6338a1 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -23,6 +23,9 @@ namespace osu.Game.Overlays private readonly Header header; private readonly Info info; + // receive input outside our bounds so we can trigger a close event on ourselves. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public BeatmapSetOverlay() { FirstWaveColour = OsuColour.Gray(0.4f); diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index f03ef3f1ed..088b0a1335 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays public const float CONTENT_X_MARGIN = 50; + // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; protected override bool OnClick(InputState state) From bbc990a6fd8be9ade98a74a7ad5c5bd19866660d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 11:26:56 +0800 Subject: [PATCH 056/344] Assign a name to individual import tests to avoid file contention --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index be48c997ea..9277310efc 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps.IO public void TestImportWhenClosed() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new HeadlessGameHost()) + using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenClosed")) { var osu = loadOsu(host); @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Beatmaps.IO public void TestImportWhenFileOpen() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new HeadlessGameHost()) + using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen")) { var osu = loadOsu(host); From 05c6829debe91a01c5a3b9ba0afe60b20f4a1773 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 12:18:24 +0800 Subject: [PATCH 057/344] Move all APIAccess State changes to the local thread Previously changes to the state were triggering events like Logout, which could get things into a bad state. --- osu.Game/Online/API/APIAccess.cs | 55 ++++++++++---------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 57f5c54a18..00abeea444 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -101,18 +101,16 @@ namespace osu.Game.Online.API } break; case APIState.Offline: + case APIState.Connecting: //work to restore a connection... if (!HasLogin) { - //OsuGame.Scheduler.Add(() => { OsuGame.ShowLogin(); }); - State = APIState.Offline; - Thread.Sleep(500); + Thread.Sleep(50); continue; } - if (State < APIState.Connecting) - State = APIState.Connecting; + State = APIState.Connecting; if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) { @@ -125,7 +123,8 @@ namespace osu.Game.Online.API var userReq = new GetUserRequest(); - userReq.Success += u => { + userReq.Success += u => + { LocalUser.Value = u; //we're connected! State = APIState.Online; @@ -133,16 +132,14 @@ namespace osu.Game.Online.API }; if (!handleRequest(userReq)) - { - State = APIState.Failing; continue; - } break; } //hard bail if we can't get a valid access token. if (authentication.RequestAccessToken() == null) { + Logout(false); State = APIState.Offline; continue; } @@ -162,20 +159,12 @@ namespace osu.Game.Online.API } } - private void clearCredentials() - { - Username = null; - Password = null; - } - public void Login(string username, string password) { Debug.Assert(State == APIState.Offline); Username = username; Password = password; - - State = APIState.Connecting; } /// @@ -204,7 +193,7 @@ namespace osu.Game.Online.API switch (statusCode) { case HttpStatusCode.Unauthorized: - State = APIState.Offline; + Logout(false); return true; case HttpStatusCode.RequestTimeout: failureCount++; @@ -215,6 +204,7 @@ namespace osu.Game.Online.API return false; State = APIState.Failing; + flushQueue(); return true; } @@ -242,26 +232,14 @@ namespace osu.Game.Online.API state = value; - switch (state) - { - case APIState.Failing: - case APIState.Offline: - flushQueue(); - break; - } - if (oldState != newState) { - //OsuGame.Scheduler.Add(delegate + log.Add($@"We just went {newState}!"); + Scheduler.Add(delegate { - //NotificationOverlay.ShowMessage($@"We just went {newState}!", newState == APIState.Online ? Color4.YellowGreen : Color4.OrangeRed, 5000); - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } + components.ForEach(c => c.APIStateChanged(this, newState)); + OnStateChange?.Invoke(oldState, newState); + }); } } } @@ -292,11 +270,12 @@ namespace osu.Game.Online.API } } - public void Logout() + public void Logout(bool clearUsername = true) { - clearCredentials(); + flushQueue(); + if (clearUsername) Username = null; + Password = null; authentication.Clear(); - State = APIState.Offline; LocalUser.Value = createGuestUser(); } From ec50834e98c2ac2a3ca3010fda4227a4555d2a89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 12:23:48 +0800 Subject: [PATCH 058/344] Load osu!direct overlay to "newest maps" tab by default --- osu.Game/Overlays/Direct/Header.cs | 2 +- osu.Game/Overlays/DirectOverlay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 2c50fb453f..77743a3a4b 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Direct public Header() { - Tabs.Current.Value = DirectTab.Search; + Tabs.Current.Value = DirectTab.NewestMaps; Tabs.Current.TriggerChange(); } } diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 9c07e1087f..5b5003b30f 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -251,7 +251,7 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; - getSetsRequest = new GetBeatmapSetsRequest(currentQuery, + getSetsRequest = new GetBeatmapSetsRequest(currentQuery.Value ?? string.Empty, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) From faad3fc7d39291f59a6feb679b585224bf9e82dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 12:40:32 +0800 Subject: [PATCH 059/344] Arbitrarily move colour assignment --- .../Timelines/Summary/Parts/MarkerPart.cs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 2e7eaf99f8..0bdd081907 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -17,15 +17,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// internal class MarkerPart : TimelinePart { - private Drawable marker; + private readonly Drawable marker; - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public MarkerPart() { - Add(marker = new MarkerVisualisation - { - Colour = colours.Red - }); + Add(marker = new MarkerVisualisation()); } protected override bool OnDragStart(InputState state) => true; @@ -102,6 +98,9 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Red; } } } From e64860ad45b44dfe27d36c1389d30af79e498e17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 12:46:34 +0800 Subject: [PATCH 060/344] Fix test case not working as expected --- .../Timelines/Summary/Parts/TimelinePart.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index 75651640d5..378ce78c67 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -32,22 +32,15 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private void updateRelativeChildSize() { - if (!Beatmap.Value.TrackLoaded) + // the track may not be loaded completely (only has a length once it is). + if (!Beatmap.Value.Track.IsLoaded) { timeline.RelativeChildSize = Vector2.One; - return; - } - - var track = Beatmap.Value.Track; - - if (!track.IsLoaded) - { - // the track may not be loaded completely (only has a length once it is). Schedule(updateRelativeChildSize); return; } - timeline.RelativeChildSize = new Vector2((float)Math.Max(1, track.Length), 1); + timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); } protected void Add(Drawable visualisation) => timeline.Add(visualisation); From 4a95d64239eb10b207604a9aaaa45a79c3024653 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 13:06:57 +0800 Subject: [PATCH 061/344] Fix yellow line in login overlay not following size correctly Also allows right click context menu to correctly extrude beyond the local masking. --- osu.Game/Overlays/LoginOverlay.cs | 49 ++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 58b259fcbb..0a47637589 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.General; using OpenTK.Graphics; @@ -28,35 +29,43 @@ namespace osu.Game.Overlays { Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, new OsuContextMenuContainer { Width = 360, AutoSizeAxes = Axes.Y, - Masking = true, - AutoSizeDuration = transition_time, - AutoSizeEasing = Easing.OutQuint, Children = new Drawable[] { - settingsSection = new LoginSettings - { - Padding = new MarginPadding(10), - RequestHide = Hide, - }, new Box { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = 3, - Colour = colours.Yellow, - Alpha = 1, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + AutoSizeDuration = transition_time, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + settingsSection = new LoginSettings + { + Padding = new MarginPadding(10), + RequestHide = Hide, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 3, + Colour = colours.Yellow, + Alpha = 1, + }, + } + } } } }; From a17cc04cdedc2455b19563977d3bf6b971de532e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 13:16:17 +0800 Subject: [PATCH 062/344] Make APIAccess's state only privately settable --- osu.Game/Online/API/APIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 00abeea444..bb72efb750 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -225,7 +225,7 @@ namespace osu.Game.Online.API public APIState State { get { return state; } - set + private set { APIState oldState = state; APIState newState = value; From 990ef3ca56d05cb424657545aa1770687ba63d4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Sep 2017 14:38:12 +0800 Subject: [PATCH 063/344] Make import tests more resilient to race condition failures Also centralises wait-or-assert logic. --- .../Beatmaps/IO/ImportBeatmapTest.cs | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 9277310efc..35bebf2d4f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(File.Exists(temp)); + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); } } @@ -61,14 +61,13 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(File.Exists(temp)); + waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); } } [Test] public void TestImportWhenFileOpen() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen")) { var osu = loadOsu(host); @@ -101,8 +100,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); - while (!osu.IsLoaded) - Thread.Sleep(1); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } @@ -113,30 +111,17 @@ namespace osu.Game.Tests.Beatmaps.IO var store = osu.Dependencies.Get(); - Action waitAction = () => - { - while (!(resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any()) - Thread.Sleep(50); - }; - - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), - @"BeatmapSet did not import to the database in allocated time."); + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + @"BeatmapSet did not import to the database in allocated time.", timeout); //ensure we were stored to beatmap database backing... - Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); IEnumerable resultBeatmaps = null; //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. - waitAction = () => - { - while ((resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12) - Thread.Sleep(50); - }; - - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), - @"Beatmaps did not import to the database in allocated time"); + waitForOrAssert(() => (resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() == 12, + @"Beatmaps did not import to the database in allocated time", timeout); var set = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526).First(); @@ -160,5 +145,11 @@ namespace osu.Game.Tests.Beatmaps.IO beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Action waitAction = () => { while (!result()) Thread.Sleep(20); }; + Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); + } } } From 545c375199703de17ccdecbd38b87427fb85bc71 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Sep 2017 21:53:33 +0900 Subject: [PATCH 064/344] Update design of EditorMenuBar to match flyte's design more closely --- osu-framework | 2 +- osu.Game/Screens/Edit/Editor.cs | 7 +- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 55 +++++++- .../Tests/Visual/TestCaseEditorMenuBar.cs | 125 ++++++++++-------- 4 files changed, 124 insertions(+), 65 deletions(-) diff --git a/osu-framework b/osu-framework index cdb031c3a8..a24fd77ad5 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit cdb031c3a8ef693cd71458c5e19c68127ab72938 +Subproject commit a24fd77ad5f69e133a84175ef79c4ab7f600316b diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3ffd7754c1..d85026bb27 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -32,16 +32,11 @@ namespace osu.Game.Screens.Edit Height = 40, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("111") - }, new EditorMenuBar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - X = 100, + RelativeSizeAxes = Axes.Both, Items = new[] { new EditorMenuBarItem("File") diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index bb349b1531..818eb37bfb 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -19,8 +19,20 @@ namespace osu.Game.Screens.Edit.Menus public EditorMenuBar() : base(Direction.Horizontal, true) { - ItemsContainer.Padding = new MarginPadding(0); + RelativeSizeAxes = Axes.X; + + ItemsContainer.Padding = new MarginPadding { Left = 100 }; BackgroundColour = Color4.Transparent; + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111"), + Depth = float.MaxValue + }, + }); } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); @@ -32,9 +44,32 @@ namespace osu.Game.Screens.Edit.Menus private Color4 openedForegroundColour; private Color4 openedBackgroundColour; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parentSizedBox.ReceiveMouseInputAt(screenSpacePos); + + /// + /// A box with width equal to this 's width, and height equal to the parent height. + /// + private readonly Box parentSizedBox; + public DrawableEditorBarMenuItem(MenuItem item) : base(item) { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + AddInternal(parentSizedBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + BypassAutoSizeAxes = Axes.Both, + Alpha = 0, + }); + } + + public override void SetFlowDirection(Direction direction) + { + AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -62,6 +97,13 @@ namespace osu.Game.Screens.Edit.Menus base.UpdateForegroundColour(); } + protected override void Update() + { + base.Update(); + + parentSizedBox.Height = Parent.DrawHeight; + } + protected override Drawable CreateBackground() => new Container { RelativeSizeAxes = Axes.Both, @@ -75,6 +117,17 @@ namespace osu.Game.Screens.Edit.Menus Child = new Box { RelativeSizeAxes = Axes.Both } } }; + + protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); + + private new class TextContainer : DrawableOsuMenuItem.TextContainer + { + public TextContainer() + { + NormalText.TextSize = BoldText.TextSize = 14; + NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; + } + } } private class SubMenu : OsuMenu diff --git a/osu.Game/Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game/Tests/Visual/TestCaseEditorMenuBar.cs index 8b5132fe08..b0c21b6b8c 100644 --- a/osu.Game/Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game/Tests/Visual/TestCaseEditorMenuBar.cs @@ -1,7 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Menus; @@ -9,74 +12,82 @@ namespace osu.Game.Tests.Visual { public class TestCaseEditorMenuBar : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) }; + public TestCaseEditorMenuBar() { - Add(new EditorMenuBar + Add(new Container { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = 50, Y = 50, - Items = new[] + Child = new EditorMenuBar { - new EditorMenuBarItem("File") + RelativeSizeAxes = Axes.Both, + Items = new[] { - Items = new[] + new EditorMenuBarItem("File") { - new EditorMenuItem("Clear All Notes"), - new EditorMenuItem("Open Difficulty..."), - new EditorMenuItem("Save"), - new EditorMenuItem("Create a new Difficulty..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Revert to Saved"), - new EditorMenuItem("Revert to Saved (Full)"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Test Beatmap"), - new EditorMenuItem("Open AiMod"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Upload Beatmap..."), - new EditorMenuItem("Export Package"), - new EditorMenuItem("Export Map Package"), - new EditorMenuItem("Import from..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Open Song Folder"), - new EditorMenuItem("Open .osu in Notepad"), - new EditorMenuItem("Open .osb in Notepad"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit"), - } - }, - new EditorMenuBarItem("Timing") - { - Items = new[] + Items = new[] + { + new EditorMenuItem("Clear All Notes"), + new EditorMenuItem("Open Difficulty..."), + new EditorMenuItem("Save"), + new EditorMenuItem("Create a new Difficulty..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Revert to Saved"), + new EditorMenuItem("Revert to Saved (Full)"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Test Beatmap"), + new EditorMenuItem("Open AiMod"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Upload Beatmap..."), + new EditorMenuItem("Export Package"), + new EditorMenuItem("Export Map Package"), + new EditorMenuItem("Import from..."), + new EditorMenuItemSpacer(), + new EditorMenuItem("Open Song Folder"), + new EditorMenuItem("Open .osu in Notepad"), + new EditorMenuItem("Open .osb in Notepad"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Exit"), + } + }, + new EditorMenuBarItem("Timing") { - new EditorMenuItem("Time Signature"), - new EditorMenuItem("Metronome Clicks"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Add Timing Section"), - new EditorMenuItem("Add Inheriting Section"), - new EditorMenuItem("Reset Current Section"), - new EditorMenuItem("Delete Timing Section"), - new EditorMenuItem("Resnap Current Section"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Timing Setup"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), - new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), - new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), - new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), - new EditorMenuItemSpacer(), - new EditorMenuItem("Set Current Position as Preview Point"), - } - }, - new EditorMenuBarItem("Testing") - { - Items = new[] + Items = new[] + { + new EditorMenuItem("Time Signature"), + new EditorMenuItem("Metronome Clicks"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Add Timing Section"), + new EditorMenuItem("Add Inheriting Section"), + new EditorMenuItem("Reset Current Section"), + new EditorMenuItem("Delete Timing Section"), + new EditorMenuItem("Resnap Current Section"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Timing Setup"), + new EditorMenuItemSpacer(), + new EditorMenuItem("Resnap All Notes", MenuItemType.Destructive), + new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), + new EditorMenuItem("Recalculate Slider Lengths", MenuItemType.Destructive), + new EditorMenuItem("Delete All Timing Sections", MenuItemType.Destructive), + new EditorMenuItemSpacer(), + new EditorMenuItem("Set Current Position as Preview Point"), + } + }, + new EditorMenuBarItem("Testing") { - new EditorMenuItem("Item 1"), - new EditorMenuItem("Item 2"), - new EditorMenuItem("Item 3"), - } - }, + Items = new[] + { + new EditorMenuItem("Item 1"), + new EditorMenuItem("Item 2"), + new EditorMenuItem("Item 3"), + } + }, + } } }); } From ba8bf6cbd51bb6a7a6c48a4aa1d03fb459372742 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Sep 2017 22:01:53 +0900 Subject: [PATCH 065/344] Add ScreenSelectionTabControl to EditorMenuBar --- .../Graphics/UserInterface/OsuTabControl.cs | 8 +- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 6 ++ .../Edit/Menus/ScreenSelectionTabControl.cs | 84 +++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 4 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 89b1f4124b..b053195030 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface public class OsuTabItem : TabItem, IHasAccentColour { protected readonly SpriteText Text; - private readonly Box box; + protected readonly Box Bar; private Color4 accentColour; public Color4 AccentColour @@ -77,13 +77,13 @@ namespace osu.Game.Graphics.UserInterface private void fadeActive() { - box.FadeIn(transition_length, Easing.OutQuint); + Bar.FadeIn(transition_length, Easing.OutQuint); Text.FadeColour(Color4.White, transition_length, Easing.OutQuint); } private void fadeInactive() { - box.FadeOut(transition_length, Easing.OutQuint); + Bar.FadeOut(transition_length, Easing.OutQuint); Text.FadeColour(AccentColour, transition_length, Easing.OutQuint); } @@ -123,7 +123,7 @@ namespace osu.Game.Graphics.UserInterface TextSize = 14, Font = @"Exo2.0-Bold", // Font should only turn bold when active? }, - box = new Box + Bar = new Box { RelativeSizeAxes = Axes.X, Height = 1, diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 818eb37bfb..a3b48d8c70 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -32,6 +32,12 @@ namespace osu.Game.Screens.Edit.Menus Colour = OsuColour.FromHex("111"), Depth = float.MaxValue }, + new ScreenSelectionTabControl + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -15 + } }); } diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs new file mode 100644 index 0000000000..a455fa8d71 --- /dev/null +++ b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Screens.Edit.Menus +{ + public class ScreenSelectionTabControl : OsuTabControl + { + public ScreenSelectionTabControl() + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + TabContainer.RelativeSizeAxes &= ~Axes.X; + TabContainer.AutoSizeAxes = Axes.X; + TabContainer.Padding = new MarginPadding(); + + Add(new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = Color4.White.Opacity(0.2f), + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(EditorScreenMode value) => new TabItem(value); + + private new class TabItem : OsuTabItem + { + private const float transition_length = 250; + + public TabItem(EditorScreenMode value) + : base(value) + { + Text.Margin = new MarginPadding(); + Text.Anchor = Anchor.CentreLeft; + Text.Origin = Anchor.CentreLeft; + } + + protected override void OnActivated() + { + base.OnActivated(); + Bar.ScaleTo(new Vector2(1, 5), transition_length, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + base.OnDeactivated(); + Bar.ScaleTo(Vector2.One, transition_length, Easing.OutQuint); + } + } + } + + public enum EditorScreenMode + { + [System.ComponentModel.Description("compose")] + Compose, + [System.ComponentModel.Description("design")] + Design, + [System.ComponentModel.Description("timing")] + Timing, + [System.ComponentModel.Description("song")] + SongSetup + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a6860e4e8c..0afb0247df 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -618,6 +618,7 @@ + From 95364d0173cdc6db99b3ada2f20cdab6f4e8221c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 27 Sep 2017 22:15:11 +0900 Subject: [PATCH 066/344] No more box background --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index a3b48d8c70..3142e3d6bc 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -22,16 +22,10 @@ namespace osu.Game.Screens.Edit.Menus RelativeSizeAxes = Axes.X; ItemsContainer.Padding = new MarginPadding { Left = 100 }; - BackgroundColour = Color4.Transparent; + BackgroundColour = OsuColour.FromHex("111"); AddRangeInternal(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("111"), - Depth = float.MaxValue - }, new ScreenSelectionTabControl { Anchor = Anchor.BottomRight, From 8688d63a9e23117dec10cf3bc19c06e51b56b42f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 Sep 2017 00:48:21 +0900 Subject: [PATCH 067/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index a24fd77ad5..9d142a8e00 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit a24fd77ad5f69e133a84175ef79c4ab7f600316b +Subproject commit 9d142a8e009794dfee828392e36025d08577131d From 4eaf6b4b94ac2850a15f1b9a775dba8201d7ebae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Sep 2017 00:08:42 +0800 Subject: [PATCH 068/344] Remove single usage of dynamic and stop referencing Microsoft.CSharp.dll --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 2 +- osu.Game/Rulesets/Scoring/Score.cs | 2 +- osu.Game/Screens/Ranking/ResultsPageScore.cs | 4 ++-- osu.Game/osu.Game.csproj | 1 - 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 13bd8d288d..537fce2548 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.API.Requests } [JsonProperty(@"statistics")] - private Dictionary jsonStats + private Dictionary jsonStats { set { diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 7f053ec1c5..c4ffa4e93c 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -38,6 +38,6 @@ namespace osu.Game.Rulesets.Scoring public DateTimeOffset Date; - public Dictionary Statistics = new Dictionary(); + public Dictionary Statistics = new Dictionary(); } } diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index bf406ff912..b01410cff5 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -186,9 +186,9 @@ namespace osu.Game.Screens.Ranking private class DrawableScoreStatistic : Container { - private readonly KeyValuePair statistic; + private readonly KeyValuePair statistic; - public DrawableScoreStatistic(KeyValuePair statistic) + public DrawableScoreStatistic(KeyValuePair statistic) { this.statistic = statistic; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a6860e4e8c..bdca48ccdf 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -124,7 +124,6 @@ $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll True From 75cd6eeb1c1b740cc62e713066e6d805f899bbe9 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 28 Sep 2017 14:20:19 +0300 Subject: [PATCH 069/344] Clean up Wave in WaveOverlayContainer --- osu.Game/Overlays/WaveOverlayContainer.cs | 37 +++-------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 4f9783a762..77c532350b 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Containers; @@ -165,10 +164,8 @@ namespace osu.Game.Overlays wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); } - private class Wave : Container, IStateful + private class Wave : VisibilityContainer { - public event Action StateChanged; - public float FinalPosition; public Wave() @@ -183,13 +180,7 @@ namespace osu.Game.Overlays Radius = 20f, }; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - }, - }; + Child = new Box { RelativeSizeAxes = Axes.Both }; } protected override void Update() @@ -201,28 +192,8 @@ namespace osu.Game.Overlays Height = Parent.Parent.DrawSize.Y * 1.5f; } - private Visibility state; - - public Visibility State - { - get { return state; } - set - { - state = value; - - switch (value) - { - case Visibility.Hidden: - this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); - break; - case Visibility.Visible: - this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - break; - } - - StateChanged?.Invoke(State); - } - } + protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); + protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); } } } From ca1f96208a00864209a8fa760a978fe75a217e0d Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Thu, 28 Sep 2017 14:31:02 +0300 Subject: [PATCH 070/344] Fix TestCaseContextMenu not being updated inline with previous changes --- osu.Game/Tests/Visual/TestCaseContextMenu.cs | 43 ++++++++++---------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseContextMenu.cs b/osu.Game/Tests/Visual/TestCaseContextMenu.cs index 28aae1f5b9..91a766f8c7 100644 --- a/osu.Game/Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game/Tests/Visual/TestCaseContextMenu.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { @@ -23,32 +24,32 @@ namespace osu.Game.Tests.Visual public TestCaseContextMenu() { - Add(container = new MyContextMenuContainer + Add(new OsuContextMenuContainer { - Size = new Vector2(200), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + container = new MyContextMenuContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - } - } - }); - - Add(new AnotherContextMenuContainer - { - Size = new Vector2(200), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new Box + Size = new Vector2(200), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + } + }, + new AnotherContextMenuContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Red, + Size = new Vector2(200), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + } } } }); From 6bb5210c7cf6c97ec96f2094e62af0f41aa74efd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Sep 2017 15:09:28 +0900 Subject: [PATCH 071/344] Remove the parentSizedBox --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 3142e3d6bc..329471fd0a 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -44,27 +44,12 @@ namespace osu.Game.Screens.Edit.Menus private Color4 openedForegroundColour; private Color4 openedBackgroundColour; - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parentSizedBox.ReceiveMouseInputAt(screenSpacePos); - - /// - /// A box with width equal to this 's width, and height equal to the parent height. - /// - private readonly Box parentSizedBox; public DrawableEditorBarMenuItem(MenuItem item) : base(item) { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; - - AddInternal(parentSizedBox = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - BypassAutoSizeAxes = Axes.Both, - Alpha = 0, - }); } public override void SetFlowDirection(Direction direction) @@ -100,8 +85,6 @@ namespace osu.Game.Screens.Edit.Menus protected override void Update() { base.Update(); - - parentSizedBox.Height = Parent.DrawHeight; } protected override Drawable CreateBackground() => new Container From b2eab1f4351ad2b84a19706c35835ae9f98aad71 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Sep 2017 15:09:56 +0900 Subject: [PATCH 072/344] Set the hover background colour as dictated by flyte --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 25 +++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 329471fd0a..488ba220a2 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -41,9 +41,6 @@ namespace osu.Game.Screens.Edit.Menus private class DrawableEditorBarMenuItem : DrawableOsuMenuItem { - private Color4 openedForegroundColour; - private Color4 openedBackgroundColour; - public DrawableEditorBarMenuItem(MenuItem item) : base(item) @@ -52,24 +49,24 @@ namespace osu.Game.Screens.Edit.Menus Origin = Anchor.CentreLeft; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ForegroundColour = colours.BlueLight; + BackgroundColour = Color4.Transparent; + ForegroundColourHover = Color4.White; + BackgroundColourHover = colours.Gray3; + } + public override void SetFlowDirection(Direction direction) { AutoSizeAxes = Axes.Both; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - ForegroundColour = ForegroundColourHover = colours.BlueLight; - BackgroundColour = BackgroundColourHover = Color4.Transparent; - openedForegroundColour = Color4.White; - openedBackgroundColour = colours.Gray3; - } - protected override void UpdateBackgroundColour() { if (State == MenuItemState.Selected) - Background.FadeColour(openedBackgroundColour); + Background.FadeColour(BackgroundColourHover); else base.UpdateBackgroundColour(); } @@ -77,7 +74,7 @@ namespace osu.Game.Screens.Edit.Menus protected override void UpdateForegroundColour() { if (State == MenuItemState.Selected) - Foreground.FadeColour(openedForegroundColour); + Foreground.FadeColour(ForegroundColourHover); else base.UpdateForegroundColour(); } From 775e8bada5e742a0ac62423fa1d61b6f3aa44047 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Sep 2017 15:10:24 +0900 Subject: [PATCH 073/344] Make the background bottom corners look nice with the new hover functionality --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 53 ++++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 488ba220a2..616cabbab2 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -41,12 +41,15 @@ namespace osu.Game.Screens.Edit.Menus private class DrawableEditorBarMenuItem : DrawableOsuMenuItem { + private BackgroundBox background; public DrawableEditorBarMenuItem(MenuItem item) : base(item) { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; + + StateChanged += stateChanged; } [BackgroundDependencyLoader] @@ -79,25 +82,15 @@ namespace osu.Game.Screens.Edit.Menus base.UpdateForegroundColour(); } - protected override void Update() + private void stateChanged(MenuItemState newState) { - base.Update(); + if (newState == MenuItemState.Selected) + background.Expand(); + else + background.Contract(); } - protected override Drawable CreateBackground() => new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 2, - Masking = true, - CornerRadius = 4, - Child = new Box { RelativeSizeAxes = Axes.Both } - } - }; - + protected override Drawable CreateBackground() => background = new BackgroundBox(); protected override DrawableOsuMenuItem.TextContainer CreateTextContainer() => new TextContainer(); private new class TextContainer : DrawableOsuMenuItem.TextContainer @@ -108,6 +101,34 @@ namespace osu.Game.Screens.Edit.Menus NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; } } + + private class BackgroundBox : CompositeDrawable + { + private readonly Container innerBackground; + + public BackgroundBox() + { + RelativeSizeAxes = Axes.Both; + Masking = true; + InternalChild = innerBackground = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + /// + /// Expands the background such that it doesn't show the bottom corners. + /// + public void Expand() => innerBackground.Height = 2; + + /// + /// Contracts the background such that it shows the bottom corners. + /// + public void Contract() => innerBackground.Height = 1; + } } private class SubMenu : OsuMenu From 7b4348254c47d60aeb76a7ff7b8c695f36da4564 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Sep 2017 20:02:55 +0900 Subject: [PATCH 074/344] Don't use new --- osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs index a455fa8d71..516b954e44 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Edit.Menus protected override TabItem CreateTabItem(EditorScreenMode value) => new TabItem(value); - private new class TabItem : OsuTabItem + private class TabItem : OsuTabItem { private const float transition_length = 250; From 21c6a63fa1bc50e4ec5caf11618fd892cefda8ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 29 Sep 2017 20:03:09 +0900 Subject: [PATCH 075/344] Use using for Description --- osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs index 516b954e44..652ef1d61f 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; +using System.ComponentModel; namespace osu.Game.Screens.Edit.Menus { @@ -72,13 +73,13 @@ namespace osu.Game.Screens.Edit.Menus public enum EditorScreenMode { - [System.ComponentModel.Description("compose")] + [Description("compose")] Compose, - [System.ComponentModel.Description("design")] + [Description("design")] Design, - [System.ComponentModel.Description("timing")] + [Description("timing")] Timing, - [System.ComponentModel.Description("song")] + [Description("song")] SongSetup } } From 40a27c810a7dabba9082475ed36aa0aa71872029 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 29 Sep 2017 19:24:14 +0800 Subject: [PATCH 076/344] Calculate SPM in spinner disc. --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 6577c7fd50..518fe188fb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -76,7 +76,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; + private double lastTime; public float RotationAbsolute; + public double SpinsPerMinute; private int completeTick; @@ -107,9 +109,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += thisAngle - lastAngle; RotationAbsolute += Math.Abs(thisAngle - lastAngle); + SpinsPerMinute = (thisAngle - lastAngle) / (Time.Current - lastTime) * 1000 * 60 / 360; } lastAngle = thisAngle; + lastTime = Time.Current; if (Complete && updateCompleteTick()) { From e2e26c91af5856851ab414186258683ce07d9b85 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Fri, 29 Sep 2017 22:30:41 +0800 Subject: [PATCH 077/344] Show SPM value basically. --- .../Objects/Drawables/DrawableSpinner.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 98dd40b0e6..c7e0353985 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Screens.Ranking; +using osu.Game.Graphics.Sprites; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -29,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container circleContainer; private readonly CirclePiece circle; private readonly GlowPiece glow; + private readonly OsuSpriteText spmText; private readonly SpriteIcon symbol; @@ -96,6 +98,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre, }, circleContainer.CreateProxy(), + spmText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + Text = @"0", + Font = @"Venera", + TextSize = 24, + Y = 120 + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + Text = @"SPINS PER MINUTE", + Font = @"Venera", + TextSize = 12, + Y = 125 + }, ticks = new SpinnerTicks { Anchor = Anchor.Centre, @@ -167,6 +187,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = disc.Rotation; ticks.Rotation = disc.Rotation; + spmText.Text = disc.SpinsPerMinute.ToString(@"#0"); float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); From 647304c14bd1b247c6ac1df2dce51bcb3339e0ca Mon Sep 17 00:00:00 2001 From: Jorolf Date: Fri, 29 Sep 2017 23:08:30 +0200 Subject: [PATCH 078/344] move logic to DirectPanel and reuse stuff for the PreviewButton --- osu.Game/Audio/AudioLoadWrapper.cs | 35 +++++ osu.Game/Overlays/BeatmapSet/PreviewButton.cs | 128 +++++------------- osu.Game/Overlays/Direct/DirectGridPanel.cs | 13 +- osu.Game/Overlays/Direct/DirectListPanel.cs | 14 +- osu.Game/Overlays/Direct/DirectPanel.cs | 53 ++++++++ osu.Game/Overlays/Direct/PlayButton.cs | 91 ++++++------- osu.Game/osu.Game.csproj | 1 + 7 files changed, 166 insertions(+), 169 deletions(-) create mode 100644 osu.Game/Audio/AudioLoadWrapper.cs diff --git a/osu.Game/Audio/AudioLoadWrapper.cs b/osu.Game/Audio/AudioLoadWrapper.cs new file mode 100644 index 0000000000..8c013cf70f --- /dev/null +++ b/osu.Game/Audio/AudioLoadWrapper.cs @@ -0,0 +1,35 @@ +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Audio +{ + public class AudioLoadWrapper : Drawable + { + private readonly string preview; + + public Track Preview; + + public AudioLoadWrapper(string preview) + { + this.preview = preview; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + if (!string.IsNullOrEmpty(preview)) + { + Preview = audio.Track.Get(preview); + Preview.Volume.Value = 0.5; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index bdb06106f0..9bb8b9a1a5 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -15,6 +15,9 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Game.Audio; +using osu.Game.Overlays.Direct; +using osu.Framework.Configuration; namespace osu.Game.Overlays.BeatmapSet { @@ -24,27 +27,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Container audioWrapper; private readonly Box bg, progress; - private readonly SpriteIcon icon; - private readonly LoadingAnimation loadingAnimation; + private readonly PlayButton playButton; private Track preview; - - private bool loading - { - set - { - if (value) - { - loadingAnimation.Show(); - icon.FadeOut(transition_duration * 5, Easing.OutQuint); - } - else - { - loadingAnimation.Hide(); - icon.FadeIn(transition_duration, Easing.OutQuint); - } - } - } + private readonly Bindable playing = new Bindable(); private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet @@ -55,42 +41,11 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - Playing = false; + playing.Value = false; preview = null; } } - private bool playing; - public bool Playing - { - get { return playing; } - set - { - if (value == playing) return; - playing = value; - - if (preview == null) - { - loading = true; - audioWrapper.Child = new AsyncLoadWrapper(new AudioLoadWrapper(BeatmapSet) - { - OnLoadComplete = d => - { - loading = false; - - preview = (d as AudioLoadWrapper)?.Preview; - Playing = Playing; - updatePlayingState(); - }, - }); - - return; - } - - updatePlayingState(); - } - } - public PreviewButton() { Height = 42; @@ -116,22 +71,16 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0f, }, }, - icon = new SpriteIcon + playButton = new PlayButton(playing) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_play, Size = new Vector2(18), - Shadow = false, - }, - loadingAnimation = new LoadingAnimation - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, }, }; - Action = () => Playing = !Playing; + Action = () => playing.Value = !playing.Value; + playing.ValueChanged += updatePlayingState; } [BackgroundDependencyLoader] @@ -144,12 +93,12 @@ namespace osu.Game.Overlays.BeatmapSet { base.Update(); - if (Playing && preview != null) + if (playing.Value && preview != null) { progress.Width = (float)(preview.CurrentTime / preview.Length); if (preview.HasCompleted) { - Playing = false; + playing.Value = false; preview = null; } } @@ -157,7 +106,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override void Dispose(bool isDisposing) { - Playing = false; + playing.Value = false; base.Dispose(isDisposing); } @@ -173,44 +122,35 @@ namespace osu.Game.Overlays.BeatmapSet base.OnHoverLost(state); } - private void updatePlayingState() + private void updatePlayingState(bool newValue) { - if (preview == null) return; - - if (Playing) + if (preview == null) { - icon.Icon = FontAwesome.fa_stop; - progress.FadeIn(100); + playButton.Loading = true; + audioWrapper.Child = new AsyncLoadWrapper(new AudioLoadWrapper(BeatmapSet.OnlineInfo.Preview) + { + OnLoadComplete = d => + { + playButton.Loading = false; - preview.Seek(0); - preview.Start(); + preview = (d as AudioLoadWrapper)?.Preview; + playing.TriggerChange(); + }, + }); } else { - icon.Icon = FontAwesome.fa_play; - progress.FadeOut(100); - preview.Stop(); - } - } - - private class AudioLoadWrapper : Drawable - { - private readonly string preview; - - public Track Preview; - - public AudioLoadWrapper(BeatmapSetInfo set) - { - preview = set.OnlineInfo.Preview; - } - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - if (!string.IsNullOrEmpty(preview)) + if (newValue) { - Preview = audio.Track.Get(preview); - Preview.Volume.Value = 0.5; + progress.FadeIn(100); + + preview.Seek(0); + preview.Start(); + } + else + { + progress.FadeOut(100); + preview.Stop(); } } } diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 8a26ae906d..3b8cd20200 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -26,6 +26,7 @@ namespace osu.Game.Overlays.Direct private Box progressBar; protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) { @@ -197,20 +198,8 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = 5, Left = 10 }, Size = new Vector2(30), Alpha = 0, - TrackUrl = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", }, }); - - PreviewPlaying.ValueChanged += newValue => playButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); - PreviewPlaying.ValueChanged += newValue => progressBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); - } - - protected override void Update() - { - base.Update(); - - if (PreviewPlaying && playButton.Track != null) - progressBar.Width = (float)(playButton.Track.CurrentTime / playButton.Track.Length); } } } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index a9d7993f7a..6a73c15ee6 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -14,6 +14,7 @@ using osu.Framework.Localisation; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Framework.Configuration; +using System; namespace osu.Game.Overlays.Direct { @@ -33,6 +34,7 @@ namespace osu.Game.Overlays.Direct private Box progressBar; protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; [BackgroundDependencyLoader] private void load(LocalisationEngine localisation, OsuColour colours) @@ -70,7 +72,6 @@ namespace osu.Game.Overlays.Direct Size = new Vector2(height / 2), FillMode = FillMode.Fit, Alpha = 0, - TrackUrl = "https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3", }, new FillFlowContainer { @@ -165,17 +166,6 @@ namespace osu.Game.Overlays.Direct Colour = colours.Yellow, }, }); - - PreviewPlaying.ValueChanged += newValue => playButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); - PreviewPlaying.ValueChanged += newValue => progressBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); - } - - protected override void Update() - { - base.Update(); - - if (PreviewPlaying && playButton.Track != null) - progressBar.Width = (float)(playButton.Track.CurrentTime / playButton.Track.Length); } } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index c4c20da297..6b1e06fd3a 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -21,6 +21,8 @@ using osu.Framework.Logging; using osu.Game.Overlays.Notifications; using osu.Game.Online.API.Requests; using osu.Framework.Configuration; +using osu.Framework.Audio.Track; +using osu.Game.Audio; namespace osu.Game.Overlays.Direct { @@ -39,9 +41,12 @@ namespace osu.Game.Overlays.Direct private BeatmapManager beatmaps; private NotificationOverlay notifications; private BeatmapSetOverlay beatmapSetOverlay; + private Container audioWrapper; + protected Track Preview; public readonly Bindable PreviewPlaying = new Bindable(); protected abstract PlayButton PlayButton { get; } + protected abstract Box PreviewBar { get; } protected override Container Content => content; @@ -82,6 +87,7 @@ namespace osu.Game.Overlays.Direct EdgeEffect = edgeEffectNormal, Children = new[] { + audioWrapper = new Container(), // temporary blackness until the actual background loads. BlackBackground = new Box { @@ -106,6 +112,53 @@ namespace osu.Game.Overlays.Direct if (downloadRequest != null) attachDownload(downloadRequest); + + PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += setPlaying; + } + + private void setPlaying(bool newValue) + { + if (newValue) + { + if(Preview == null) + { + PlayButton.Loading = true; + audioWrapper.Child = new AsyncLoadWrapper(new AudioLoadWrapper("https://b.ppy.sh/preview/" + SetInfo.OnlineBeatmapSetID + ".mp3") + { + OnLoadComplete = d => + { + PlayButton.Loading = false; + Preview = (d as AudioLoadWrapper)?.Preview; + Preview.Start(); + }, + }); + } + else + { + Preview.Start(); + } + } + else + { + Preview?.Stop(); + } + } + + protected override void Update() + { + base.Update(); + + if (PreviewPlaying && Preview != null) + { + PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); + if (Preview.HasCompleted) + { + PreviewPlaying.Value = false; + Preview = null; + } + } } protected override bool OnHover(InputState state) diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index b0d011a81c..34b0ace1c5 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -11,81 +11,70 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using System.Threading.Tasks; namespace osu.Game.Overlays.Direct { public class PlayButton : Container { - public string TrackUrl; - - public Bindable Playing; - - public Track Track; - private Bindable gameBeatmap; - private AudioManager audio; + private readonly Bindable playing; private Color4 hoverColour; private readonly SpriteIcon icon; + private readonly LoadingAnimation loadingAnimation; + private const float transition_duration = 500; + + private bool loading; + public bool Loading + { + get { return loading; } + set + { + loading = value; + if (value) + { + loadingAnimation.Show(); + icon.FadeOut(transition_duration * 5, Easing.OutQuint); + } + else + { + loadingAnimation.Hide(); + icon.FadeIn(transition_duration, Easing.OutQuint); + } + } + } public PlayButton(Bindable playing) { - Playing = playing; - Add(icon = new SpriteIcon + this.playing = playing; + AddRange(new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_play, + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Icon = FontAwesome.fa_play, + }, + loadingAnimation = new LoadingAnimation(), }); - Playing.ValueChanged += newValue => icon.Icon = newValue ? (Track == null ? FontAwesome.fa_spinner : FontAwesome.fa_pause) : FontAwesome.fa_play; + playing.ValueChanged += newValue => icon.Icon = newValue ? FontAwesome.fa_pause : FontAwesome.fa_play; - Playing.ValueChanged += newValue => - { - if (newValue) - Track?.Start(); - else - Track?.Stop(); - }; - - Playing.ValueChanged += newValue => icon.FadeColour(newValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + playing.ValueChanged += newValue => icon.FadeColour(newValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); } [BackgroundDependencyLoader] - private void load(OsuColour colour, OsuGameBase game, AudioManager audio) + private void load(OsuColour colour) { hoverColour = colour.Yellow; - gameBeatmap = game.Beatmap; - this.audio = audio; } - private Task loadTask; - protected override bool OnClick(InputState state) { - gameBeatmap.Value.Track.Stop(); - - Playing.Value = !Playing.Value; - - if (loadTask == null) - { - icon.Spin(2000, RotationDirection.Clockwise); - - loadTask = Task.Run(() => - { - Track = audio.Track.Get(TrackUrl); - Track.Looping = true; - if (Playing) - Track.Start(); - - icon.ClearTransforms(); - icon.Rotation = 0; - Playing.TriggerChange(); - }); - } - + playing.Value = !playing.Value; return true; } @@ -97,7 +86,7 @@ namespace osu.Game.Overlays.Direct protected override void OnHoverLost(InputState state) { - if(!Playing) + if(!playing.Value) icon.FadeColour(Color4.White, 120, Easing.InOutQuint); base.OnHoverLost(state); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 95a15c06ba..3d81c5a539 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -239,6 +239,7 @@ + From 26e7a3f157ced6e6fb0ab01f05b9ec2abe2ab862 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Fri, 29 Sep 2017 23:12:12 +0200 Subject: [PATCH 079/344] add license header --- osu.Game/Audio/AudioLoadWrapper.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Audio/AudioLoadWrapper.cs b/osu.Game/Audio/AudioLoadWrapper.cs index 8c013cf70f..69bf9d8147 100644 --- a/osu.Game/Audio/AudioLoadWrapper.cs +++ b/osu.Game/Audio/AudioLoadWrapper.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics; From 2457df8e18b88006b3ecae32dd1ab0ac970a79e1 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Fri, 29 Sep 2017 23:26:16 +0200 Subject: [PATCH 080/344] remove unused usings and reset the track to the start when playing again --- osu.Game/Audio/AudioLoadWrapper.cs | 5 ----- osu.Game/Overlays/BeatmapSet/PreviewButton.cs | 2 -- osu.Game/Overlays/Direct/DirectGridPanel.cs | 1 - osu.Game/Overlays/Direct/DirectListPanel.cs | 2 -- osu.Game/Overlays/Direct/DirectPanel.cs | 3 ++- osu.Game/Overlays/Direct/PlayButton.cs | 4 ---- 6 files changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game/Audio/AudioLoadWrapper.cs b/osu.Game/Audio/AudioLoadWrapper.cs index 69bf9d8147..efdde48564 100644 --- a/osu.Game/Audio/AudioLoadWrapper.cs +++ b/osu.Game/Audio/AudioLoadWrapper.cs @@ -6,11 +6,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Game.Beatmaps; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Game.Audio { diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index 9bb8b9a1a5..ea49963f67 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,7 +11,6 @@ using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; using osu.Game.Audio; diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 3b8cd20200..10611cd5be 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -12,7 +12,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Framework.Configuration; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 6a73c15ee6..7f5fa3e3f0 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -13,8 +13,6 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; -using osu.Framework.Configuration; -using System; namespace osu.Game.Overlays.Direct { diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 6b1e06fd3a..2195fc9ce7 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -131,12 +131,13 @@ namespace osu.Game.Overlays.Direct { PlayButton.Loading = false; Preview = (d as AudioLoadWrapper)?.Preview; - Preview.Start(); + PreviewPlaying.TriggerChange(); }, }); } else { + Preview.Seek(0); Preview.Start(); } } diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 34b0ace1c5..0405049c6a 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -3,16 +3,12 @@ using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -using System.Threading.Tasks; namespace osu.Game.Overlays.Direct { From 59247bcf1eeef982a1ab442afcef0c3afa055561 Mon Sep 17 00:00:00 2001 From: Jorolf Date: Fri, 29 Sep 2017 23:31:42 +0200 Subject: [PATCH 081/344] another unused using --- osu.Game/Audio/AudioLoadWrapper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Audio/AudioLoadWrapper.cs b/osu.Game/Audio/AudioLoadWrapper.cs index efdde48564..67836d1690 100644 --- a/osu.Game/Audio/AudioLoadWrapper.cs +++ b/osu.Game/Audio/AudioLoadWrapper.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics; -using osu.Game.Beatmaps; namespace osu.Game.Audio { From 92c3d722b4140020088fa8a471b6e24a2b8ad0b0 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sat, 30 Sep 2017 05:41:32 +0300 Subject: [PATCH 082/344] Show mapper's profile when clicking on avatar in BeatmapSetOverlay --- osu.Game/OsuGame.cs | 6 ++--- osu.Game/Overlays/BeatmapSet/AuthorInfo.cs | 28 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c137b8f6f5..75a1d61371 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -188,11 +188,11 @@ namespace osu.Game GetToolbarHeight = () => ToolbarOffset, Depth = -1 }, overlayContent.Add); - LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); LoadComponentAsync(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -2 }, mainContent.Add); + LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -3 }, mainContent.Add); LoadComponentAsync(musicController = new MusicController { - Depth = -3, + Depth = -4, Position = new Vector2(0, Toolbar.HEIGHT), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -200,7 +200,7 @@ namespace osu.Game LoadComponentAsync(notificationOverlay = new NotificationOverlay { - Depth = -3, + Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, }, overlayContent.Add); diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index b0e4e49e31..fc9fd1e614 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -9,6 +9,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Users; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.BeatmapSet { @@ -17,8 +20,11 @@ namespace osu.Game.Overlays.BeatmapSet private const float height = 50; private readonly UpdateableAvatar avatar; + private readonly ClickableArea clickableArea; private readonly FillFlowContainer fields; + private UserProfileOverlay profile; + private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet { @@ -31,6 +37,8 @@ namespace osu.Game.Overlays.BeatmapSet var i = BeatmapSet.OnlineInfo; avatar.User = i.Author; + clickableArea.Action = () => profile?.ShowUser(avatar.User); + fields.Children = new Drawable[] { new Field("made by", i.Author.Username, @"Exo2.0-RegularItalic"), @@ -58,11 +66,15 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { - avatar = new UpdateableAvatar + clickableArea = new ClickableArea { - Size = new Vector2(height), + AutoSizeAxes = Axes.Both, CornerRadius = 3, Masking = true, + Child = avatar = new UpdateableAvatar + { + Size = new Vector2(height), + }, EdgeEffect = new EdgeEffectParameters { Colour = Color4.Black.Opacity(0.25f), @@ -80,6 +92,13 @@ namespace osu.Game.Overlays.BeatmapSet }; } + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile) + { + this.profile = profile; + clickableArea.Action = () => profile?.ShowUser(avatar.User); + } + private class Field : FillFlowContainer { public Field(string first, string second, string secondFont) @@ -103,5 +122,10 @@ namespace osu.Game.Overlays.BeatmapSet }; } } + + private class ClickableArea : OsuClickableContainer, IHasTooltip + { + public string TooltipText => @"View Profile"; + } } } From 3de42ee4050f8b08d7c0db3796e4d6913097dc9a Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 30 Sep 2017 15:23:10 +0800 Subject: [PATCH 083/344] Smooth spm values into a time range. --- .../Objects/Drawables/DrawableSpinner.cs | 3 ++- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 20 ++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index c7e0353985..6b9e91d4f4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -187,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = disc.Rotation; ticks.Rotation = disc.Rotation; - spmText.Text = disc.SpinsPerMinute.ToString(@"#0"); + spmText.Text = Math.Truncate(disc.SpinsPerMinute).ToString(@"#0"); float relativeCircleScale = spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 518fe188fb..04bbd8b871 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -76,9 +77,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - private double lastTime; public float RotationAbsolute; public double SpinsPerMinute; + private readonly Queue rotations = new Queue(); + private readonly Queue times = new Queue(); + private const double spm_count_duration = 595; // not using hundreds to avoid frame rounding issues private int completeTick; @@ -109,11 +112,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += thisAngle - lastAngle; RotationAbsolute += Math.Abs(thisAngle - lastAngle); - SpinsPerMinute = (thisAngle - lastAngle) / (Time.Current - lastTime) * 1000 * 60 / 360; + if (rotations.Count > 0) + { + float rotationFrom = rotations.Peek(); + double timeFrom = times.Peek(); + while (Time.Current - times.Peek() > spm_count_duration) + { + rotationFrom = rotations.Dequeue(); + timeFrom = times.Dequeue(); + } + SpinsPerMinute = (currentRotation - rotationFrom) / (Time.Current - timeFrom) * 1000 * 60 / 360; + } } lastAngle = thisAngle; - lastTime = Time.Current; + rotations.Enqueue(currentRotation); + times.Enqueue(Time.Current); if (Complete && updateCompleteTick()) { From 5e751ea0ec38aabdf24bb80be772ee88eaf5274e Mon Sep 17 00:00:00 2001 From: "Franc[e]sco" Date: Sun, 1 Oct 2017 00:15:23 +0200 Subject: [PATCH 084/344] Only convert chords into strong hits for mania -> taiko --- .../Beatmaps/TaikoBeatmapConverter.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 4f2707ff88..ceaecbb555 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -55,14 +55,17 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Beatmap converted = base.ConvertBeatmap(original); - // Post processing step to transform hit objects with the same start time into strong hits - converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => + if (original.BeatmapInfo.RulesetID == 3) { - TaikoHitObject first = x.First(); - if (x.Skip(1).Any()) - first.IsStrong = true; - return first; - }).ToList(); + // Post processing step to transform mania hit objects with the same start time into strong hits + converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => + { + TaikoHitObject first = x.First(); + if (x.Skip(1).Any()) + first.IsStrong = true; + return first; + }).ToList(); + } return converted; } From b62f2437acc92d24d2b7f334cf53a3d9201fc84a Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Sun, 1 Oct 2017 23:38:11 +0300 Subject: [PATCH 085/344] Don't allow counter became 0 --- osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs index e89c1e292c..c8b8b6a072 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/RemainingTimeCounter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay var currentTime = Clock.CurrentTime; if (currentTime < endTime) { - int currentSecond = (int)Math.Floor((endTime - Clock.CurrentTime) / 1000.0); + int currentSecond = (int)Math.Ceiling((endTime - Clock.CurrentTime) / 1000.0); if (currentSecond != previousSecond) { counter.Text = currentSecond.ToString(); From ac6c323f93ef73786845e3384675c91a664e1fb0 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Mon, 2 Oct 2017 00:44:57 +0300 Subject: [PATCH 086/344] Clear all tasks and transforms when resetting Breaks --- .../Play/BreaksOverlay/BreakOverlay.cs | 36 +++++++++++++++---- osu.Game/Tests/Visual/TestCaseBreakOverlay.cs | 4 --- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 90321df9d6..fe4d5ea424 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -5,9 +5,11 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -17,7 +19,21 @@ namespace osu.Game.Screens.Play.BreaksOverlay private const float remaining_time_container_max_size = 0.3f; private const int vertical_margin = 25; - public List Breaks; + private List breaks; + public List Breaks + { + set + { + breaks = value; + initializeBreaks(); + } + get + { + return breaks; + } + } + + private readonly List tasks = new List(); private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; @@ -71,15 +87,21 @@ namespace osu.Game.Screens.Play.BreaksOverlay protected override void LoadComplete() { base.LoadComplete(); - InitializeBreaks(); + initializeBreaks(); } - public void InitializeBreaks() + private void initializeBreaks() { - if (Breaks == null) + FinishTransforms(true); + + foreach (var t in tasks) + t.Cancel(); + tasks.Clear(); + + if (breaks == null) return; - foreach (var b in Breaks) + foreach (var b in breaks) { if (!b.HasEffect) continue; @@ -104,7 +126,9 @@ namespace osu.Game.Screens.Play.BreaksOverlay .Then() .ResizeWidthTo(0, b.Duration - fade_duration); - Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); + tasks.Add(new ScheduledDelegate(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime)); + Scheduler.Add(tasks.Last()); + remainingTimeCounter.FadeIn(fade_duration); info.FadeIn(fade_duration); diff --git a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs index 055a2bc5c4..074aa16934 100644 --- a/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBreakOverlay.cs @@ -37,8 +37,6 @@ namespace osu.Game.Tests.Visual EndTime = Clock.CurrentTime + duration, } }; - - breakOverlay.InitializeBreaks(); } private void startMultipleBreaks() @@ -58,8 +56,6 @@ namespace osu.Game.Tests.Visual EndTime = currentTime + 6000, } }; - - breakOverlay.InitializeBreaks(); } } } \ No newline at end of file From 2a9edcbb4f0754563d484f3e8acd2f0d9ea38c37 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 08:10:16 +0900 Subject: [PATCH 087/344] Add test case for the editor --- osu.Game/Tests/Visual/TestCaseEditor.cs | 47 +++++++++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 2 files changed, 48 insertions(+) create mode 100644 osu.Game/Tests/Visual/TestCaseEditor.cs diff --git a/osu.Game/Tests/Visual/TestCaseEditor.cs b/osu.Game/Tests/Visual/TestCaseEditor.cs new file mode 100644 index 0000000000..6114d1eb2e --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseEditor.cs @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Screens.Edit; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditor : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Editor) }; + + private readonly Random rng; + + private BeatmapManager beatmaps; + private OsuGameBase osuGame; + + public TestCaseEditor() + { + rng = new Random(1337); + + Add(new Editor()); + AddStep("Next beatmap", nextBeatmap); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame, BeatmapManager beatmaps) + { + this.osuGame = osuGame; + this.beatmaps = beatmaps; + } + + private void nextBeatmap() + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count == 0) + return; + + BeatmapInfo info = sets[rng.Next(0, sets.Count)].Beatmaps[0]; + osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(info); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c8e42a3ad3..9dfc2401ae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -742,6 +742,7 @@ + From 8ed23a60e97601226c80a00f7a5fa36e1cdaa683 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:10:40 +0900 Subject: [PATCH 088/344] Remove items from the editor menu bar --- osu.Game/Screens/Edit/Editor.cs | 146 +------------------------------- 1 file changed, 1 insertion(+), 145 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d85026bb27..6395e6c3b1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -36,151 +36,7 @@ namespace osu.Game.Screens.Edit { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Items = new[] - { - new EditorMenuBarItem("File") - { - Items = new[] - { - new EditorMenuItem("Clear all notes"), - new EditorMenuItem("Open difficulty..."), - new EditorMenuItem("Save"), - new EditorMenuItem("Create new difficulty..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Revert to saved"), - new EditorMenuItem("Revert to saved (full"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Test beatmap"), - new EditorMenuItem("Open AiMod"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Upload Beatmap..."), - new EditorMenuItem("Export package"), - new EditorMenuItem("Export map package"), - new EditorMenuItem("Import from..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Open song folder"), - new EditorMenuItem("Open .osu in Notepad"), - new EditorMenuItem("Open .osb in Notepad"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Exit) - } - }, - new EditorMenuBarItem("Edit") - { - Items = new[] - { - new EditorMenuItem("Undo"), - new EditorMenuItem("Redo"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Cut"), - new EditorMenuItem("Copy"), - new EditorMenuItem("Paste"), - new EditorMenuItem("Delete"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Select all"), - new EditorMenuItem("Clone"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Reverse selection"), - new EditorMenuItem("Flip horizontally"), - new EditorMenuItem("Flip vertically"), - new EditorMenuItem("Rotate 90deg clockwise"), - new EditorMenuItem("Rotate 90deg anticlockwise"), - new EditorMenuItem("Rotate by..."), - new EditorMenuItem("Scale by..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Reset selected objects' samples"), - new EditorMenuItem("Reset all samples", MenuItemType.Destructive), - new EditorMenuItem("Reset combo colours", MenuItemType.Destructive), - new EditorMenuItem("Reset breaks", MenuItemType.Destructive), - new EditorMenuItemSpacer(), - new EditorMenuItem("Nudge backward"), - new EditorMenuItem("Nudge forward") - } - }, - new EditorMenuBarItem("View") - { - Items = new[] - { - new EditorMenuItem("Compose"), - new EditorMenuItem("Design"), - new EditorMenuItem("Timing"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Song setup..."), - new EditorMenuItem("Timing setup..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Volume"), - new EditorMenuItem("Grid level"), - new EditorMenuItem("Show video"), - new EditorMenuItem("Show sample name"), - new EditorMenuItem("Snaking sliders"), - new EditorMenuItem("Hit animations"), - new EditorMenuItem("Follow points"), - new EditorMenuItem("Stacking") - } - }, - new EditorMenuBarItem("Compose") - { - Items = new[] - { - new EditorMenuItem("Snap divisor"), - new EditorMenuItem("Audio rate"), - new EditorMenuItem("Grid snapping"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Create polygon cricles..."), - new EditorMenuItem("Convert slider to stream"), - new EditorMenuItem("Enable live mapping mode"), - new EditorMenuItem("Sample import") - } - }, - new EditorMenuBarItem("Design") - { - Items = new[] - { - new EditorMenuItem("Move all elements in time...") - } - }, - new EditorMenuBarItem("Timing") - { - Items = new[] - { - new EditorMenuItem("Time signature"), - new EditorMenuItem("Metronome clicks"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Add timing section"), - new EditorMenuItem("Add inheriting section"), - new EditorMenuItem("Reset current section"), - new EditorMenuItem("Delete timing section"), - new EditorMenuItem("Resnap current section"), - new EditorMenuItemSpacer(), - new EditorMenuItem("Timing setup..."), - new EditorMenuItemSpacer(), - new EditorMenuItem("Resnap all notes", MenuItemType.Destructive), - new EditorMenuItem("Move all notes in time...", MenuItemType.Destructive), - new EditorMenuItem("Recalculate slider lengths", MenuItemType.Destructive), - new EditorMenuItem("Delete all timing sections", MenuItemType.Destructive), - new EditorMenuItemSpacer(), - new EditorMenuItem("Set current position as preview point") - } - }, - new EditorMenuBarItem("Web") - { - Items = new[] - { - new EditorMenuItem("This Beatmap's information page"), - new EditorMenuItem("This Beatmap's thread"), - new EditorMenuItem("Quick reply") - } - }, - new EditorMenuBarItem("Help") - { - Items = new[] - { - new EditorMenuItem("Show in-game help"), - new EditorMenuItem("View FAQ") - } - } - } + RelativeSizeAxes = Axes.Both } } }); From 7b1ef53f36fc60877bfacbe729ef72e71942a316 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:24:25 +0900 Subject: [PATCH 089/344] Expose a mode change event from EditorMenuBar --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 616cabbab2..4c9b3c84b3 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -11,11 +11,19 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using System; namespace osu.Game.Screens.Edit.Menus { public class EditorMenuBar : OsuMenu { + /// + /// Invaoked when the selected mode has changed. + /// + public event Action ModeChanged; + + private readonly ScreenSelectionTabControl tabControl; + public EditorMenuBar() : base(Direction.Horizontal, true) { @@ -26,13 +34,21 @@ namespace osu.Game.Screens.Edit.Menus AddRangeInternal(new Drawable[] { - new ScreenSelectionTabControl + tabControl = new ScreenSelectionTabControl { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, X = -15 } }); + + tabControl.Current.ValueChanged += v => ModeChanged?.Invoke(v); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + tabControl.Current.TriggerChange(); } protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); From 221902f4fefe2c94e0d546b0ecab4065bc198f33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:26:16 +0900 Subject: [PATCH 090/344] Restructure Editor construction --- osu.Game/Screens/Edit/Editor.cs | 68 +++++++++++++++++---------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6395e6c3b1..8b6c0f0bf5 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -26,57 +26,61 @@ namespace osu.Game.Screens.Edit public Editor() { - Add(new Container + EditorMenuBar menuBar; + SummaryTimeline timeline; + + Children = new[] { - RelativeSizeAxes = Axes.X, - Height = 40, - Children = new Drawable[] + new Container { - new EditorMenuBar + Name = "Top bar", + RelativeSizeAxes = Axes.X, + Height = 40, + Child = menuBar = new EditorMenuBar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both } - } - }); - - SummaryTimeline summaryTimeline; - Add(new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 60, - Children = new Drawable[] + }, + new Container { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - new Container + Name = "Bottom bar", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 10 }, - Child = new FillFlowContainer + bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + new Container { - Name = "Bottom bar", RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new[] + Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 10 }, + Child = new FillFlowContainer { - summaryTimeline = new SummaryTimeline + Name = "Bottom bar", + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.65f + timeline = new SummaryTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.65f + } } } } } + }, } - }); + }; - summaryTimeline.Beatmap.BindTo(Beatmap); + timeline.Beatmap.BindTo(Beatmap); } [BackgroundDependencyLoader] From c2d63eb0bd9f239084f12de5c51aabab9e7477d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:26:41 +0900 Subject: [PATCH 091/344] Add EditorScreen + screen changing functionality --- osu.Game/Screens/Edit/Editor.cs | 24 +++++++++++ osu.Game/Screens/Edit/Screens/EditorScreen.cs | 40 +++++++++++++++++++ osu.Game/Tests/Visual/TestCaseEditor.cs | 3 +- osu.Game/osu.Game.csproj | 1 + 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/Edit/Screens/EditorScreen.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8b6c0f0bf5..7f5ad0aa0a 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -13,6 +13,7 @@ using osu.Game.Screens.Edit.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using OpenTK; using osu.Framework.Allocation; +using osu.Game.Screens.Edit.Screens; namespace osu.Game.Screens.Edit { @@ -23,6 +24,9 @@ namespace osu.Game.Screens.Edit internal override bool ShowOverlays => false; private readonly Box bottomBackground; + private readonly Container modeContainer; + + private EditorScreen currentScreen; public Editor() { @@ -77,10 +81,16 @@ namespace osu.Game.Screens.Edit } } }, + modeContainer = new Container + { + Name = "Screen container", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 40, Bottom = 60 } } }; timeline.Beatmap.BindTo(Beatmap); + menuBar.ModeChanged += onModeChanged; } [BackgroundDependencyLoader] @@ -89,6 +99,20 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + private void onModeChanged(EditorScreenMode mode) + { + currentScreen?.Exit(); + + switch (mode) + { + default: + currentScreen = new EditorScreen(); + break; + } + + modeContainer.Add(currentScreen); + } + protected override void OnResuming(Screen last) { Beatmap.Value.Track?.Stop(); diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs new file mode 100644 index 0000000000..49921a0025 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -0,0 +1,40 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; + +namespace osu.Game.Screens.Edit.Screens +{ + public class EditorScreen : CompositeDrawable + { + private readonly Container content; + + public EditorScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + + InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + this.ScaleTo(0.5f).FadeTo(0) + .Then() + .ScaleTo(1f, 500, Easing.OutQuint).FadeTo(1f, 500, Easing.OutQuint); + } + + public void Exit() + { + this.ScaleTo(1.5f, 500).FadeOut(500f).Expire(); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseEditor.cs b/osu.Game/Tests/Visual/TestCaseEditor.cs index 6114d1eb2e..6f7d05d299 100644 --- a/osu.Game/Tests/Visual/TestCaseEditor.cs +++ b/osu.Game/Tests/Visual/TestCaseEditor.cs @@ -7,12 +7,13 @@ using osu.Framework.Allocation; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Screens; namespace osu.Game.Tests.Visual { public class TestCaseEditor : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(Editor) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; private readonly Random rng; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9dfc2401ae..94bd4862af 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -618,6 +618,7 @@ + From 2a310f02bcf0326f81d996ec72cd3076b1488436 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:27:27 +0900 Subject: [PATCH 092/344] modeContainer -> screenContainer --- osu.Game/Screens/Edit/Editor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7f5ad0aa0a..33b1f04cba 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit internal override bool ShowOverlays => false; private readonly Box bottomBackground; - private readonly Container modeContainer; + private readonly Container screenContainer; private EditorScreen currentScreen; @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit } } }, - modeContainer = new Container + screenContainer = new Container { Name = "Screen container", RelativeSizeAxes = Axes.Both, @@ -110,7 +110,7 @@ namespace osu.Game.Screens.Edit break; } - modeContainer.Add(currentScreen); + screenContainer.Add(currentScreen); } protected override void OnResuming(Screen last) From 6018b4e5e4b606e0c7fd65720153f7e6bfbaa34c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 09:34:51 +0900 Subject: [PATCH 093/344] Make EditorScreen a Container --- osu.Game/Screens/Edit/Screens/EditorScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index 49921a0025..ecc7ee1e7c 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -10,8 +10,9 @@ using osu.Framework.MathUtils; namespace osu.Game.Screens.Edit.Screens { - public class EditorScreen : CompositeDrawable + public class EditorScreen : Container { + protected override Container Content => content; private readonly Container content; public EditorScreen() From 91cede79c28e763110e0657f1108494f4144b953 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 10:07:34 +0900 Subject: [PATCH 094/344] Mask the editor screens --- osu.Game/Screens/Edit/Editor.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 33b1f04cba..05443420ef 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -81,11 +81,16 @@ namespace osu.Game.Screens.Edit } } }, - screenContainer = new Container + new Container { Name = "Screen container", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, Bottom = 60 } + Padding = new MarginPadding { Top = 40, Bottom = 60 }, + Child = screenContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true + } } }; From af4c6276e46b82b7a6779d2424b2dcad4284323e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 10:09:10 +0900 Subject: [PATCH 095/344] Adjust transforms of EditorScreen --- osu.Game/Screens/Edit/Screens/EditorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index ecc7ee1e7c..8c4f6e7dbe 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -30,12 +30,12 @@ namespace osu.Game.Screens.Edit.Screens this.ScaleTo(0.5f).FadeTo(0) .Then() - .ScaleTo(1f, 500, Easing.OutQuint).FadeTo(1f, 500, Easing.OutQuint); + .ScaleTo(1f, 500, Easing.OutQuint).FadeTo(1f, 250, Easing.OutQuint); } public void Exit() { - this.ScaleTo(1.5f, 500).FadeOut(500f).Expire(); + this.ScaleTo(1.5f, 500).FadeOut(250).Expire(); } } } From 69b61a62a53ac1a23c1f5dafc1264fce51294a99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 10:09:21 +0900 Subject: [PATCH 096/344] Implement base Compose screen --- osu.Game/Screens/Edit/Editor.cs | 4 ++ .../Screens/Edit/Screens/Compose/Compose.cs | 42 +++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 3 files changed, 47 insertions(+) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/Compose.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 05443420ef..06b2a6de64 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -14,6 +14,7 @@ using osu.Game.Screens.Edit.Components.Timelines.Summary; using OpenTK; using osu.Framework.Allocation; using osu.Game.Screens.Edit.Screens; +using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Screens.Edit { @@ -110,6 +111,9 @@ namespace osu.Game.Screens.Edit switch (mode) { + case EditorScreenMode.Compose: + currentScreen = new Compose(); + break; default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs new file mode 100644 index 0000000000..6da7fc900d --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -0,0 +1,42 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class Compose : EditorScreen + { + public Compose() + { + Children = new[] + { + new Container + { + Name = "Timeline", + RelativeSizeAxes = Axes.X, + Height = 110, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f) + }, + new Container + { + Name = "Content", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 17, Vertical = 10 } + } + } + } + }; + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 94bd4862af..9a8536bbc2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -619,6 +619,7 @@ + From 7211dd201546ae7293971431bd9c8a067137d56b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Oct 2017 10:11:43 +0900 Subject: [PATCH 097/344] Remove unnecessary usings --- osu.Game/Screens/Edit/Editor.cs | 1 - osu.Game/Screens/Edit/Screens/Compose/Compose.cs | 1 - osu.Game/Screens/Edit/Screens/EditorScreen.cs | 4 ---- osu.Game/Tests/Visual/TestCaseEditor.cs | 1 - 4 files changed, 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 06b2a6de64..8a11a50fd6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Menus; using osu.Game.Screens.Edit.Components.Timelines.Summary; using OpenTK; diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index 6da7fc900d..2fe40dd010 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -6,7 +6,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Screens.Compose { diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index 8c4f6e7dbe..c456ad6a51 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -1,12 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.MathUtils; namespace osu.Game.Screens.Edit.Screens { diff --git a/osu.Game/Tests/Visual/TestCaseEditor.cs b/osu.Game/Tests/Visual/TestCaseEditor.cs index 6f7d05d299..6da5e514b2 100644 --- a/osu.Game/Tests/Visual/TestCaseEditor.cs +++ b/osu.Game/Tests/Visual/TestCaseEditor.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Screens; From 5ce2723719f6697c03125142a5b4c66d808008c8 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Mon, 2 Oct 2017 05:12:56 +0300 Subject: [PATCH 098/344] Don't use linq query --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index fe4d5ea424..c7c5afe42c 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Threading; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; using System.Collections.Generic; -using System.Linq; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -127,7 +126,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay .ResizeWidthTo(0, b.Duration - fade_duration); tasks.Add(new ScheduledDelegate(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime)); - Scheduler.Add(tasks.Last()); + Scheduler.Add(tasks[tasks.Count - 1]); remainingTimeCounter.FadeIn(fade_duration); From c2f487aa3edb9012fd97b58e8d5ae1b0cc8e4013 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Mon, 2 Oct 2017 05:56:38 +0300 Subject: [PATCH 099/344] Add Rank as a property to the Score Processor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 11 +++++++++-- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 2 +- osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs | 5 +++-- osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs | 10 ---------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e8dd87a6a6..0b631a7148 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -51,6 +51,11 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt Combo = new BindableInt(); + /// + /// The current rank. + /// + public readonly Bindable Rank = new Bindable(ScoreRank.X); + /// /// THe highest combo achieved by this score. /// @@ -74,9 +79,10 @@ namespace osu.Game.Rulesets.Scoring protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; + Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; } - public static ScoreRank RankFrom(double acc) + private ScoreRank rankFrom(double acc) { if (acc == 1) return ScoreRank.X; @@ -101,6 +107,7 @@ namespace osu.Game.Rulesets.Scoring Accuracy.Value = 1; Health.Value = 1; Combo.Value = 0; + Rank.Value = ScoreRank.X; HighestCombo.Value = 0; alreadyFailed = false; @@ -142,7 +149,7 @@ namespace osu.Game.Rulesets.Scoring score.Combo = Combo; score.MaxCombo = HighestCombo; score.Accuracy = Accuracy; - score.Rank = RankFrom(Accuracy); + score.Rank = Rank; score.Date = DateTimeOffset.Now; score.Health = Health; } diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index c7c5afe42c..53a2f4919e 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay public void BindProcessor(ScoreProcessor processor) { info.AccuracyDisplay.Current.BindTo(processor.Accuracy); - info.GradeDisplay.Current.BindTo(processor.Accuracy); + info.GradeDisplay.Current.BindTo(processor.Rank); } } } diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs index 8d2bec06aa..351dc66930 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoContainer.cs @@ -5,6 +5,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -12,7 +13,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { public PercentageInfoLine AccuracyDisplay; public InfoLine RankDisplay; - public GradeInfoLine GradeDisplay; + public InfoLine GradeDisplay; public InfoContainer() { @@ -40,7 +41,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay { AccuracyDisplay = new PercentageInfoLine("Accuracy"), RankDisplay = new InfoLine("Rank", @"#"), - GradeDisplay = new GradeInfoLine("Grade"), + GradeDisplay = new InfoLine("Grade"), }, } }; diff --git a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs index f74329ceb3..751523d68c 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/InfoLine.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.BreaksOverlay { @@ -80,13 +79,4 @@ namespace osu.Game.Screens.Play.BreaksOverlay protected override string Format(double count) => $@"{count:P2}"; } - - public class GradeInfoLine : InfoLine - { - public GradeInfoLine(string name, string prefix = "") : base(name, prefix) - { - } - - protected override string Format(double count) => $@"{ScoreProcessor.RankFrom(count)}"; - } } From 70524628635d5a17535bc4f4277261164b85750c Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Mon, 2 Oct 2017 08:51:00 +0300 Subject: [PATCH 100/344] Update inline with framework --- osu-framework | 2 +- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu-framework b/osu-framework index 9d142a8e00..5f19dd913d 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 9d142a8e009794dfee828392e36025d08577131d +Subproject commit 5f19dd913dfc69013a3b9cf30ccfd9c44881a321 diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 53a2f4919e..9ffaf906c8 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Threading; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Scoring; using System.Collections.Generic; @@ -32,8 +31,6 @@ namespace osu.Game.Screens.Play.BreaksOverlay } } - private readonly List tasks = new List(); - private readonly bool letterboxing; private readonly LetterboxOverlay letterboxOverlay; private readonly Container remainingTimeBox; @@ -92,10 +89,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay private void initializeBreaks() { FinishTransforms(true); - - foreach (var t in tasks) - t.Cancel(); - tasks.Clear(); + Scheduler.CancelDelayedTasks(); if (breaks == null) return; @@ -125,8 +119,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay .Then() .ResizeWidthTo(0, b.Duration - fade_duration); - tasks.Add(new ScheduledDelegate(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime)); - Scheduler.Add(tasks[tasks.Count - 1]); + Scheduler.AddDelayed(() => remainingTimeCounter.StartCounting(b.EndTime), b.StartTime - Clock.CurrentTime); remainingTimeCounter.FadeIn(fade_duration); From b6ed977e1e383a388b57f557e707cff546ee56f0 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Mon, 2 Oct 2017 09:04:03 +0300 Subject: [PATCH 101/344] Fix hard crash and fix breaks have been initialized twice --- osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs | 6 ------ osu.Game/Screens/Play/Player.cs | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs index 9ffaf906c8..3b2335a455 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/BreakOverlay.cs @@ -80,12 +80,6 @@ namespace osu.Game.Screens.Play.BreaksOverlay }; } - protected override void LoadComplete() - { - base.LoadComplete(); - initializeBreaks(); - } - private void initializeBreaks() { FinishTransforms(true); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 79ace6c45a..589f4b663a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -192,8 +192,8 @@ namespace osu.Game.Screens.Play { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Breaks = beatmap.Breaks, - Clock = decoupledClock + Clock = decoupledClock, + Breaks = beatmap.Breaks }, } }, From 66afba62190f56c698678a4fb4ad03e280ed8318 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2017 17:38:48 +0800 Subject: [PATCH 102/344] Allow TestCasePlayer to instantiate only one ruleset type --- .../Tests/TestCaseCatchPlayer.cs | 4 +++ osu.Game/Rulesets/RulesetStore.cs | 1 - osu.Game/Tests/Visual/TestCasePlayer.cs | 31 ++++++++++++------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs index 8d18a712d8..45ab8ac300 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs @@ -10,6 +10,10 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer { + public TestCaseCatchPlayer() : base(typeof(CatchRuleset)) + { + + } protected override Beatmap CreateBeatmap() { var beatmap = new Beatmap(); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5eef4a8470..407a5f7c3c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -38,7 +38,6 @@ namespace osu.Game.Rulesets protected override void Prepare(bool reset = false) { - Connection.CreateTable(); if (reset) diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 4a25a52e36..b0953ceb7e 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.IO; using System.Linq; using System.Text; @@ -18,31 +19,39 @@ namespace osu.Game.Tests.Visual { public class TestCasePlayer : OsuTestCase { + private readonly Type ruleset; + protected Player Player; - private RulesetStore rulesets; public override string Description => @"Showing everything to play the game."; + /// + /// Create a TestCase which runs through the Player screen. + /// + /// An optional ruleset type which we want to target. If not provided we'll allow all rulesets to be tested. + protected TestCasePlayer(Type ruleset) + { + this.ruleset = ruleset; + } + + public TestCasePlayer() + { + + } + [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - this.rulesets = rulesets; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Add(new Box { RelativeSizeAxes = Framework.Graphics.Axes.Both, Colour = Color4.Black, }); - foreach (var r in rulesets.Query()) - AddStep(r.Name, () => loadPlayerFor(r)); + string instantiation = ruleset?.AssemblyQualifiedName; - loadPlayerFor(rulesets.Query().First()); + foreach (var r in rulesets.Query(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) + AddStep(r.Name, () => loadPlayerFor(r)); } protected virtual Beatmap CreateBeatmap() From 12a9cbad56b84960226b21204ee2e6453d67d9a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2017 21:54:26 +0800 Subject: [PATCH 103/344] Allow Beatmap to populate some metadata defaults if they aren't provided via BetamapInfo --- osu.Game/Beatmaps/Beatmap.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 458c2304f2..383a331eb4 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -58,6 +58,23 @@ namespace osu.Game.Beatmaps ComboColors = original?.ComboColors ?? ComboColors; HitObjects = original?.HitObjects ?? HitObjects; Storyboard = original?.Storyboard ?? Storyboard; + + 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", + Author = @"Unknown Creator", + }, + Version = @"Normal", + Difficulty = new BeatmapDifficulty() + }; + } } } From 7168629b2a28f59a121a844b45f481b138e3df0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2017 21:55:37 +0800 Subject: [PATCH 104/344] Remove CatcherArea abstraction Also fixes catcher size being relative to aspect ratio. --- .../Tests/TestCaseCatcher.cs | 4 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 34 ++- osu.Game.Rulesets.Catch/UI/Catcher.cs | 193 +++++++++++++++ osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 229 ------------------ .../osu.Game.Rulesets.Catch.csproj | 2 +- 5 files changed, 225 insertions(+), 237 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/Catcher.cs delete mode 100644 osu.Game.Rulesets.Catch/UI/CatcherArea.cs diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs index 6a065e197d..21a9bdec51 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcher.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public override IReadOnlyList RequiredTypes => new[] { - typeof(CatcherArea), + typeof(Catcher), }; [BackgroundDependencyLoader] @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests new CatchInputManager(rulesets.GetRuleset(2)) { RelativeSizeAxes = Axes.Both, - Child = new CatcherArea + Child = new Catcher { RelativePositionAxes = Axes.Both, RelativeSizeAxes = Axes.Both, diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2b6f9bbf5a..38f3273055 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -1,10 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Game.Rulesets.UI; using OpenTK; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -15,11 +17,15 @@ namespace osu.Game.Rulesets.Catch.UI { protected override Container Content => content; private readonly Container content; - private readonly CatcherArea catcherArea; + + private readonly Container catcherContainer; + private readonly Catcher catcher; public CatchPlayfield() : base(Axes.Y) { + Container explodingFruitContainer; + Reversed.Value = true; Size = new Vector2(1); @@ -33,16 +39,34 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, }, - catcherArea = new CatcherArea + explodingFruitContainer = new Container { RelativeSizeAxes = Axes.Both, + }, + catcherContainer = new Container { + RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, - Height = 0.3f + Height = 180, + Child = catcher = new Catcher + { + ExplodingFruitTarget = explodingFruitContainer, + RelativePositionAxes = Axes.Both, + Origin = Anchor.TopCentre, + X = 0.5f, + } } }; } + protected override void Update() + { + base.Update(); + catcher.Size = new Vector2(catcherContainer.DrawSize.Y); + } + + public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2; + public override void Add(DrawableHitObject h) { h.Depth = (float)h.HitObject.StartTime; @@ -50,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.UI base.Add(h); var fruit = (DrawableFruit)h; - fruit.CheckPosition = catcherArea.CheckIfWeCanCatch; + fruit.CheckPosition = CheckIfWeCanCatch; } public override void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) @@ -59,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.UI { Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre; Remove(judgedObject); - catcherArea.Add(judgedObject, screenPosition); + catcher.Add(judgedObject, screenPosition); } } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs new file mode 100644 index 0000000000..87fe95ed2f --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -0,0 +1,193 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.MathUtils; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class Catcher : Container, IKeyBindingHandler + { + private Texture texture; + + private Container caughtFruit; + + public Container ExplodingFruitTarget; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); + + Children = new Drawable[] + { + createCatcherSprite(), + caughtFruit = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + } + }; + } + + private int currentDirection; + + private bool dashing; + + protected bool Dashing + { + get { return dashing; } + set + { + if (value == dashing) return; + + dashing = value; + + if (dashing) + Schedule(addAdditiveSprite); + } + } + + private void addAdditiveSprite() + { + if (!dashing) return; + + var additive = createCatcherSprite(); + + additive.RelativePositionAxes = Axes.Both; + additive.Blending = BlendingMode.Additive; + additive.Position = Position; + additive.Scale = Scale; + + ((Container)Parent).Add(additive); + + additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); + + Scheduler.AddDelayed(addAdditiveSprite, 50); + } + + private Sprite createCatcherSprite() => new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = texture, + OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly. + }; + + public bool OnPressed(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection--; + return true; + case CatchAction.MoveRight: + currentDirection++; + return true; + case CatchAction.Dash: + Dashing = true; + return true; + } + + return false; + } + + public bool OnReleased(CatchAction action) + { + switch (action) + { + case CatchAction.MoveLeft: + currentDirection++; + return true; + case CatchAction.MoveRight: + currentDirection--; + return true; + case CatchAction.Dash: + Dashing = false; + return true; + } + + return false; + } + + /// + /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. + /// + private const double base_speed = 1.0 / 512; + + protected override void Update() + { + base.Update(); + + if (currentDirection == 0) return; + + double dashModifier = Dashing ? 1 : 0.5; + + Scale = new Vector2(Math.Sign(currentDirection), 1); + X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1); + } + + public void Add(DrawableHitObject fruit, Vector2 absolutePosition) + { + fruit.RelativePositionAxes = Axes.None; + fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0); + + fruit.Anchor = Anchor.TopCentre; + fruit.Origin = Anchor.BottomCentre; + fruit.Scale *= 0.7f; + fruit.LifetimeEnd = double.MaxValue; + + float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; + + while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) + { + fruit.X += RNG.Next(-5, 5); + fruit.Y -= RNG.Next(0, 5); + } + + caughtFruit.Add(fruit); + + if (((CatchBaseHit)fruit.HitObject).LastInCombo) + explode(); + } + + private void explode() + { + var fruit = caughtFruit.ToArray(); + + foreach (var f in fruit) + { + var originalX = f.X * Scale.X; + + if (ExplodingFruitTarget != null) + { + f.Anchor = Anchor.TopLeft; + f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); + + caughtFruit.Remove(f); + + ExplodingFruitTarget.Add(f); + } + + f.MoveToY(f.Y - 50, 250, Easing.OutSine) + .Then() + .MoveToY(f.Y + 50, 500, Easing.InSine); + + f.MoveToX(f.X + originalX * 6, 1000); + f.FadeOut(750); + + f.Expire(); + } + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs deleted file mode 100644 index 2930dbb7cc..0000000000 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Framework.MathUtils; -using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Catch.UI -{ - public class CatcherArea : Container - { - private Catcher catcher; - private Container explodingFruitContainer; - - public void Add(DrawableHitObject fruit, Vector2 screenPosition) => catcher.AddToStack(fruit, screenPosition); - - public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - catcher = new Catcher - { - RelativePositionAxes = Axes.Both, - ExplodingFruitTarget = explodingFruitContainer, - Origin = Anchor.TopCentre, - X = 0.5f, - } - }; - } - - protected override void Update() - { - base.Update(); - - catcher.Size = new Vector2(DrawSize.Y); - } - - private class Catcher : Container, IKeyBindingHandler - { - private Texture texture; - - private Container caughtFruit; - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); - - Children = new Drawable[] - { - createCatcherSprite(), - caughtFruit = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - } - }; - } - - private int currentDirection; - - private bool dashing; - - public Container ExplodingFruitTarget; - - protected bool Dashing - { - get { return dashing; } - set - { - if (value == dashing) return; - - dashing = value; - - if (dashing) - Schedule(addAdditiveSprite); - } - } - - private void addAdditiveSprite() - { - if (!dashing) return; - - var additive = createCatcherSprite(); - - additive.RelativePositionAxes = Axes.Both; - additive.Blending = BlendingMode.Additive; - additive.Position = Position; - additive.Scale = Scale; - - ((CatcherArea)Parent).Add(additive); - - additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); - - Scheduler.AddDelayed(addAdditiveSprite, 50); - } - - private Sprite createCatcherSprite() => new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Texture = texture, - OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly. - }; - - public bool OnPressed(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection--; - return true; - case CatchAction.MoveRight: - currentDirection++; - return true; - case CatchAction.Dash: - Dashing = true; - return true; - } - - return false; - } - - public bool OnReleased(CatchAction action) - { - switch (action) - { - case CatchAction.MoveLeft: - currentDirection++; - return true; - case CatchAction.MoveRight: - currentDirection--; - return true; - case CatchAction.Dash: - Dashing = false; - return true; - } - - return false; - } - - /// - /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. - /// - private const double base_speed = 1.0 / 512; - - protected override void Update() - { - base.Update(); - - if (currentDirection == 0) return; - - double dashModifier = Dashing ? 1 : 0.5; - - Scale = new Vector2(Math.Sign(currentDirection), 1); - X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1); - } - - public void AddToStack(DrawableHitObject fruit, Vector2 absolutePosition) - { - fruit.RelativePositionAxes = Axes.None; - fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0); - - fruit.Anchor = Anchor.TopCentre; - fruit.Origin = Anchor.BottomCentre; - fruit.Scale *= 0.7f; - fruit.LifetimeEnd = double.MaxValue; - - float distance = fruit.DrawSize.X / 2 * fruit.Scale.X; - - while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance)) - { - fruit.X += RNG.Next(-5, 5); - fruit.Y -= RNG.Next(0, 5); - } - - caughtFruit.Add(fruit); - - if (((CatchBaseHit)fruit.HitObject).LastInCombo) - explode(); - } - - private void explode() - { - var fruit = caughtFruit.ToArray(); - - foreach (var f in fruit) - { - var originalX = f.X * Scale.X; - - if (ExplodingFruitTarget != null) - { - f.Anchor = Anchor.TopLeft; - f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); - - caughtFruit.Remove(f); - - ExplodingFruitTarget.Add(f); - } - - f.MoveToY(f.Y - 50, 250, Easing.OutSine) - .Then() - .MoveToY(f.Y + 50, 500, Easing.InSine); - - f.MoveToX(f.X + originalX * 6, 1000); - f.FadeOut(750); - - f.Expire(); - } - } - } - } -} diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index 787825d482..718ae32a17 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -59,7 +59,7 @@ - + From 3338024c17b6febbf5aa47acc41b021e1f25c83c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2017 22:12:53 +0800 Subject: [PATCH 105/344] Fix incorrect whitespace --- osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs index 45ab8ac300..5be07e94c0 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Catch.Tests { public TestCaseCatchPlayer() : base(typeof(CatchRuleset)) { - } + protected override Beatmap CreateBeatmap() { var beatmap = new Beatmap(); From 37393ab2c946bfb236e7ae0514efcf895ffcec4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Oct 2017 22:24:22 +0800 Subject: [PATCH 106/344] Move brace --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 38f3273055..c4033b562d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -43,7 +43,8 @@ namespace osu.Game.Rulesets.Catch.UI { RelativeSizeAxes = Axes.Both, }, - catcherContainer = new Container { + catcherContainer = new Container + { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Origin = Anchor.TopLeft, From cf44868bcdd090d0b43313545b9e0c5646b42e5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 15:02:11 +0900 Subject: [PATCH 107/344] Give EditorScreen a Beatmap --- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/Screens/EditorScreen.cs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8a11a50fd6..ca072ed73e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -118,6 +118,7 @@ namespace osu.Game.Screens.Edit break; } + currentScreen.Beatmap.BindTo(Beatmap); screenContainer.Add(currentScreen); } diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index c456ad6a51..509c61a530 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -1,13 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; namespace osu.Game.Screens.Edit.Screens { public class EditorScreen : Container { + public readonly Bindable Beatmap = new Bindable(); + protected override Container Content => content; private readonly Container content; From 1a7e3fa09e4d51db6a30dfe7975538fc1277b824 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Oct 2017 19:21:08 +0900 Subject: [PATCH 108/344] Initial implementation of a test case which showcases waveforms --- osu.Game/Tests/Visual/TestCaseWaveform.cs | 149 ++++++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 2 files changed, 150 insertions(+) create mode 100644 osu.Game/Tests/Visual/TestCaseWaveform.cs diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs new file mode 100644 index 0000000000..5f4e86fb92 --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -0,0 +1,149 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual +{ + internal class TestCaseWaveform : OsuTestCase + { + private readonly Bindable beatmapBacking = new Bindable(); + + private readonly List displays = new List(); + + public TestCaseWaveform() + { + MusicController mc; + FillFlowContainer flow; + Child = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + mc = new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 100, + State = Visibility.Visible + }, + } + }; + + for (int i = 1; i <= 16; i *= 2) + { + var newDisplay = new WaveformDisplay(i) { RelativeSizeAxes = Axes.Both }; + + displays.Add(newDisplay); + + flow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + newDisplay, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {(1f / i).ToString("0.00")}" + } + } + } + } + }); + } + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + beatmapBacking.BindTo(osuGame.Beatmap); + beatmapBacking.ValueChanged += b => b.Track.QueryWaveform(processWaveform); + } + + private void processWaveform(Waveform waveform) => Schedule(() => displays.ForEach(d => d.Display(waveform))); + + private class WaveformDisplay : CompositeDrawable + { + private readonly int resolution; + + public WaveformDisplay(int resolution) + { + this.resolution = resolution; + } + + public void Display(Waveform waveform) + { + ClearInternal(); + + var generated = waveform.Generate((int)MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) / resolution); + + for (int i = 0; i < generated.Count; i++) + { + var point = generated[i]; + + // Left channel + AddInternal(new NonInputBox + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + X = 1f / generated.Count * i, + Size = new Vector2(1f / generated.Count, point.Amplitude[0] / 2), + Colour = Color4.Red + }); + + if (waveform.Channels >= 2) + { + // Right channel + AddInternal(new NonInputBox + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + X = 1f / generated.Count * i, + Size = new Vector2(1f / generated.Count, point.Amplitude[1] / 2), + Colour = Color4.Green + }); + } + } + } + + private class NonInputBox : Box + { + public override bool HandleInput => false; + } + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9a8536bbc2..56490b6119 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -777,6 +777,7 @@ + From 319649f4465a45bc93c615d57ceb11ac36cd8460 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Oct 2017 21:19:38 +0900 Subject: [PATCH 109/344] Make TestCaseWaveform use a custom drawnode instead of boxes --- osu.Game/Tests/Visual/TestCaseWaveform.cs | 128 +++++++++++++++++----- 1 file changed, 101 insertions(+), 27 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs index 5f4e86fb92..3f71e74a8d 100644 --- a/osu.Game/Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -5,12 +5,19 @@ using System; using System.Collections.Generic; using OpenTK; using OpenTK.Graphics; +using OpenTK.Graphics.ES30; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; @@ -92,51 +99,118 @@ namespace osu.Game.Tests.Visual private void processWaveform(Waveform waveform) => Schedule(() => displays.ForEach(d => d.Display(waveform))); - private class WaveformDisplay : CompositeDrawable + private class WaveformDisplay : Drawable { + private List points; + private int channels; + + private Shader shader; + private readonly Texture texture; + private readonly int resolution; public WaveformDisplay(int resolution) { this.resolution = resolution; + + texture = Texture.WhitePixel; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } public void Display(Waveform waveform) { - ClearInternal(); + points = waveform.Generate((int)MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) / resolution); + channels = waveform.Channels; + Invalidate(Invalidation.DrawNode); + } - var generated = waveform.Generate((int)MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) / resolution); + protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); - for (int i = 0; i < generated.Count; i++) + private readonly WaveformDrawNodeSharedData sharedData = new WaveformDrawNodeSharedData(); + protected override void ApplyDrawNode(DrawNode node) + { + var n = (WaveformDrawNode)node; + + n.Shader = shader; + n.Texture = texture; + n.Points = points; + n.Channels = channels; + n.Size = DrawSize; + n.Shared = sharedData; + + + base.ApplyDrawNode(node); + } + + private class WaveformDrawNodeSharedData + { + public readonly QuadBatch VertexBatch = new QuadBatch(1000, 10); + } + + private class WaveformDrawNode : DrawNode + { + public Shader Shader; + public Texture Texture; + + public WaveformDrawNodeSharedData Shared; + + public List Points; + public Vector2 Size; + public int Channels; + + public override void Draw(Action vertexAction) { - var point = generated[i]; + base.Draw(vertexAction); - // Left channel - AddInternal(new NonInputBox - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.Both, - X = 1f / generated.Count * i, - Size = new Vector2(1f / generated.Count, point.Amplitude[0] / 2), - Colour = Color4.Red - }); + if (Points == null || Points.Count == 0) + return; - if (waveform.Channels >= 2) + Shader.Bind(); + Texture.TextureGL.Bind(); + + float separation = Size.X / (Points.Count - 1); + Vector2 localInflationAmount = new Vector2(0, 1) * DrawInfo.MatrixInverse.ExtractScale().Xy; + + for (int i = 0; i < Points.Count - 1; i++) { - // Right channel - AddInternal(new NonInputBox + ColourInfo colour = DrawInfo.Colour; + Quad quadToDraw; + + switch (Channels) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.Both, - X = 1f / generated.Count * i, - Size = new Vector2(1f / generated.Count, point.Amplitude[1] / 2), - Colour = Color4.Green - }); + default: + case 2: + { + float height = Size.Y / 2; + quadToDraw = new Quad( + new Vector2(i * separation, height - Points[i].Amplitude[0] * height), + new Vector2((i + 1) * separation, height - Points[i + 1].Amplitude[0] * height), + new Vector2(i * separation, height + Points[i].Amplitude[1] * height), + new Vector2((i + 1) * separation, height + Points[i + 1].Amplitude[1] * height) + ); + } + break; + case 1: + { + quadToDraw = new Quad( + new Vector2(i * separation, Size.Y - Points[i].Amplitude[0] * Size.Y), + new Vector2((i + 1) * separation, Size.Y - Points[i + 1].Amplitude[0] * Size.Y), + new Vector2(i * separation, Size.Y), + new Vector2((i + 1) * separation, Size.Y) + ); + break; + } + } + + Texture.DrawQuad(quadToDraw * DrawInfo.Matrix, colour, null, Shared.VertexBatch.Add, Vector2.Divide(localInflationAmount, quadToDraw.Size)); } + + Shader.Unbind(); } } From 3eeb36cbd42f3cfa4b7b448e9c4170af4a346601 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 3 Oct 2017 23:23:20 +0900 Subject: [PATCH 110/344] Remove now unused class --- osu.Game/Tests/Visual/TestCaseWaveform.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs index 3f71e74a8d..2966728fb5 100644 --- a/osu.Game/Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -143,7 +143,6 @@ namespace osu.Game.Tests.Visual n.Size = DrawSize; n.Shared = sharedData; - base.ApplyDrawNode(node); } @@ -213,11 +212,6 @@ namespace osu.Game.Tests.Visual Shader.Unbind(); } } - - private class NonInputBox : Box - { - public override bool HandleInput => false; - } } } } From a37b10d51220b5f0df864cd8e503696eb669a0b6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 14:42:22 +0900 Subject: [PATCH 111/344] Make TestCaseWaveform use invalidations + remove some of the crud --- osu.Game/Tests/Visual/TestCaseWaveform.cs | 69 +++++++++++++++-------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs index 2966728fb5..4df7ffe367 100644 --- a/osu.Game/Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -28,8 +28,6 @@ namespace osu.Game.Tests.Visual { private readonly Bindable beatmapBacking = new Bindable(); - private readonly List displays = new List(); - public TestCaseWaveform() { MusicController mc; @@ -53,9 +51,13 @@ namespace osu.Game.Tests.Visual for (int i = 1; i <= 16; i *= 2) { - var newDisplay = new WaveformDisplay(i) { RelativeSizeAxes = Axes.Both }; + var newDisplay = new WaveformDisplay + { + RelativeSizeAxes = Axes.Both, + Resolution = 1f / i + }; - displays.Add(newDisplay); + newDisplay.Beatmap.BindTo(beatmapBacking); flow.Add(new Container { @@ -91,29 +93,21 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - beatmapBacking.BindTo(osuGame.Beatmap); - beatmapBacking.ValueChanged += b => b.Track.QueryWaveform(processWaveform); - } - - private void processWaveform(Waveform waveform) => Schedule(() => displays.ForEach(d => d.Display(waveform))); + private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); private class WaveformDisplay : Drawable { - private List points; - private int channels; + public readonly Bindable Beatmap = new Bindable(); + + private Waveform waveform; private Shader shader; private readonly Texture texture; - private readonly int resolution; - - public WaveformDisplay(int resolution) + public WaveformDisplay() { - this.resolution = resolution; - texture = Texture.WhitePixel; + Beatmap.ValueChanged += generateWaveform; } [BackgroundDependencyLoader] @@ -122,26 +116,51 @@ namespace osu.Game.Tests.Visual shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } - public void Display(Waveform waveform) + private float resolution = 1; + public float Resolution { - points = waveform.Generate((int)MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) / resolution); - channels = waveform.Channels; - Invalidate(Invalidation.DrawNode); + get { return resolution; } + set + { + if (resolution == value) + return; + resolution = value; + + Invalidate(Invalidation.DrawNode); + } } - protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); + private Track lastQueriedTrack; + + private void generateWaveform(WorkingBeatmap beatmap) + { + // Cancel the old query so we don't saturate the audio thread + lastQueriedTrack?.CancelWaveformQuery(); + + beatmap.Track.QueryWaveform(w => + { + if (Beatmap.Value == beatmap) + { + waveform = w; + Invalidate(Invalidation.DrawNode); + } + }); + + lastQueriedTrack = beatmap.Track; + } private readonly WaveformDrawNodeSharedData sharedData = new WaveformDrawNodeSharedData(); + protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); protected override void ApplyDrawNode(DrawNode node) { var n = (WaveformDrawNode)node; n.Shader = shader; n.Texture = texture; - n.Points = points; - n.Channels = channels; n.Size = DrawSize; n.Shared = sharedData; + n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) * resolution)); + n.Channels = waveform.Channels; base.ApplyDrawNode(node); } From 01c839eda7dfeb6f68edf6b6a442e956f6715c60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 14:55:17 +0900 Subject: [PATCH 112/344] Move WaveformDisplay into separate class and add some commenting --- .../Edit/Screens/Compose/WaveformDisplay.cs | 168 ++++++++++++++++++ osu.Game/Tests/Visual/TestCaseWaveform.cs | 149 +--------------- osu.Game/osu.Game.csproj | 1 + 3 files changed, 170 insertions(+), 148 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs new file mode 100644 index 0000000000..0f0c32ea0d --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -0,0 +1,168 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class WaveformDisplay : Drawable + { + /// + /// The beatmap which the audio waveform should be displayed for. + /// + /// + public readonly Bindable Beatmap = new Bindable(); + + private Shader shader; + private readonly Texture texture; + + public WaveformDisplay() + { + texture = Texture.WhitePixel; + Beatmap.ValueChanged += generateWaveform; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders) + { + shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + } + + private float resolution = 1; + /// + /// Controls the amount of interpolation of the waveform into the width of this . + /// Points in the waveform are interpolated between 1 / pixels of this . + /// + /// + public float Resolution + { + get { return resolution; } + set + { + if (value < 0 || value > 1) + throw new ArgumentOutOfRangeException(nameof(value)); + + if (resolution == value) + return; + resolution = value; + + Invalidate(Invalidation.DrawNode); + } + } + + private Waveform waveform; + private Track lastQueriedTrack; + private void generateWaveform(WorkingBeatmap beatmap) + { + // Cancel the old query so we don't saturate the audio thread + lastQueriedTrack?.CancelWaveformQuery(); + + beatmap.Track.QueryWaveform(w => + { + if (Beatmap.Value == beatmap) + { + waveform = w; + Invalidate(Invalidation.DrawNode); + } + }); + + lastQueriedTrack = beatmap.Track; + } + + private readonly WaveformDrawNodeSharedData sharedData = new WaveformDrawNodeSharedData(); + protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); + protected override void ApplyDrawNode(DrawNode node) + { + var n = (WaveformDrawNode)node; + + n.Shader = shader; + n.Texture = texture; + n.Size = DrawSize; + n.Shared = sharedData; + n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) * resolution)); + n.Channels = waveform.Channels; + + base.ApplyDrawNode(node); + } + + private class WaveformDrawNodeSharedData + { + public readonly QuadBatch VertexBatch = new QuadBatch(1000, 10); + } + + private class WaveformDrawNode : DrawNode + { + public Shader Shader; + public Texture Texture; + + public WaveformDrawNodeSharedData Shared; + + public List Points; + public Vector2 Size; + public int Channels; + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + if (Points == null || Points.Count == 0) + return; + + Shader.Bind(); + Texture.TextureGL.Bind(); + + float separation = Size.X / (Points.Count - 1); + Vector2 localInflationAmount = new Vector2(0, 1) * DrawInfo.MatrixInverse.ExtractScale().Xy; + + for (int i = 0; i < Points.Count - 1; i++) + { + ColourInfo colour = DrawInfo.Colour; + Quad quadToDraw; + + switch (Channels) + { + default: + case 2: + { + float height = Size.Y / 2; + quadToDraw = new Quad( + new Vector2(i * separation, height - Points[i].Amplitude[0] * height), + new Vector2((i + 1) * separation, height - Points[i + 1].Amplitude[0] * height), + new Vector2(i * separation, height + Points[i].Amplitude[1] * height), + new Vector2((i + 1) * separation, height + Points[i + 1].Amplitude[1] * height) + ); + } + break; + case 1: + { + quadToDraw = new Quad( + new Vector2(i * separation, Size.Y - Points[i].Amplitude[0] * Size.Y), + new Vector2((i + 1) * separation, Size.Y - Points[i + 1].Amplitude[0] * Size.Y), + new Vector2(i * separation, Size.Y), + new Vector2((i + 1) * separation, Size.Y) + ); + break; + } + } + + Texture.DrawQuad(quadToDraw * DrawInfo.Matrix, colour, null, Shared.VertexBatch.Add, Vector2.Divide(localInflationAmount, quadToDraw.Size)); + } + + Shader.Unbind(); + } + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs index 4df7ffe367..0430185bf7 100644 --- a/osu.Game/Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -1,26 +1,17 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; -using System.Collections.Generic; using OpenTK; using OpenTK.Graphics; -using OpenTK.Graphics.ES30; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Batches; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Tests.Visual { @@ -94,143 +85,5 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); - - private class WaveformDisplay : Drawable - { - public readonly Bindable Beatmap = new Bindable(); - - private Waveform waveform; - - private Shader shader; - private readonly Texture texture; - - public WaveformDisplay() - { - texture = Texture.WhitePixel; - Beatmap.ValueChanged += generateWaveform; - } - - [BackgroundDependencyLoader] - private void load(ShaderManager shaders) - { - shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); - } - - private float resolution = 1; - public float Resolution - { - get { return resolution; } - set - { - if (resolution == value) - return; - resolution = value; - - Invalidate(Invalidation.DrawNode); - } - } - - private Track lastQueriedTrack; - - private void generateWaveform(WorkingBeatmap beatmap) - { - // Cancel the old query so we don't saturate the audio thread - lastQueriedTrack?.CancelWaveformQuery(); - - beatmap.Track.QueryWaveform(w => - { - if (Beatmap.Value == beatmap) - { - waveform = w; - Invalidate(Invalidation.DrawNode); - } - }); - - lastQueriedTrack = beatmap.Track; - } - - private readonly WaveformDrawNodeSharedData sharedData = new WaveformDrawNodeSharedData(); - protected override DrawNode CreateDrawNode() => new WaveformDrawNode(); - protected override void ApplyDrawNode(DrawNode node) - { - var n = (WaveformDrawNode)node; - - n.Shader = shader; - n.Texture = texture; - n.Size = DrawSize; - n.Shared = sharedData; - n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) * resolution)); - n.Channels = waveform.Channels; - - base.ApplyDrawNode(node); - } - - private class WaveformDrawNodeSharedData - { - public readonly QuadBatch VertexBatch = new QuadBatch(1000, 10); - } - - private class WaveformDrawNode : DrawNode - { - public Shader Shader; - public Texture Texture; - - public WaveformDrawNodeSharedData Shared; - - public List Points; - public Vector2 Size; - public int Channels; - - public override void Draw(Action vertexAction) - { - base.Draw(vertexAction); - - if (Points == null || Points.Count == 0) - return; - - Shader.Bind(); - Texture.TextureGL.Bind(); - - float separation = Size.X / (Points.Count - 1); - Vector2 localInflationAmount = new Vector2(0, 1) * DrawInfo.MatrixInverse.ExtractScale().Xy; - - for (int i = 0; i < Points.Count - 1; i++) - { - ColourInfo colour = DrawInfo.Colour; - Quad quadToDraw; - - switch (Channels) - { - default: - case 2: - { - float height = Size.Y / 2; - quadToDraw = new Quad( - new Vector2(i * separation, height - Points[i].Amplitude[0] * height), - new Vector2((i + 1) * separation, height - Points[i + 1].Amplitude[0] * height), - new Vector2(i * separation, height + Points[i].Amplitude[1] * height), - new Vector2((i + 1) * separation, height + Points[i + 1].Amplitude[1] * height) - ); - } - break; - case 1: - { - quadToDraw = new Quad( - new Vector2(i * separation, Size.Y - Points[i].Amplitude[0] * Size.Y), - new Vector2((i + 1) * separation, Size.Y - Points[i + 1].Amplitude[0] * Size.Y), - new Vector2(i * separation, Size.Y), - new Vector2((i + 1) * separation, Size.Y) - ); - break; - } - } - - Texture.DrawQuad(quadToDraw * DrawInfo.Matrix, colour, null, Shared.VertexBatch.Add, Vector2.Divide(localInflationAmount, quadToDraw.Size)); - } - - Shader.Unbind(); - } - } - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 56490b6119..f89810dfc1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -620,6 +620,7 @@ + From 80e984f72dea9361b6b2d53cb9bdbe42614412d2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 18:55:38 +0900 Subject: [PATCH 113/344] Update in-line with framework --- osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs index 0f0c32ea0d..a781e63fd9 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose n.Texture = texture; n.Size = DrawSize; n.Shared = sharedData; - n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.TotalPoints) * resolution)); + n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.MaximumPoints) * resolution)); n.Channels = waveform.Channels; base.ApplyDrawNode(node); From 81960c7b487d2a66b134ad9554d0a639153a2483 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 18:55:45 +0900 Subject: [PATCH 114/344] CI fixes --- osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs | 2 -- osu.Game/Tests/Visual/TestCaseWaveform.cs | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs index a781e63fd9..236f6b6d47 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -23,7 +23,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose /// /// The beatmap which the audio waveform should be displayed for. /// - /// public readonly Bindable Beatmap = new Bindable(); private Shader shader; @@ -46,7 +45,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose /// Controls the amount of interpolation of the waveform into the width of this . /// Points in the waveform are interpolated between 1 / pixels of this . /// - /// public float Resolution { get { return resolution; } diff --git a/osu.Game/Tests/Visual/TestCaseWaveform.cs b/osu.Game/Tests/Visual/TestCaseWaveform.cs index 0430185bf7..eff4f5cdad 100644 --- a/osu.Game/Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game/Tests/Visual/TestCaseWaveform.cs @@ -21,7 +21,6 @@ namespace osu.Game.Tests.Visual public TestCaseWaveform() { - MusicController mc; FillFlowContainer flow; Child = flow = new FillFlowContainer { @@ -30,7 +29,7 @@ namespace osu.Game.Tests.Visual Spacing = new Vector2(0, 10), Children = new Drawable[] { - mc = new MusicController + new MusicController { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -74,7 +73,7 @@ namespace osu.Game.Tests.Visual { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = $"Resolution: {(1f / i).ToString("0.00")}" + Text = $"Resolution: {(1f / i):0.00}" } } } From 5ca4a2d2c81600178816afb37778e40ce480f47c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 19:09:39 +0900 Subject: [PATCH 115/344] Add some nullchecks to WaveformDisplay --- osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs index 236f6b6d47..d1550117df 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -90,8 +90,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose n.Texture = texture; n.Size = DrawSize; n.Shared = sharedData; - n.Points = waveform.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.MaximumPoints) * resolution)); - n.Channels = waveform.Channels; + n.Points = waveform?.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.MaximumPoints) * resolution)); + n.Channels = waveform?.Channels ?? 0; base.ApplyDrawNode(node); } From 357a4673373a3dd1e0fc1da0bc5b01e3ba998c7d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 19:24:19 +0900 Subject: [PATCH 116/344] Implement design mode --- osu.Game/Screens/Edit/Editor.cs | 4 ++ .../Screens/Edit/Screens/Design/Design.cs | 52 +++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 3 files changed, 57 insertions(+) create mode 100644 osu.Game/Screens/Edit/Screens/Design/Design.cs diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index ca072ed73e..036243e668 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -14,6 +14,7 @@ using OpenTK; using osu.Framework.Allocation; using osu.Game.Screens.Edit.Screens; using osu.Game.Screens.Edit.Screens.Compose; +using osu.Game.Screens.Edit.Screens.Design; namespace osu.Game.Screens.Edit { @@ -113,6 +114,9 @@ namespace osu.Game.Screens.Edit case EditorScreenMode.Compose: currentScreen = new Compose(); break; + case EditorScreenMode.Design: + currentScreen = new Design(); + break; default: currentScreen = new EditorScreen(); break; diff --git a/osu.Game/Screens/Edit/Screens/Design/Design.cs b/osu.Game/Screens/Edit/Screens/Design/Design.cs new file mode 100644 index 0000000000..e527d7dad9 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Design/Design.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Edit.Screens.Design +{ + internal class Design : EditorScreen + { + public Design() + { + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.35f + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f + }, + new Container + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Child = new OsuSpriteText { Text = "Design screen" } + } + } + } + } + }); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9a8536bbc2..39fd968763 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -618,6 +618,7 @@ + From e2824d4732c1c177619820ae231223991520ff5f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 19:26:26 +0900 Subject: [PATCH 117/344] Reduce harshness of scale for now Though I don't feel like we should worry about this much just yet until we have actual designs and can see how it looks. It's very well possible that we use different transitions here... --- osu.Game/Screens/Edit/Screens/EditorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index 509c61a530..152c00a48d 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -28,14 +28,14 @@ namespace osu.Game.Screens.Edit.Screens { base.LoadComplete(); - this.ScaleTo(0.5f).FadeTo(0) + this.ScaleTo(0.75f).FadeTo(0) .Then() .ScaleTo(1f, 500, Easing.OutQuint).FadeTo(1f, 250, Easing.OutQuint); } public void Exit() { - this.ScaleTo(1.5f, 500).FadeOut(250).Expire(); + this.ScaleTo(1.25f, 500).FadeOut(250).Expire(); } } } From 34eede0d04dd887e472846163027d36b52f1bbc5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 19:36:55 +0900 Subject: [PATCH 118/344] Re-namespace EditorScreenMode --- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 2 +- .../Edit/Menus/ScreenSelectionTabControl.cs | 14 +------------- .../Screens/Edit/Screens/EditorScreenMode.cs | 19 +++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/Edit/Screens/EditorScreenMode.cs diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 4c9b3c84b3..34a96b0a6e 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -11,7 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; -using System; +using osu.Game.Screens.Edit.Screens; namespace osu.Game.Screens.Edit.Menus { diff --git a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs index 652ef1d61f..f5e47464ae 100644 --- a/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Menus/ScreenSelectionTabControl.cs @@ -9,8 +9,8 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Screens; using OpenTK; -using System.ComponentModel; namespace osu.Game.Screens.Edit.Menus { @@ -70,16 +70,4 @@ namespace osu.Game.Screens.Edit.Menus } } } - - public enum EditorScreenMode - { - [Description("compose")] - Compose, - [Description("design")] - Design, - [Description("timing")] - Timing, - [Description("song")] - SongSetup - } } diff --git a/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs b/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs new file mode 100644 index 0000000000..6489bb305b --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/EditorScreenMode.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Screens.Edit.Screens +{ + public enum EditorScreenMode + { + [Description("compose")] + Compose, + [Description("design")] + Design, + [Description("timing")] + Timing, + [Description("song")] + SongSetup + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 39fd968763..f1ba54cba3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -613,6 +613,7 @@ + From 8a52fdc8fa5abc503c6399cb214a7c0d97a39092 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 19:37:17 +0900 Subject: [PATCH 119/344] Use a bindable for the current screen in EditorMenuBar Replaces the current Action. --- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/Menus/EditorMenuBar.cs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 036243e668..4fca38a6c6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -96,7 +96,7 @@ namespace osu.Game.Screens.Edit }; timeline.Beatmap.BindTo(Beatmap); - menuBar.ModeChanged += onModeChanged; + menuBar.Mode.ValueChanged += onModeChanged; } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs index 34a96b0a6e..a4348b4489 100644 --- a/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Menus/EditorMenuBar.cs @@ -11,16 +11,14 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Configuration; using osu.Game.Screens.Edit.Screens; namespace osu.Game.Screens.Edit.Menus { public class EditorMenuBar : OsuMenu { - /// - /// Invaoked when the selected mode has changed. - /// - public event Action ModeChanged; + public readonly Bindable Mode = new Bindable(); private readonly ScreenSelectionTabControl tabControl; @@ -42,7 +40,7 @@ namespace osu.Game.Screens.Edit.Menus } }); - tabControl.Current.ValueChanged += v => ModeChanged?.Invoke(v); + tabControl.Current.BindTo(Mode); } protected override void LoadComplete() From 1377f73b460b90f7fd4037b51536cb36a7fff92f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 21:57:29 +0900 Subject: [PATCH 120/344] Multiply resolution before clamping --- osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs index d1550117df..e11cd667e6 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose n.Texture = texture; n.Size = DrawSize; n.Shared = sharedData; - n.Points = waveform?.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth), 0, waveform.MaximumPoints) * resolution)); + n.Points = waveform?.Generate((int)(MathHelper.Clamp(Math.Ceiling(DrawWidth) * Resolution, 0, waveform.MaximumPoints))); n.Channels = waveform?.Channels ?? 0; base.ApplyDrawNode(node); From ea4545299386900ba257dadcb25f7a0960901884 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 21:57:46 +0900 Subject: [PATCH 121/344] Allow resolution > 1 --- osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs index e11cd667e6..3856558a32 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/WaveformDisplay.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose get { return resolution; } set { - if (value < 0 || value > 1) + if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); if (resolution == value) From 8427bb44d18759b516865d49fd648327e3d14c99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 21:10:01 +0900 Subject: [PATCH 122/344] Implement basic layout for the compose screen ScrollableTimeline --- .../Screens/Compose/ScrollableTimeline.cs | 143 ++++++++++++++++++ .../Visual/TestCaseEditorComposeTimeline.cs | 46 ++++++ osu.Game/osu.Game.csproj | 2 + 3 files changed, 191 insertions(+) create mode 100644 osu.Game/Screens/Edit/Screens/Compose/ScrollableTimeline.cs create mode 100644 osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs diff --git a/osu.Game/Screens/Edit/Screens/Compose/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/ScrollableTimeline.cs new file mode 100644 index 0000000000..31d207a152 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/ScrollableTimeline.cs @@ -0,0 +1,143 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Screens.Compose +{ + public class ScrollableTimeline : CompositeDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + private readonly Container timelineContainer; + private readonly WaveformDisplay waveform; + + public ScrollableTimeline() + { + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("111") + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("222") + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Y, + Width = 160, + Padding = new MarginPadding { Horizontal = 25 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 4), + Children = new[] + { + new OsuCheckbox { LabelText = "Hit Objects" }, + new OsuCheckbox { LabelText = "Hit Sounds" }, + new OsuCheckbox { LabelText = "Waveform" } + } + } + } + }, + new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("333") + }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 15 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 30), + Children = new[] + { + new SpriteIcon + { + Size = new Vector2(18), + Icon = FontAwesome.fa_search_plus, + Colour = OsuColour.FromHex("555") + }, + new SpriteIcon + { + Size = new Vector2(18), + Icon = FontAwesome.fa_search_minus, + Colour = OsuColour.FromHex("555") + }, + } + } + } + }, + timelineContainer = new Container + { + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + waveform = new WaveformDisplay + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("081a84") + // Resolution = 0.33f, + } + } + } + } + } + }; + + waveform.Beatmap.BindTo(Beatmap); + } + + protected override bool OnWheel(InputState state) + { + if (!state.Keyboard.ControlPressed) + return false; + + waveform.ScaleTo(new Vector2(MathHelper.Clamp(waveform.Scale.X + state.Mouse.WheelDelta, 1, 30), 1), 150, Easing.OutQuint); + return true; + } + + protected override void Update() + { + base.Update(); + + timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1); + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs new file mode 100644 index 0000000000..714c7b5d50 --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Edit.Screens.Compose; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorComposeTimeline : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(WaveformDisplay) }; + + private readonly ScrollableTimeline timeline; + + public TestCaseEditorComposeTimeline() + { + Children = new Drawable[] + { + new MusicController + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + State = Visibility.Visible + }, + timeline = new ScrollableTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(1000, 100) + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osuGame) + { + timeline.Beatmap.BindTo(osuGame.Beatmap); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fde38848f1..d9c98e9b92 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -622,6 +622,7 @@ + @@ -748,6 +749,7 @@ + From e9bc5dbd0f5e18cb0520596303cf021acc3f6c5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 4 Oct 2017 23:35:20 +0900 Subject: [PATCH 123/344] Include framework commits --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 9d142a8e00..bcd92492a9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 9d142a8e009794dfee828392e36025d08577131d +Subproject commit bcd92492a9f01178b83c2360cb9b87536435adde From 6a4198d0d67b77294916ebd0767a1817bf30e2d4 Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Wed, 4 Oct 2017 22:52:12 +0300 Subject: [PATCH 124/344] Initial EF Core commit --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Patterns/Legacy/PatternGenerator.cs | 2 +- .../ManiaDifficultyCalculator.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 2 +- .../UI/ManiaRulesetContainer.cs | 10 +- .../Scoring/OsuScoreProcessor.cs | 2 +- .../UI/Cursor/GameplayCursor.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 12 +-- .../Scoring/TaikoScoreProcessor.cs | 4 +- .../Tests/TestCaseTaikoPlayfield.cs | 4 +- .../UI/TaikoRulesetContainer.cs | 2 +- .../Beatmaps/Formats/OsuLegacyDecoderTest.cs | 8 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 16 +-- .../Beatmaps/IO/OszArchiveReaderTest.cs | 2 +- osu.Game.Tests/app.config | 4 + osu.Game.Tests/osu.Game.Tests.csproj | 9 -- osu.Game.Tests/packages.config | 2 - osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficulty.cs | 7 +- osu.Game/Beatmaps/BeatmapInfo.cs | 67 +++++------- osu.Game/Beatmaps/BeatmapManager.cs | 63 +++++------ osu.Game/Beatmaps/BeatmapMetadata.cs | 9 +- osu.Game/Beatmaps/BeatmapOnlineInfo.cs | 4 + osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 21 ++-- osu.Game/Beatmaps/BeatmapSetInfo.cs | 23 ++-- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 16 ++- osu.Game/Beatmaps/BeatmapStore.cs | 102 +++++++----------- osu.Game/Beatmaps/DifficultyCalculator.cs | 2 +- osu.Game/Beatmaps/Drawables/BeatmapPanel.cs | 2 +- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 8 +- osu.Game/Beatmaps/Formats/BeatmapDecoder.cs | 4 +- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 24 ++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 4 +- osu.Game/Database/DatabaseBackedStore.cs | 79 +------------- osu.Game/Database/DbContextBase.cs | 8 ++ osu.Game/Database/OsuDbContext.cs | 55 ++++++++++ osu.Game/Database/StoreVersion.cs | 15 --- osu.Game/IO/FileInfo.cs | 9 +- osu.Game/IO/FileStore.cs | 91 +++++----------- .../Input/Bindings/DatabasedKeyBinding.cs | 17 ++- .../DatabasedKeyBindingInputManager.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 55 ++++------ .../API/Requests/DownloadBeatmapSetRequest.cs | 2 +- .../API/Requests/GetBeatmapDetailsRequest.cs | 2 +- .../API/Requests/GetBeatmapSetsRequest.cs | 10 +- .../Online/API/Requests/GetScoresRequest.cs | 8 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 42 +++++--- osu.Game/Overlays/BeatmapSet/Header.cs | 4 +- osu.Game/Overlays/BeatmapSet/Info.cs | 4 +- osu.Game/Overlays/Direct/DirectGridPanel.cs | 10 +- osu.Game/Overlays/Direct/DirectListPanel.cs | 10 +- osu.Game/Overlays/Direct/FilterControl.cs | 2 +- osu.Game/Overlays/DirectOverlay.cs | 12 +-- .../KeyBinding/KeyBindingsSubsection.cs | 2 +- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- osu.Game/Overlays/Music/PlaylistList.cs | 4 +- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- osu.Game/Overlays/MusicController.cs | 4 +- .../Overlays/Toolbar/ToolbarModeSelector.cs | 2 +- osu.Game/Rulesets/RulesetInfo.cs | 11 +- osu.Game/Rulesets/RulesetStore.cs | 91 +++++++++------- osu.Game/Rulesets/Scoring/ScoreStore.cs | 3 +- osu.Game/Rulesets/UI/RulesetContainer.cs | 4 +- osu.Game/Screens/Menu/Intro.cs | 2 +- osu.Game/Screens/Multiplayer/DrawableRoom.cs | 6 +- osu.Game/Screens/Multiplayer/RoomInspector.cs | 8 +- osu.Game/Screens/Play/Player.cs | 6 +- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- osu.Game/Screens/Ranking/ResultsPageScore.cs | 6 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 +-- .../Screens/Select/BeatmapDeleteDialog.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 6 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 +- .../Screens/Select/Details/AdvancedStats.cs | 12 +-- osu.Game/Screens/Select/FilterCriteria.cs | 10 +- .../Select/Leaderboards/Leaderboard.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 4 +- osu.Game/Storyboards/Storyboard.cs | 2 +- osu.Game/Tests/Platform/TestStorage.cs | 17 +-- .../Tests/Visual/TestCaseBeatmapDetails.cs | 16 +-- .../Tests/Visual/TestCaseBeatmapSetOverlay.cs | 44 ++++---- osu.Game/Tests/Visual/TestCaseDirect.cs | 52 ++++----- osu.Game/Tests/Visual/TestCaseDrawableRoom.cs | 12 +-- .../Tests/Visual/TestCasePlaySongSelect.cs | 34 +++--- osu.Game/Tests/Visual/TestCasePlayer.cs | 7 +- osu.Game/Tests/Visual/TestCaseResults.cs | 2 +- .../Tests/Visual/TestCaseRoomInspector.cs | 12 +-- .../Visual/TestCaseScrollingPlayfield.cs | 4 +- osu.Game/osu.Game.csproj | 100 ++++++++++++++--- osu.Game/packages.config | 43 +++++++- 93 files changed, 730 insertions(+), 708 deletions(-) create mode 100644 osu.Game/Database/DbContextBase.cs create mode 100644 osu.Game/Database/OsuDbContext.cs delete mode 100644 osu.Game/Database/StoreVersion.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index d7c86e1f89..713ee6bb44 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { beatmap = original; - BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty; + BeatmapDifficulty difficulty = original.BeatmapInfo.BeatmapDifficulty; int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); random = new FastRandom(seed); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 20966a75f7..07a7c47f82 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // The true distance, accounting for any repeats double distance = (distanceData?.Distance ?? 0) * repeatCount; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BeatmapDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index a3173f9784..d3b743062d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (drainTime == 0) drainTime = 10000; - BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.Difficulty; + BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BeatmapDifficulty; conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index b98802db69..6a96bc0a7d 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Mania return 0; } - protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize))); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.BeatmapDifficulty.CircleSize))); } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index a200ba31e2..d6c076803d 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty; + BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BeatmapDifficulty; hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 3b49d81674..3858c60367 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -88,18 +88,18 @@ namespace osu.Game.Rulesets.Mania.UI protected override BeatmapConverter CreateBeatmapConverter() { if (IsForCurrentRuleset) - AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize)); + AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.BeatmapDifficulty.CircleSize)); else { float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) AvailableColumns = 7; - else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5) - AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6; + else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.BeatmapDifficulty.CircleSize) >= 5) + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BeatmapDifficulty.OverallDifficulty) > 5 ? 7 : 6; else if (percentSliderOrSpinner > 0.6) - AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4; + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BeatmapDifficulty.OverallDifficulty) > 4 ? 5 : 4; else - AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7)); + AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.BeatmapDifficulty.OverallDifficulty) + 1, 7)); } return new ManiaBeatmapConverter(IsForCurrentRuleset, AvailableColumns); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 50239bf16c..b320940e67 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate; + hpDrainRate = beatmap.BeatmapInfo.BeatmapDifficulty.DrainRate; foreach (var obj in beatmap.HitObjects) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index adfc946f86..724a5f2b7a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (autoCursorScale && beatmap.Value != null) { // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.Difficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BeatmapDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); } cursorContainer.Scale = new Vector2(scale); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 4f2707ff88..6a8f046800 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { // Rewrite the beatmap info to add the slider velocity multiplier BeatmapInfo info = original.BeatmapInfo.DeepClone(); - info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; + info.BeatmapDifficulty.SliderMultiplier *= legacy_velocity_multiplier; Beatmap converted = base.ConvertBeatmap(original); @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BeatmapDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; // The duration of the taiko hit object double taikoDuration = distance / taikoVelocity; @@ -103,12 +103,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps speedAdjustedBeatLength *= speedAdjustment; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BeatmapDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BeatmapDifficulty.SliderTickRate, taikoDuration / repeats); if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { @@ -151,13 +151,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Samples = obj.Samples, IsStrong = strong, Duration = taikoDuration, - TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, + TickRate = beatmap.BeatmapInfo.BeatmapDifficulty.SliderTickRate == 3 ? 3 : 4, }; } } else if (endTimeData != null) { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BeatmapDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index abdda9676f..9b208813dc 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98)); + double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BeatmapDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpIncreaseTick = hp_hit_tick; hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; hpIncreaseGood = hpMultiplierNormal * hp_hit_good; - hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); + hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BeatmapDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); foreach (var obj in beatmap.HitObjects) { diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs index 79ee2945ad..8e93d52492 100644 --- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs @@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Taiko.Tests HitObjects = new List { new CentreHit() }, BeatmapInfo = new BeatmapInfo { - Difficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata + BeatmapDifficulty = new BeatmapDifficulty(), + BeatmapMetadata = new BeatmapMetadata { Artist = @"Unknown", Title = @"Sample Beatmap", diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index f0853aef0e..ca0a7e058f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI StartTime = time, }; - barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); + barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BeatmapDifficulty); bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs index da3b448f74..4c71601eed 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -23,8 +23,8 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) { var beatmap = decoder.Decode(new StreamReader(stream)); - var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + var meta = beatmap.BeatmapInfo.BeatmapMetadata; + Assert.AreEqual(241526, meta.BeatmapSetOnlineInfoId); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmapInfo.Countdown); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); - Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.IsTrue(beatmapInfo.RulesetInfoId == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); } @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) { var beatmap = decoder.Decode(new StreamReader(stream)); - var difficulty = beatmap.BeatmapInfo.Difficulty; + var difficulty = beatmap.BeatmapInfo.BeatmapDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); Assert.AreEqual(8, difficulty.OverallDifficulty); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 35bebf2d4f..4b704fe242 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Beatmaps.IO var store = osu.Dependencies.Get(); - waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.BeatmapSetOnlineInfoId == 241526)).Any(), @"BeatmapSet did not import to the database in allocated time.", timeout); //ensure we were stored to beatmap database backing... @@ -120,29 +120,29 @@ namespace osu.Game.Tests.Beatmaps.IO IEnumerable resultBeatmaps = null; //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. - waitForOrAssert(() => (resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() == 12, + waitForOrAssert(() => (resultBeatmaps = store.QueryBeatmaps(s => s.BeatmapSetOnlineInfoId == 241526 && s.BeatmapDifficultyId > 0)).Count() == 12, @"Beatmaps did not import to the database in allocated time", timeout); - var set = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526).First(); + var set = store.QueryBeatmapSets(s => s.BeatmapSetOnlineInfoId == 241526).First(); Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count(), $@"Incorrect database beatmap count post-import ({resultBeatmaps.Count()} but should be {set.Beatmaps.Count})."); foreach (BeatmapInfo b in resultBeatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); + Assert.IsTrue(set.Beatmaps.Any(c => c.BeatmapOnlineInfoId == b.BeatmapOnlineInfoId)); 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.RulesetInfoId == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap; + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetInfoId == 1))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap; + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetInfoId == 2))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); - beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; + beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetInfoId == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 7a7a8a58bc..9ca3089944 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; - Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual(241526, meta.BeatmapSetOnlineInfoId); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config index faeaf001de..11af32e2cf 100644 --- a/osu.Game.Tests/app.config +++ b/osu.Game.Tests/app.config @@ -6,6 +6,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 8ebd38022e..e188c82e79 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -39,15 +39,6 @@ True - - $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll - - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll - - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll - diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index af47f642e3..b94c0c6e2d 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -6,6 +6,4 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - - \ No newline at end of file diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 458c2304f2..0d6cd6f49a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps new Color4(121, 9, 13, 255) }; - public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; + public BeatmapMetadata Metadata => BeatmapInfo?.BeatmapMetadata ?? BeatmapInfo?.BeatmapSetInfo?.BeatmapMetadata; /// /// The HitObjects this Beatmap contains. diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 7c2294cae9..e354f3703e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,7 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using SQLite.Net.Attributes; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace osu.Game.Beatmaps { @@ -12,8 +13,8 @@ namespace osu.Game.Beatmaps /// public const float DEFAULT_DIFFICULTY = 5; - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 5e4e122fb5..565e1b97d6 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -2,49 +2,43 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; using osu.Game.IO.Serialization; using osu.Game.Rulesets; -using SQLite.Net.Attributes; -using SQLiteNetExtensions.Attributes; namespace osu.Game.Beatmaps { public class BeatmapInfo : IEquatable, IJsonSerializable { - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } //TODO: should be in database public int BeatmapVersion; - public int? OnlineBeatmapID { get; set; } + public int? BeatmapOnlineInfoId { get; set; } - public int? OnlineBeatmapSetID { get; set; } + public int? BeatmapSetOnlineInfoId { get; set; } - [ForeignKey(typeof(BeatmapSetInfo))] - public int BeatmapSetInfoID { get; set; } + [ForeignKey(nameof(BeatmapSetInfo))] + public int BeatmapSetInfoId { get; set; } + public BeatmapSetInfo BeatmapSetInfo { get; set; } - [ManyToOne] - public BeatmapSetInfo BeatmapSet { get; set; } + [ForeignKey(nameof(BeatmapMetadata))] + public int BeatmapMetadataId { get; set; } + public BeatmapMetadata BeatmapMetadata { get; set; } - [ForeignKey(typeof(BeatmapMetadata))] - public int BeatmapMetadataID { get; set; } + [ForeignKey(nameof(BeatmapDifficulty))] + public int BeatmapDifficultyId { get; set; } + public BeatmapDifficulty BeatmapDifficulty { get; set; } - [OneToOne(CascadeOperations = CascadeOperation.All)] - public BeatmapMetadata Metadata { get; set; } - - [ForeignKey(typeof(BeatmapDifficulty)), NotNull] - public int BaseDifficultyID { get; set; } - - [OneToOne(CascadeOperations = CascadeOperation.All)] - public BeatmapDifficulty Difficulty { get; set; } - - [Ignore] + [NotMapped] public BeatmapMetrics Metrics { get; set; } - [Ignore] + [NotMapped] public BeatmapOnlineInfo OnlineInfo { get; set; } public string Path { get; set; } @@ -57,7 +51,6 @@ namespace osu.Game.Beatmaps /// /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). /// - [Indexed] [JsonProperty("file_md5")] public string MD5Hash { get; set; } @@ -67,11 +60,9 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } public bool SpecialStyle { get; set; } - [ForeignKey(typeof(RulesetInfo))] - public int RulesetID { get; set; } - - [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)] - public RulesetInfo Ruleset { get; set; } + [ForeignKey(nameof(RulesetInfo))] + public int RulesetInfoId { get; set; } + public RulesetInfo RulesetInfo { get; set; } public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } @@ -99,7 +90,7 @@ namespace osu.Game.Beatmaps } } - [Ignore] + [NotMapped] public int[] Bookmarks { get; set; } = new int[0]; public double DistanceSpacing { get; set; } @@ -114,20 +105,20 @@ namespace osu.Game.Beatmaps public bool Equals(BeatmapInfo other) { - if (ID == 0 || other?.ID == 0) + if (Id == 0 || other?.Id == 0) // one of the two BeatmapInfos we are comparing isn't sourced from a database. // fall back to reference equality. return ReferenceEquals(this, other); - return ID == other?.ID; + return Id == other?.Id; } - public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && + BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && + (BeatmapMetadata ?? BeatmapSetInfo.BeatmapMetadata).AudioFile == (other.BeatmapMetadata ?? other.BeatmapSetInfo.BeatmapMetadata).AudioFile; - public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && - BeatmapSet.Hash == other.BeatmapSet.Hash && - (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSetInfo != null && other.BeatmapSetInfo != null && + BeatmapSetInfo.Hash == other.BeatmapSetInfo.Hash && + (BeatmapMetadata ?? BeatmapSetInfo.BeatmapMetadata).BackgroundFile == (other.BeatmapMetadata ?? other.BeatmapSetInfo.BeatmapMetadata).BackgroundFile; } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a1b678392b..3b023e34e4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -19,9 +19,9 @@ using osu.Game.IO; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; -using SQLite.Net; using osu.Game.Online.API.Requests; using System.Threading.Tasks; +using osu.Game.Database; using osu.Game.Online.API; namespace osu.Game.Beatmaps @@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps private readonly FileStore files; - private readonly SQLiteConnection connection; + private readonly OsuDbContext connection; private readonly RulesetStore rulesets; @@ -83,7 +83,7 @@ namespace osu.Game.Beatmaps /// public Func GetStableStorage { private get; set; } - public BeatmapManager(Storage storage, FileStore files, SQLiteConnection connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) + public BeatmapManager(Storage storage, FileStore files, OsuDbContext connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) { beatmaps = new BeatmapStore(connection); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps // let's only allow one concurrent import at a time for now. lock (importLock) - connection.RunInTransaction(() => Import(set = importToStorage(archiveReader))); + Import(set = importToStorage(archiveReader)); return set; } @@ -180,7 +180,7 @@ namespace osu.Game.Beatmaps public void Import(BeatmapSetInfo beatmapSetInfo) { // If we have an ID then we already exist in the database. - if (beatmapSetInfo.ID != 0) return; + if (beatmapSetInfo.Id != 0) return; beatmaps.Add(beatmapSetInfo); } @@ -200,7 +200,7 @@ namespace osu.Game.Beatmaps ProgressNotification downloadNotification = new ProgressNotification { - Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", + Text = $"Downloading {beatmapSetInfo.BeatmapMetadata.Artist} - {beatmapSetInfo.BeatmapMetadata.Title}", }; var request = new DownloadBeatmapSetRequest(beatmapSetInfo); @@ -251,7 +251,7 @@ namespace osu.Game.Beatmaps /// /// The whose download request is wanted. /// The object if it exists, or null. - public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); + public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.BeatmapSetOnlineInfoId == beatmap.BeatmapSetOnlineInfoId); /// /// Delete a beatmap from the manager. @@ -302,14 +302,15 @@ namespace osu.Game.Beatmaps if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - lock (beatmaps) - beatmaps.Populate(beatmapInfo); + // TODO Include() + //lock (beatmaps) + // beatmaps.Populate(beatmapInfo); - if (beatmapInfo.BeatmapSet == null) - throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); + if (beatmapInfo.BeatmapSetInfo == null) + throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoId} is not in the local database."); - if (beatmapInfo.Metadata == null) - beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; + if (beatmapInfo.BeatmapMetadata == null) + beatmapInfo.BeatmapMetadata = beatmapInfo.BeatmapSetInfo.BeatmapMetadata; WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(files.Store, beatmapInfo); @@ -336,10 +337,11 @@ namespace osu.Game.Beatmaps { lock (beatmaps) { - BeatmapSetInfo set = beatmaps.Query().FirstOrDefault(query); + BeatmapSetInfo set = beatmaps.QueryBeatmapSet(query); - if (set != null) - beatmaps.Populate(set); + // TODO Include() + //if (set != null) + // beatmaps.Populate(set); return set; } @@ -350,7 +352,7 @@ namespace osu.Game.Beatmaps /// /// A stale instance. /// A fresh instance. - public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID); + public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.Id == beatmapSet.Id); /// /// Perform a lookup query on available s. @@ -359,7 +361,7 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public List QueryBeatmapSets(Expression> query) { - return beatmaps.QueryAndPopulate(query); + return beatmaps.QueryBeatmapSets(query); } /// @@ -369,10 +371,11 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo QueryBeatmap(Func query) { - BeatmapInfo set = beatmaps.Query().FirstOrDefault(query); + BeatmapInfo set = beatmaps.QueryBeatmap(query); - if (set != null) - beatmaps.Populate(set); + // TODO Include() + //if (set != null) + // beatmaps.Populate(set); return set; } @@ -384,7 +387,7 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public List QueryBeatmaps(Expression> query) { - lock (beatmaps) return beatmaps.QueryAndPopulate(query); + lock (beatmaps) return beatmaps.QueryBeatmaps(query); } /// @@ -424,7 +427,7 @@ namespace osu.Game.Beatmaps // check if this beatmap has already been imported and exit early if so. BeatmapSetInfo beatmapSet; lock (beatmaps) - beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault(); + beatmapSet = beatmaps.QueryBeatmapSet(b => b.Hash == hash); if (beatmapSet != null) { @@ -459,11 +462,11 @@ namespace osu.Game.Beatmaps beatmapSet = new BeatmapSetInfo { - OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, + BeatmapSetOnlineInfoId = metadata.BeatmapSetOnlineInfoId, Beatmaps = new List(), Hash = hash, Files = fileInfos, - Metadata = metadata + BeatmapMetadata = metadata }; var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); @@ -485,11 +488,11 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); // TODO: Diff beatmap metadata with set metadata and leave it here if necessary - beatmap.BeatmapInfo.Metadata = null; + beatmap.BeatmapInfo.BeatmapMetadata = null; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.Ruleset = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID); - beatmap.BeatmapInfo.StarDifficulty = rulesets.Query().FirstOrDefault(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) + beatmap.BeatmapInfo.RulesetInfo = rulesets.QueryRulesetInfo(r => r.Id == beatmap.BeatmapInfo.RulesetInfoId); + beatmap.BeatmapInfo.StarDifficulty = rulesets.QueryRulesetInfo(r => r.Id == beatmap.BeatmapInfo.RulesetInfoId)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) .Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); @@ -509,9 +512,9 @@ namespace osu.Game.Beatmaps lock (beatmaps) { if (populate) - return beatmaps.QueryAndPopulate(b => !b.DeletePending).ToList(); + return beatmaps.QueryBeatmapSets(b => !b.DeletePending); else - return beatmaps.Query(b => !b.DeletePending).ToList(); + return beatmaps.QueryBeatmapSets(b => !b.DeletePending); } } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index cc9a51b4e2..8266dc9161 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -1,18 +1,19 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; -using SQLite.Net.Attributes; namespace osu.Game.Beatmaps { public class BeatmapMetadata { - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - public int? OnlineBeatmapSetID { get; set; } + public int? BeatmapSetOnlineInfoId { get; set; } public string Title { get; set; } public string TitleUnicode { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs index 399cabda99..2264a4fc1a 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace osu.Game.Beatmaps @@ -10,6 +11,9 @@ namespace osu.Game.Beatmaps /// public class BeatmapOnlineInfo { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + /// /// The length in milliseconds of this beatmap's song. /// diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index a05362b32d..aec87296a0 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -1,27 +1,26 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using osu.Game.IO; -using SQLite.Net.Attributes; -using SQLiteNetExtensions.Attributes; namespace osu.Game.Beatmaps { public class BeatmapSetFileInfo { - [PrimaryKey, AutoIncrement] + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } - [ForeignKey(typeof(BeatmapSetInfo)), NotNull] - public int BeatmapSetInfoID { get; set; } + [ForeignKey(nameof(BeatmapSetInfo))] + public int BeatmapSetInfoId { get; set; } + public BeatmapSetInfo BeatmapSetInfo { get; set; } - [ForeignKey(typeof(FileInfo)), NotNull] - public int FileInfoID { get; set; } - - [OneToOne(CascadeOperations = CascadeOperation.CascadeRead)] + [ForeignKey(nameof(FileInfo))] + public int FileInfoId { get; set; } public FileInfo FileInfo { get; set; } - [NotNull] + [Required] public string Filename { get; set; } } -} \ No newline at end of file +} diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index f47affcab8..9be00152c3 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -2,41 +2,36 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; -using SQLite.Net.Attributes; -using SQLiteNetExtensions.Attributes; namespace osu.Game.Beatmaps { public class BeatmapSetInfo { - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - public int? OnlineBeatmapSetID { get; set; } + public int? BeatmapSetOnlineInfoId { get; set; } - [OneToOne(CascadeOperations = CascadeOperation.All)] - public BeatmapMetadata Metadata { get; set; } + [ForeignKey(nameof(BeatmapMetadata))] + public int BeatmapMetadataId { get; set; } + public BeatmapMetadata BeatmapMetadata { get; set; } - [NotNull, ForeignKey(typeof(BeatmapMetadata))] - public int BeatmapMetadataID { get; set; } - - [OneToMany(CascadeOperations = CascadeOperation.All)] public List Beatmaps { get; set; } - [Ignore] + [NotMapped] public BeatmapSetOnlineInfo OnlineInfo { get; set; } public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); - [Indexed] public bool DeletePending { get; set; } public string Hash { get; set; } public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; - [OneToMany(CascadeOperations = CascadeOperation.All)] public List Files { get; set; } public bool Protected { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 6b59f0f298..c33ee66dac 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,6 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; using osu.Game.Users; @@ -12,11 +15,19 @@ namespace osu.Game.Beatmaps /// public class BeatmapSetOnlineInfo { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + /// /// The author of the beatmaps in this set. /// public User Author; + public int BeatmapSetInfoId { get; set; } + public BeatmapSetInfo BeatmapSetInfo { get; set; } + + public List Beatmaps { get; set; } + /// /// The date this beatmap set was submitted to the online listing. /// @@ -35,7 +46,7 @@ namespace osu.Game.Beatmaps /// /// The different sizes of cover art for this beatmap set. /// - [JsonProperty(@"covers")] + [Required, JsonProperty(@"covers")] public BeatmapSetOnlineCovers Covers { get; set; } /// @@ -64,6 +75,9 @@ namespace osu.Game.Beatmaps public class BeatmapSetOnlineCovers { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } + public string CoverLowRes { get; set; } [JsonProperty(@"cover@2x")] diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 0f2d8cffa6..ece006a818 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -2,9 +2,11 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using osu.Game.Database; -using SQLite.Net; -using SQLiteNetExtensions.Extensions; namespace osu.Game.Beatmaps { @@ -19,13 +21,7 @@ namespace osu.Game.Beatmaps public event Action BeatmapHidden; public event Action BeatmapRestored; - /// - /// The current version of this store. Used for migrations (see ). - /// The initial version is 1. - /// - protected override int StoreVersion => 4; - - public BeatmapStore(SQLiteConnection connection) + public BeatmapStore(OsuDbContext connection) : base(connection) { } @@ -42,18 +38,13 @@ namespace osu.Game.Beatmaps { if (reset) { - Connection.DropTable(); - Connection.DropTable(); - Connection.DropTable(); - Connection.DropTable(); - Connection.DropTable(); + // https://stackoverflow.com/a/10450893 + Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata"); + Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty"); + Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo"); + Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo"); + Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo"); } - - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); } protected override void StartupTasks() @@ -62,46 +53,14 @@ namespace osu.Game.Beatmaps cleanupPendingDeletions(); } - /// - /// Perform migrations between two store versions. - /// - /// The current store version. This will be zero on a fresh database initialisation. - /// The target version which we are migrating to (equal to the current ). - protected override void PerformMigration(int currentVersion, int targetVersion) - { - base.PerformMigration(currentVersion, targetVersion); - - while (currentVersion++ < targetVersion) - { - switch (currentVersion) - { - case 1: - case 2: - // cannot migrate; breaking underlying changes. - Reset(); - break; - case 3: - // Added MD5Hash column to BeatmapInfo - Connection.MigrateTable(); - break; - case 4: - // Added Hidden column to BeatmapInfo - Connection.MigrateTable(); - break; - } - } - } - /// /// Add a to the database. /// /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { - Connection.RunInTransaction(() => - { - Connection.InsertOrReplaceWithChildren(beatmapSet, true); - }); + Connection.BeatmapSetInfo.Update(beatmapSet); + Connection.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); } @@ -116,7 +75,8 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - Connection.Update(beatmapSet); + Connection.BeatmapSetInfo.Remove(beatmapSet); + Connection.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); return true; @@ -132,7 +92,7 @@ namespace osu.Game.Beatmaps if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; - Connection.Update(beatmapSet); + Connection.BeatmapSetInfo.Update(beatmapSet); BeatmapSetAdded?.Invoke(beatmapSet); return true; @@ -148,7 +108,7 @@ namespace osu.Game.Beatmaps if (beatmap.Hidden) return false; beatmap.Hidden = true; - Connection.Update(beatmap); + Connection.BeatmapInfo.Update(beatmap); BeatmapHidden?.Invoke(beatmap); return true; @@ -164,7 +124,7 @@ namespace osu.Game.Beatmaps if (!beatmap.Hidden) return false; beatmap.Hidden = false; - Connection.Update(beatmap); + Connection.BeatmapInfo.Update(beatmap); BeatmapRestored?.Invoke(beatmap); return true; @@ -172,11 +132,27 @@ namespace osu.Game.Beatmaps private void cleanupPendingDeletions() { - Connection.RunInTransaction(() => - { - foreach (var b in QueryAndPopulate(b => b.DeletePending && !b.Protected)) - Connection.Delete(b, true); - }); + Connection.BeatmapSetInfo.RemoveRange(Connection.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); + } + + public BeatmapSetInfo QueryBeatmapSet(Func query) + { + return Connection.BeatmapSetInfo.FirstOrDefault(query); + } + + public List QueryBeatmapSets(Expression> query) + { + return Connection.BeatmapSetInfo.Where(query).ToList(); + } + + public BeatmapInfo QueryBeatmap(Func query) + { + return Connection.BeatmapInfo.FirstOrDefault(query); + } + + public List QueryBeatmaps(Expression> query) + { + return Connection.BeatmapInfo.Where(query).ToList(); } } } diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 60cbf0ac61..1cd0516b0e 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects; foreach (var h in Objects) - h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BeatmapDifficulty); PreprocessHitObjects(); } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index e216f1b83e..dfff949fe9 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -135,7 +135,7 @@ namespace osu.Game.Beatmaps.Drawables new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author}", + Text = $"{(beatmap.BeatmapMetadata ?? beatmap.BeatmapSetInfo.BeatmapMetadata).Author}", TextSize = 16, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 42db025a40..2981b3b053 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables new ConstrainedIconContainer { RelativeSizeAxes = Axes.Both, - Icon = beatmap.Ruleset.CreateInstance().CreateIcon() + Icon = beatmap.RulesetInfo.CreateInstance().CreateIcon() } }; } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index d8cd58d939..3746a1fabe 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -18,14 +18,14 @@ namespace osu.Game.Beatmaps public DummyWorkingBeatmap(OsuGameBase game) : base(new BeatmapInfo { - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Artist = "please load a beatmap!", Title = "no beatmaps available!", Author = "no one", }, - BeatmapSet = new BeatmapSetInfo(), - Difficulty = new BeatmapDifficulty + BeatmapSetInfo = new BeatmapSetInfo(), + BeatmapDifficulty = new BeatmapDifficulty { DrainRate = 0, CircleSize = 0, @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps SliderMultiplier = 0, SliderTickRate = 0, }, - Ruleset = new DummyRulesetInfo() + RulesetInfo = new DummyRulesetInfo() }) { this.game = game; diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 81695c3b5a..9d97ca8602 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -48,8 +48,8 @@ namespace osu.Game.Beatmaps.Formats { BeatmapInfo = new BeatmapInfo { - Metadata = new BeatmapMetadata(), - Difficulty = new BeatmapDifficulty(), + BeatmapMetadata = new BeatmapMetadata(), + BeatmapDifficulty = new BeatmapDifficulty(), }, }; diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 21fee0f465..9d6586ef26 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = splitKeyVal(line, ':'); - var metadata = beatmap.BeatmapInfo.Metadata; + var metadata = beatmap.BeatmapInfo.BeatmapMetadata; switch (pair.Key) { case @"AudioFilename": @@ -97,9 +97,9 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.RulesetInfoId = int.Parse(pair.Value); - switch (beatmap.BeatmapInfo.RulesetID) + switch (beatmap.BeatmapInfo.RulesetInfoId) { case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(); @@ -155,7 +155,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = splitKeyVal(line, ':'); - var metadata = beatmap.BeatmapInfo.Metadata; + var metadata = beatmap.BeatmapInfo.BeatmapMetadata; switch (pair.Key) { case @"Title": @@ -177,17 +177,17 @@ namespace osu.Game.Beatmaps.Formats beatmap.BeatmapInfo.Version = pair.Value; break; case @"Source": - beatmap.BeatmapInfo.Metadata.Source = pair.Value; + beatmap.BeatmapInfo.BeatmapMetadata.Source = pair.Value; break; case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = pair.Value; + beatmap.BeatmapInfo.BeatmapMetadata.Tags = pair.Value; break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatmapOnlineInfoId = int.Parse(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); - metadata.OnlineBeatmapSetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatmapSetOnlineInfoId = int.Parse(pair.Value); + metadata.BeatmapSetOnlineInfoId = int.Parse(pair.Value); break; } } @@ -196,7 +196,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = splitKeyVal(line, ':'); - var difficulty = beatmap.BeatmapInfo.Difficulty; + var difficulty = beatmap.BeatmapInfo.BeatmapDifficulty; switch (pair.Key) { case @"HPDrainRate": @@ -270,7 +270,7 @@ namespace osu.Game.Beatmaps.Formats string filename = split[2].Trim('"'); if (type == EventType.Background) - beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; + beatmap.BeatmapInfo.BeatmapMetadata.BackgroundFile = filename; break; case EventType.Break: @@ -674,7 +674,7 @@ namespace osu.Game.Beatmaps.Formats } foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BeatmapDifficulty); } private KeyValuePair splitKeyVal(string line, char separator) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 277846ee80..4b50e50efe 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -24,8 +24,8 @@ namespace osu.Game.Beatmaps protected WorkingBeatmap(BeatmapInfo beatmapInfo) { BeatmapInfo = beatmapInfo; - BeatmapSetInfo = beatmapInfo.BeatmapSet; - Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + BeatmapSetInfo = beatmapInfo.BeatmapSetInfo; + Metadata = beatmapInfo.BeatmapMetadata ?? BeatmapSetInfo?.BeatmapMetadata ?? new BeatmapMetadata(); Mods.ValueChanged += mods => applyRateAdjustments(); } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index d8e2e35bd7..f4b6a866dc 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,27 +2,23 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; -using SQLite.Net; -using SQLiteNetExtensions.Extensions; namespace osu.Game.Database { public abstract class DatabaseBackedStore { protected readonly Storage Storage; - protected readonly SQLiteConnection Connection; + protected readonly OsuDbContext Connection; - protected virtual int StoreVersion => 1; - - protected DatabaseBackedStore(SQLiteConnection connection, Storage storage = null) + protected DatabaseBackedStore(OsuDbContext connection, Storage storage = null) { Storage = storage; Connection = connection; + Connection.Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10)); try { @@ -33,36 +29,6 @@ namespace osu.Game.Database Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database..."); Prepare(true); } - - checkMigrations(); - } - - private void checkMigrations() - { - var storeName = GetType().Name; - - var reportedVersion = Connection.Table().Where(s => s.StoreName == storeName).FirstOrDefault() ?? new StoreVersion - { - StoreName = storeName, - Version = 0 - }; - - if (reportedVersion.Version != StoreVersion) - PerformMigration(reportedVersion.Version, reportedVersion.Version = StoreVersion); - - Connection.InsertOrReplace(reportedVersion); - - StartupTasks(); - } - - /// - /// Called when the database version of this store doesn't match the local version. - /// Any manual migration operations should be performed in this. - /// - /// The current store version. This will be zero on a fresh database initialisation. - /// The target version which we are migrating to (equal to the current ). - protected virtual void PerformMigration(int currentVersion, int targetVersion) - { } /// @@ -83,43 +49,6 @@ namespace osu.Game.Database /// public void Reset() => Prepare(true); - - public TableQuery Query(Expression> filter = null) where T : class - { - checkType(typeof(T)); - - var query = Connection.Table(); - - if (filter != null) - query = query.Where(filter); - - return query; - } - - /// - /// Query and populate results. - /// - /// An filter to refine results. - /// - public List QueryAndPopulate(Expression> filter) - where T : class - { - checkType(typeof(T)); - - return Connection.GetAllWithChildren(filter, true); - } - - /// - /// Populate a database-backed item. - /// - /// - /// Whether population should recurse beyond a single level. - public void Populate(T item, bool recursive = true) - { - checkType(item.GetType()); - Connection.GetChildren(item, recursive); - } - private void checkType(Type type) { if (!ValidTypes.Contains(type)) diff --git a/osu.Game/Database/DbContextBase.cs b/osu.Game/Database/DbContextBase.cs new file mode 100644 index 0000000000..93f4ac4c23 --- /dev/null +++ b/osu.Game/Database/DbContextBase.cs @@ -0,0 +1,8 @@ +using Microsoft.EntityFrameworkCore; + +namespace osu.Game.Database +{ + public abstract class DbContextBase:DbContext + { + } +} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs new file mode 100644 index 0000000000..41ae0711f4 --- /dev/null +++ b/osu.Game/Database/OsuDbContext.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using osu.Game.Beatmaps; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Rulesets; + +namespace osu.Game.Database +{ + public class OsuDbContext : DbContext + { + private readonly string connectionString; + + public OsuDbContext() + { + connectionString = "DataSource=:memory:"; + } + + public OsuDbContext(string connectionString) + { + this.connectionString = connectionString; + } + + public DbSet BeatmapMetadata { get; set; } + public DbSet BeatmapDifficulty { get; set; } + public DbSet BeatmapInfo { get; set; } + public DbSet BeatmapSetInfo { get; set; } + public DbSet BeatmapSetFileInfo { get; set; } + public DbSet DatabasedKeyBinding { get; set; } + public DbSet FileInfo { get; set; } + public DbSet RulesetInfo { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + base.OnConfiguring(optionsBuilder); + optionsBuilder.UseSqlite(connectionString); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.Entity().HasIndex(b => b.MD5Hash); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => b.Variant); + modelBuilder.Entity().HasIndex(b => b.IntAction); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.ReferenceCount); + modelBuilder.Entity().HasIndex(b => b.Name).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.InstantiationInfo).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Available); + //modelBuilder.Entity().HasOne(b => b.BeatmapSetOnlineInfo).WithOne(bs => bs.BeatmapMetadata).OnDelete(DeleteBehavior.SetNull); + //modelBuilder.Entity().HasOne(b => b.BeatmapSetOnlineInfo).WithOne(bs => bs.BeatmapSetInfo).OnDelete(DeleteBehavior.SetNull); + //modelBuilder.Entity().HasOne(b => b.BeatmapSetOnlineInfo).WithMany(bs => bs.Beatmaps).OnDelete(DeleteBehavior.SetNull); + } + } +} diff --git a/osu.Game/Database/StoreVersion.cs b/osu.Game/Database/StoreVersion.cs deleted file mode 100644 index 00314875a6..0000000000 --- a/osu.Game/Database/StoreVersion.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using SQLite.Net.Attributes; - -namespace osu.Game.Database -{ - public class StoreVersion - { - [PrimaryKey] - public string StoreName { get; set; } - - public int Version { get; set; } - } -} diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 367fd68f7b..7ba75eeb0c 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -1,22 +1,21 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.IO; -using SQLite.Net.Attributes; namespace osu.Game.IO { public class FileInfo { - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - [Indexed(Unique = true)] public string Hash { get; set; } public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash); - [Indexed] public int ReferenceCount { get; set; } } } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index c3d8c1df46..416b6c2989 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -4,12 +4,12 @@ using System; using System.IO; using System.Linq; +using Microsoft.EntityFrameworkCore; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; -using SQLite.Net; namespace osu.Game.IO { @@ -22,9 +22,7 @@ namespace osu.Game.IO public readonly ResourceStore Store; - protected override int StoreVersion => 2; - - public FileStore(SQLiteConnection connection, Storage storage) : base(connection, storage) + public FileStore(OsuDbContext connection, Storage storage) : base(connection, storage) { Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); } @@ -44,10 +42,8 @@ namespace osu.Game.IO if (Storage.ExistsDirectory(prefix)) Storage.DeleteDirectory(prefix); - Connection.DropTable(); + Connection.Database.ExecuteSqlCommand("DELETE FROM FileInfo"); } - - Connection.CreateTable(); } protected override void StartupTasks() @@ -56,33 +52,11 @@ namespace osu.Game.IO deletePending(); } - /// - /// Perform migrations between two store versions. - /// - /// The current store version. This will be zero on a fresh database initialisation. - /// The target version which we are migrating to (equal to the current ). - protected override void PerformMigration(int currentVersion, int targetVersion) - { - base.PerformMigration(currentVersion, targetVersion); - - while (currentVersion++ < targetVersion) - { - switch (currentVersion) - { - case 1: - case 2: - // cannot migrate; breaking underlying changes. - Reset(); - break; - } - } - } - public FileInfo Add(Stream data, bool reference = true) { string hash = data.ComputeSHA2Hash(); - var existing = Connection.Table().Where(f => f.Hash == hash).FirstOrDefault(); + var existing = Connection.FileInfo.Where(f => f.Hash == hash).FirstOrDefault(); var info = existing ?? new FileInfo { Hash = hash }; @@ -100,61 +74,54 @@ namespace osu.Game.IO } if (existing == null) - Connection.Insert(info); + Connection.FileInfo.Add(info); if (reference || existing == null) Reference(info); + Connection.SaveChanges(); return info; } public void Reference(params FileInfo[] files) { - Connection.RunInTransaction(() => + var incrementedFiles = files.GroupBy(f => f.Id).Select(f => { - var incrementedFiles = files.GroupBy(f => f.ID).Select(f => - { - var accurateRefCount = Connection.Get(f.First().ID); - accurateRefCount.ReferenceCount += f.Count(); - return accurateRefCount; - }); - - Connection.UpdateAll(incrementedFiles); + var accurateRefCount = Connection.Find(f.First().Id); + accurateRefCount.ReferenceCount += f.Count(); + return accurateRefCount; }); + //Connection.FileInfo.UpdateRange(incrementedFiles); + Connection.SaveChanges(); } public void Dereference(params FileInfo[] files) { - Connection.RunInTransaction(() => + var incrementedFiles = files.GroupBy(f => f.Id).Select(f => { - var incrementedFiles = files.GroupBy(f => f.ID).Select(f => - { - var accurateRefCount = Connection.Get(f.First().ID); - accurateRefCount.ReferenceCount -= f.Count(); - return accurateRefCount; - }); - - Connection.UpdateAll(incrementedFiles); + var accurateRefCount = Connection.Find(f.First().Id); + accurateRefCount.ReferenceCount -= f.Count(); + return accurateRefCount; }); + + //Connection.FileInfo.UpdateRange(incrementedFiles); + Connection.SaveChanges(); } private void deletePending() { - Connection.RunInTransaction(() => + foreach (var f in Connection.FileInfo.Where(f => f.ReferenceCount < 1)) { - foreach (var f in Query(f => f.ReferenceCount < 1)) + try { - try - { - Storage.Delete(Path.Combine(prefix, f.StoragePath)); - Connection.Delete(f); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete beatmap {f}"); - } + Storage.Delete(Path.Combine(prefix, f.StoragePath)); + Connection.FileInfo.Remove(f); } - }); + catch (Exception e) + { + Logger.Error(e, $@"Could not delete beatmap {f}"); + } + } } } -} \ No newline at end of file +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index cbf74d6984..73a8e117d3 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -1,23 +1,23 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using osu.Framework.Input.Bindings; using osu.Game.Rulesets; -using SQLite.Net.Attributes; -using SQLiteNetExtensions.Attributes; namespace osu.Game.Input.Bindings { [Table("KeyBinding")] public class DatabasedKeyBinding : KeyBinding { - [PrimaryKey, AutoIncrement] - public int ID { get; set; } + [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int Id { get; set; } - [ForeignKey(typeof(RulesetInfo))] - public int? RulesetID { get; set; } + [ForeignKey(nameof(RulesetInfo))] + public int? RulesetInfoId { get; set; } + public RulesetInfo RulesetInfo; - [Indexed] public int? Variant { get; set; } [Column("Keys")] @@ -27,7 +27,6 @@ namespace osu.Game.Input.Bindings private set { KeyCombination = value; } } - [Indexed] [Column("Action")] public int IntAction { @@ -35,4 +34,4 @@ namespace osu.Game.Input.Bindings set { Action = value; } } } -} \ No newline at end of file +} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs index 0a4fcf4389..41a2f54277 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingInputManager.cs @@ -48,7 +48,7 @@ namespace osu.Game.Input.Bindings protected override void ReloadMappings() { - KeyBindings = store.Query(ruleset?.ID, variant); + KeyBindings = store.Query(ruleset?.Id, variant); } } } \ No newline at end of file diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index c5ba1683dd..3157bee6a7 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -4,78 +4,54 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Input.Bindings; using osu.Game.Rulesets; -using SQLite.Net; namespace osu.Game.Input { public class KeyBindingStore : DatabaseBackedStore { - public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null) + public KeyBindingStore(OsuDbContext connection, RulesetStore rulesets, Storage storage = null) : base(connection, storage) { foreach (var info in rulesets.AllRulesets) { var ruleset = info.CreateInstance(); foreach (var variant in ruleset.AvailableVariants) - insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant); + insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.Id, variant); } } public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings); - protected override int StoreVersion => 3; - - protected override void PerformMigration(int currentVersion, int targetVersion) - { - base.PerformMigration(currentVersion, targetVersion); - - while (currentVersion++ < targetVersion) - { - switch (currentVersion) - { - case 1: - case 2: - case 3: - // cannot migrate; breaking underlying changes. - Reset(); - break; - } - } - } - protected override void Prepare(bool reset = false) { - if (reset) - Connection.DropTable(); - - Connection.CreateTable(); + Connection.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { - var query = Query(rulesetId, variant); - // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { int count; - while (group.Count() > (count = query.Count(k => (int)k.Action == (int)group.Key))) + while (group.Count() > (count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key))) { var insertable = group.Skip(count).First(); // insert any defaults which are missing. - Connection.Insert(new DatabasedKeyBinding + Connection.DatabasedKeyBinding.Add(new DatabasedKeyBinding { KeyCombination = insertable.KeyCombination, Action = insertable.Action, - RulesetID = rulesetId, + RulesetInfoId = rulesetId, Variant = variant }); + Connection.SaveChanges(); } } } @@ -85,9 +61,18 @@ namespace osu.Game.Input typeof(DatabasedKeyBinding) }; - public IEnumerable Query(int? rulesetId = null, int? variant = null) => - Query(b => b.RulesetID == rulesetId && b.Variant == variant); + public List Query(int? rulesetId = null, int? variant = null) => + new List(Connection.DatabasedKeyBinding.Where(b => b.RulesetInfoId == rulesetId && b.Variant == variant)); - public void Update(KeyBinding keyBinding) => Connection.Update(keyBinding); + public void Update(KeyBinding keyBinding) + { + var dbKeyBinding = Connection.DatabasedKeyBinding.FirstOrDefault(kb => kb.ToString() == keyBinding.ToString()); + if (dbKeyBinding!=null) + { + dbKeyBinding.KeyCombination = keyBinding.KeyCombination; + dbKeyBinding.Action = keyBinding.Action; + } + Connection.SaveChanges(); + } } } diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index 5a9f609bca..f8e20d1e9f 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -19,6 +19,6 @@ namespace osu.Game.Online.API.Requests Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total); } - protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download"; + protected override string Target => $@"beatmapsets/{BeatmapSet.BeatmapSetOnlineInfoId}/download"; } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs index 934ef7ffa2..233e16beb1 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -10,7 +10,7 @@ namespace osu.Game.Online.API.Requests { private readonly BeatmapInfo beatmap; - private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; + private string lookupString => beatmap.BeatmapOnlineInfoId > 0 ? beatmap.BeatmapOnlineInfoId.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}"; public GetBeatmapDetailsRequest(BeatmapInfo beatmap) { diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs index 470e13ea7b..37d7a5e2d6 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetsRequest.cs @@ -30,7 +30,7 @@ namespace osu.Game.Online.API.Requests this.direction = direction; } - protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.ID ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; + protected override string Target => $@"beatmapsets/search?q={query}&m={ruleset.Id ?? 0}&s={(int)rankStatus}&sort={sortCriteria.ToString().ToLower()}_{directionString}"; } public class GetBeatmapSetsResponse : BeatmapMetadata @@ -63,8 +63,8 @@ namespace osu.Game.Online.API.Requests { return new BeatmapSetInfo { - OnlineBeatmapSetID = onlineId, - Metadata = this, + BeatmapSetOnlineInfoId = onlineId, + BeatmapMetadata = this, OnlineInfo = new BeatmapSetOnlineInfo { Author = new User @@ -99,8 +99,8 @@ namespace osu.Game.Online.API.Requests { return new BeatmapInfo { - Metadata = this, - Ruleset = rulesets.GetRuleset(ruleset), + BeatmapMetadata = this, + RulesetInfo = rulesets.GetRuleset(ruleset), StarDifficulty = starDifficulty, OnlineInfo = new BeatmapOnlineInfo { diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 537fce2548..6bb654f403 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -18,8 +18,8 @@ namespace osu.Game.Online.API.Requests public GetScoresRequest(BeatmapInfo beatmap) { - if (!beatmap.OnlineBeatmapID.HasValue) - throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (!beatmap.BeatmapOnlineInfoId.HasValue) + throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.BeatmapOnlineInfoId)}."); this.beatmap = beatmap; @@ -32,7 +32,7 @@ namespace osu.Game.Online.API.Requests score.ApplyBeatmap(beatmap); } - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; + protected override string Target => $@"beatmaps/{beatmap.BeatmapOnlineInfoId}/scores"; } public class GetScoresResponse @@ -116,7 +116,7 @@ namespace osu.Game.Online.API.Requests public void ApplyBeatmap(BeatmapInfo beatmap) { Beatmap = beatmap; - Ruleset = beatmap.Ruleset; + Ruleset = beatmap.RulesetInfo; // Evaluate the mod string Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c137b8f6f5..6dacef6e8d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -110,7 +110,7 @@ namespace osu.Game configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value); - Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + Ruleset.ValueChanged += r => configRuleset.Value = r.Id ?? 0; } private ScheduledDelegate scoreLoad; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 8e7bfa8a76..f2ee6effb9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -3,7 +3,9 @@ using System; using System.Diagnostics; +using System.Linq; using System.Reflection; +using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Development; @@ -17,7 +19,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; using osu.Game.Online.API; -using SQLite.Net; using osu.Framework.Graphics.Performance; using osu.Game.Database; using osu.Game.Input; @@ -82,14 +83,21 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - private SQLiteConnection createConnection() - { - var conn = Host.Storage.GetDatabase(@"client"); - conn.BusyTimeout = new TimeSpan(TimeSpan.TicksPerSecond * 10); - return conn; - } + //private OsuDbContext dbContext; - private SQLiteConnection connection; + private OsuDbContext createDbContext() + { + var connectionString = Host.Storage.GetDatabaseConnectionString(@"client"); + var context = new OsuDbContext(connectionString); + var connection = context.Database.GetDbConnection(); + connection.Open(); + using (var command = connection.CreateCommand()) + { + command.CommandText = "PRAGMA journal_mode=WAL;"; + command.ExecuteNonQuery(); + } + return context; + } [BackgroundDependencyLoader] private void load() @@ -97,8 +105,10 @@ namespace osu.Game dependencies.Cache(this); dependencies.Cache(LocalConfig); - connection = createConnection(); - connection.CreateTable(); + + using (var dbContext = createDbContext()) + if (dbContext.Database.GetPendingMigrations().Any()) + dbContext.Database.Migrate(); dependencies.Cache(API = new APIAccess { @@ -106,11 +116,11 @@ namespace osu.Game Token = LocalConfig.Get(OsuSetting.Token) }); - dependencies.Cache(RulesetStore = new RulesetStore(connection)); - dependencies.Cache(FileStore = new FileStore(connection, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, API, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager, RulesetStore)); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(connection, RulesetStore)); + dependencies.Cache(RulesetStore = new RulesetStore(createDbContext())); + dependencies.Cache(FileStore = new FileStore(createDbContext(), Host.Storage)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, createDbContext(), RulesetStore, API, Host)); + dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, createDbContext(), Host, BeatmapManager, RulesetStore)); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(createDbContext(), RulesetStore)); dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. @@ -237,7 +247,7 @@ namespace osu.Game LocalConfig.Save(); } - connection?.Dispose(); + //dbContext?.Dispose(); base.Dispose(isDisposing); } diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index a93ccbf704..25e598ffb9 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -43,8 +43,8 @@ namespace osu.Game.Overlays.BeatmapSet beatmapSet = value; Picker.BeatmapSet = author.BeatmapSet = details.BeatmapSet = BeatmapSet; - title.Text = BeatmapSet.Metadata.Title; - artist.Text = BeatmapSet.Metadata.Artist; + title.Text = BeatmapSet.BeatmapMetadata.Title; + artist.Text = BeatmapSet.BeatmapMetadata.Artist; cover?.FadeOut(400, Easing.Out); coverContainer.Add(cover = new DelayedLoadWrapper(new BeatmapSetCover(BeatmapSet) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 4a59591a72..16cb19dbd7 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -33,8 +33,8 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - source.Text = BeatmapSet.Metadata.Source; - tags.Text = BeatmapSet.Metadata.Tags; + source.Text = BeatmapSet.BeatmapMetadata.Source; + tags.Text = BeatmapSet.BeatmapMetadata.Tags; } } diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 1675a2f663..8d3d91fcd2 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -66,13 +66,13 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = localisation.GetUnicodePreference(SetInfo.BeatmapMetadata.TitleUnicode, SetInfo.BeatmapMetadata.Title), TextSize = 18, Font = @"Exo2.0-BoldItalic", }, new OsuSpriteText { - Text = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = localisation.GetUnicodePreference(SetInfo.BeatmapMetadata.ArtistUnicode, SetInfo.BeatmapMetadata.Artist), Font = @"Exo2.0-BoldItalic", }, }, @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.Direct }, new OsuSpriteText { - Text = SetInfo.Metadata.Author, + Text = SetInfo.BeatmapMetadata.Author, TextSize = 14, Font = @"Exo2.0-SemiBoldItalic", Shadow = false, @@ -132,11 +132,11 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Text = $"from {SetInfo.Metadata.Source}", + Text = $"from {SetInfo.BeatmapMetadata.Source}", TextSize = 14, Shadow = false, Colour = colours.Gray5, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.BeatmapMetadata.Source) ? 0f : 1f, }, }, }, diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 6702b7394c..b46ce78654 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -54,13 +54,13 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Current = localisation.GetUnicodePreference(SetInfo.BeatmapMetadata.TitleUnicode, SetInfo.BeatmapMetadata.Title), TextSize = 18, Font = @"Exo2.0-BoldItalic", }, new OsuSpriteText { - Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Current = localisation.GetUnicodePreference(SetInfo.BeatmapMetadata.ArtistUnicode, SetInfo.BeatmapMetadata.Artist), Font = @"Exo2.0-BoldItalic", }, new FillFlowContainer @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Direct }, new OsuSpriteText { - Text = SetInfo.Metadata.Author, + Text = SetInfo.BeatmapMetadata.Author, TextSize = 14, Font = @"Exo2.0-SemiBoldItalic", }, @@ -109,11 +109,11 @@ namespace osu.Game.Overlays.Direct }, new OsuSpriteText { - Text = $"from {SetInfo.Metadata.Source}", + Text = $"from {SetInfo.BeatmapMetadata.Source}", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 14, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.BeatmapMetadata.Source) ? 0f : 1f, }, }, }, diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 28d26d0641..1500723680 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Direct private void Bindable_ValueChanged(RulesetInfo obj) { - iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100); + iconContainer.FadeTo(Ruleset.Id == obj?.Id ? 1f : 0.5f, 100); } public RulesetToggleButton(Bindable bindable, RulesetInfo ruleset) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 5b5003b30f..df08400ee9 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -57,9 +57,9 @@ namespace osu.Game.Overlays var tags = new List(); foreach (var s in beatmapSets) { - artists.Add(s.Metadata.Artist); - songs.Add(s.Metadata.Title); - tags.AddRange(s.Metadata.Tags.Split(' ')); + artists.Add(s.BeatmapMetadata.Artist); + songs.Add(s.BeatmapMetadata.Title); + tags.AddRange(s.BeatmapMetadata.Tags.Split(' ')); } ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); @@ -175,8 +175,8 @@ namespace osu.Game.Overlays private void setAdded(BeatmapSetInfo set) { // if a new map was imported, we should remove it from search results (download completed etc.) - panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); - BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); + panels?.FirstOrDefault(p => p.SetInfo.BeatmapSetOnlineInfoId == set.BeatmapSetOnlineInfoId)?.FadeOut(400).Expire(); + BeatmapSets = BeatmapSets?.Where(b => b.BeatmapSetOnlineInfoId != set.BeatmapSetOnlineInfoId); } private void updateResultCounts() @@ -260,7 +260,7 @@ namespace osu.Game.Overlays { BeatmapSets = r?. Select(response => response.ToBeatmapSet(rulesets)). - Where(b => beatmaps.QueryBeatmapSet(q => q.OnlineBeatmapSetID == b.OnlineBeatmapSetID) == null); + Where(b => beatmaps.QueryBeatmapSet(q => q.BeatmapSetOnlineInfoId == b.BeatmapSetOnlineInfoId) == null); recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); }; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index bd69403831..8442203f14 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(KeyBindingStore store) { - var bindings = store.Query(Ruleset?.ID, variant); + var bindings = store.Query(Ruleset?.Id, variant); foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 723b3f4e96..6a8476dddd 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Music hoverColour = colours.Yellow; artistColour = colours.Gray9; - var metadata = BeatmapSetInfo.Metadata; + var metadata = BeatmapSetInfo.BeatmapMetadata; FilterTerms = metadata.SearchableTerms; Children = new Drawable[] diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 6f1eaded7f..b989b1cb1c 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Music public bool RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { - var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == beatmapSet.ID); + var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.Id == beatmapSet.Id); if (itemToRemove == null) return false; return items.Remove(itemToRemove); @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Music set { foreach (PlaylistItem s in items.Children) - s.Selected = s.BeatmapSetInfo.ID == value?.ID; + s.Selected = s.BeatmapSetInfo.Id == value?.Id; } } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index d05ad85726..e78e3d44be 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Music private void itemSelected(BeatmapSetInfo set) { - if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1)) + if (set.Id == (beatmapBacking.Value?.BeatmapSetInfo?.Id ?? -1)) { beatmapBacking.Value?.Track?.Seek(0); return; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 64d0d628f0..4efd395212 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -302,8 +302,8 @@ namespace osu.Game.Overlays else { //figure out the best direction based on order in playlist. - var last = playlist.BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); + var last = playlist.BeatmapSets.TakeWhile(b => b.Id != current.BeatmapSetInfo?.Id).Count(); + var next = beatmap == null ? -1 : playlist.BeatmapSets.TakeWhile(b => b.Id != beatmap.BeatmapSetInfo?.Id).Count(); direction = last > next ? TransformDirection.Prev : TransformDirection.Next; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 60c1261190..cd18980e32 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Toolbar { foreach (ToolbarModeButton m in modeButtons.Children.Cast()) { - bool isActive = m.Ruleset.ID == ruleset.ID; + bool isActive = m.Ruleset.Id == ruleset.Id; m.Active = isActive; if (isActive) activeButton = m; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 740369b1b6..9c51e1e66a 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -2,26 +2,23 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using SQLite.Net.Attributes; +using System.ComponentModel.DataAnnotations; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { - [PrimaryKey, AutoIncrement] - public int? ID { get; set; } + [Key] + public int? Id { get; set; } - [Indexed(Unique = true)] public string Name { get; set; } - [Indexed(Unique = true)] public string InstantiationInfo { get; set; } - [Indexed] public bool Available { get; set; } public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); - public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; + public bool Equals(RulesetInfo other) => other != null && Id == other.Id && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5eef4a8470..f29929a6aa 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,8 +6,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using Microsoft.EntityFrameworkCore; using osu.Game.Database; -using SQLite.Net; namespace osu.Game.Rulesets { @@ -18,9 +18,9 @@ namespace osu.Game.Rulesets { private static readonly Dictionary loaded_assemblies = new Dictionary(); - public IEnumerable AllRulesets => Query().Where(r => r.Available); + public IEnumerable AllRulesets => Connection.RulesetInfo.Where(r => r.Available); - public RulesetStore(SQLiteConnection connection) : base(connection) + public RulesetStore(OsuDbContext connection) : base(connection) { } @@ -38,54 +38,50 @@ namespace osu.Game.Rulesets protected override void Prepare(bool reset = false) { - - Connection.CreateTable(); - if (reset) { - Connection.DeleteAll(); + Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } - + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())); - Connection.RunInTransaction(() => + //add all legacy modes in correct order + foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { - //add all legacy modes in correct order - foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) + var rulesetInfo = createRulesetInfo(r); + if (Connection.RulesetInfo.SingleOrDefault(rsi=>rsi.Id==rulesetInfo.Id)==null) { - Connection.InsertOrReplace(createRulesetInfo(r)); + Connection.RulesetInfo.Add(rulesetInfo); } + } - //add any other modes - foreach (var r in instances.Where(r => r.LegacyID < 0)) - { - var us = createRulesetInfo(r); - - var existing = Query().Where(ri => ri.InstantiationInfo == us.InstantiationInfo).FirstOrDefault(); - - if (existing == null) - Connection.Insert(us); - } - }); - - Connection.RunInTransaction(() => + //add any other modes + foreach (var r in instances.Where(r => r.LegacyID < 0)) { - //perform a consistency check - foreach (var r in Query()) - { - try - { - r.CreateInstance(); - r.Available = true; - } - catch - { - r.Available = false; - } + var us = createRulesetInfo(r); - Connection.Update(r); + var existing = Connection.RulesetInfo.Where(ri => ri.InstantiationInfo == us.InstantiationInfo).FirstOrDefault(); + + if (existing == null) + Connection.RulesetInfo.Add(us); + } + + //perform a consistency check + foreach (var r in Connection.RulesetInfo) + { + try + { + r.CreateInstance(); + r.Available = true; } - }); + catch + { + r.Available = false; + } + + //Connection.RulesetInfo.Update(r); + } + Connection.SaveChanges(); } private static void loadRulesetFromFile(string file) @@ -107,11 +103,24 @@ namespace osu.Game.Rulesets { Name = ruleset.Description, InstantiationInfo = ruleset.GetType().AssemblyQualifiedName, - ID = ruleset.LegacyID + Id = ruleset.LegacyID }; protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) }; - public RulesetInfo GetRuleset(int id) => Query().First(r => r.ID == id); + public RulesetInfo GetRuleset(int id) => Connection.RulesetInfo.First(r => r.Id == id); + + public RulesetInfo QueryRulesetInfo(Func query) + { + return Connection.RulesetInfo.FirstOrDefault(query); + } + + public List QueryRulesets(Func query = null) + { + var rulesets = Connection.RulesetInfo; + if (query != null) + return rulesets.Where(query).ToList(); + return rulesets.ToList(); + } } } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index c06d31e38f..1604e8cb1c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -11,7 +11,6 @@ using osu.Game.IO.Legacy; using osu.Game.IPC; using osu.Game.Rulesets.Replays; using SharpCompress.Compressors.LZMA; -using SQLite.Net; namespace osu.Game.Rulesets.Scoring { @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; - public ScoreStore(Storage storage, SQLiteConnection connection, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(connection) + public ScoreStore(Storage storage, OsuDbContext connection, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(connection) { this.storage = storage; this.beatmaps = beatmaps; diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 729df02ffd..0e784c06af 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -172,11 +172,11 @@ namespace osu.Game.Rulesets.UI // Apply difficulty adjustments from mods before using Difficulty. foreach (var mod in Mods.OfType()) - mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty); + mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BeatmapDifficulty); // Apply defaults foreach (var h in Beatmap.HitObjects) - h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BeatmapDifficulty); // Post-process the beatmap processor.PostProcess(Beatmap); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 3aeef4bbc9..66e6da3af9 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Menu { var sets = beatmaps.GetAllUsableBeatmapSets(false); if (sets.Count > 0) - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); + setInfo = beatmaps.QueryBeatmapSet(s => s.Id == sets[RNG.Next(0, sets.Count - 1)].Id); } if (setInfo == null) diff --git a/osu.Game/Screens/Multiplayer/DrawableRoom.cs b/osu.Game/Screens/Multiplayer/DrawableRoom.cs index d2f88224c2..f3d648e07b 100644 --- a/osu.Game/Screens/Multiplayer/DrawableRoom.cs +++ b/osu.Game/Screens/Multiplayer/DrawableRoom.cs @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Multiplayer coverContainer.FadeIn(transition_duration); coverContainer.Children = new[] { - new AsyncLoadWrapper(new BeatmapSetCover(value.BeatmapSet) + new AsyncLoadWrapper(new BeatmapSetCover(value.BeatmapSetInfo) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -239,9 +239,9 @@ namespace osu.Game.Screens.Multiplayer }) { RelativeSizeAxes = Axes.Both }, }; - beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title); + beatmapTitle.Current = localisation.GetUnicodePreference(value.BeatmapMetadata.TitleUnicode, value.BeatmapMetadata.Title); beatmapDash.Text = @" - "; - beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist); + beatmapArtist.Current = localisation.GetUnicodePreference(value.BeatmapMetadata.ArtistUnicode, value.BeatmapMetadata.Artist); } else { diff --git a/osu.Game/Screens/Multiplayer/RoomInspector.cs b/osu.Game/Screens/Multiplayer/RoomInspector.cs index 66ce51b428..aabbd2be5f 100644 --- a/osu.Game/Screens/Multiplayer/RoomInspector.cs +++ b/osu.Game/Screens/Multiplayer/RoomInspector.cs @@ -331,7 +331,7 @@ namespace osu.Game.Screens.Multiplayer coverContainer.FadeIn(transition_duration); coverContainer.Children = new[] { - new AsyncLoadWrapper(new BeatmapSetCover(value.BeatmapSet) + new AsyncLoadWrapper(new BeatmapSetCover(value.BeatmapSetInfo) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -341,10 +341,10 @@ namespace osu.Game.Screens.Multiplayer }) { RelativeSizeAxes = Axes.Both }, }; - beatmapTitle.Current = localisation.GetUnicodePreference(value.Metadata.TitleUnicode, value.Metadata.Title); + beatmapTitle.Current = localisation.GetUnicodePreference(value.BeatmapMetadata.TitleUnicode, value.BeatmapMetadata.Title); beatmapDash.Text = @" - "; - beatmapArtist.Current = localisation.GetUnicodePreference(value.Metadata.ArtistUnicode, value.Metadata.Artist); - beatmapAuthor.Text = $"mapped by {value.Metadata.Author}"; + beatmapArtist.Current = localisation.GetUnicodePreference(value.BeatmapMetadata.ArtistUnicode, value.BeatmapMetadata.Artist); + beatmapAuthor.Text = $"mapped by {value.BeatmapMetadata.Author}"; } else { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e120c7f193..9b0172a1b2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -99,18 +99,18 @@ namespace osu.Game.Screens.Play if (beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; + ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.RulesetInfo; var rulesetInstance = ruleset.CreateInstance(); try { - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.ID == beatmap.BeatmapInfo.Ruleset.ID); + RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working, ruleset.Id == beatmap.BeatmapInfo.RulesetInfo.Id); } catch (BeatmapInvalidForRulesetException) { // we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. - ruleset = beatmap.BeatmapInfo.Ruleset; + ruleset = beatmap.BeatmapInfo.RulesetInfo; rulesetInstance = ruleset.CreateInstance(); RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap, true); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 9366797f47..92369c4f29 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -177,7 +177,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(LocalisationEngine localisation) { - var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + var metadata = beatmap?.BeatmapInfo?.BeatmapMetadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index b01410cff5..694391a5ff 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -324,9 +324,9 @@ namespace osu.Game.Screens.Ranking title.Colour = artist.Colour = colours.BlueDarker; versionMapper.Colour = colours.Gray8; - versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.Metadata.Author}"; - title.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); - artist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); + versionMapper.Text = $"{beatmap.Version} - mapped by {beatmap.BeatmapMetadata.Author}"; + title.Current = localisation.GetUnicodePreference(beatmap.BeatmapMetadata.TitleUnicode, beatmap.BeatmapMetadata.Title); + artist.Current = localisation.GetUnicodePreference(beatmap.BeatmapMetadata.ArtistUnicode, beatmap.BeatmapMetadata.Artist); } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index abd288baf2..0c31f51c2b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -113,15 +113,15 @@ namespace osu.Game.Screens.Select }); } - public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)); + public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.Id == beatmapSet.Id)); internal void UpdateBeatmap(BeatmapInfo beatmap) { // todo: this method should not run more than once for the same BeatmapSetInfo. - var set = manager.Refresh(beatmap.BeatmapSet); + var set = manager.Refresh(beatmap.BeatmapSetInfo); // todo: this method should be smarter as to not recreate panels that haven't changed, etc. - var group = groups.Find(b => b.BeatmapSet.ID == set.ID); + var group = groups.Find(b => b.BeatmapSet.Id == set.Id); if (group == null) return; @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Select if (selectedGroup == group && newGroup.BeatmapPanels.Count > 0) { var newSelection = - newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ?? + newGroup.BeatmapPanels.Find(p => p.Beatmap.Id == selectedPanel?.Beatmap.Id) ?? newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, group.BeatmapPanels.IndexOf(selectedPanel))]; selectGroup(newGroup, newSelection); @@ -337,8 +337,8 @@ namespace osu.Game.Screens.Select { foreach (var b in beatmapSet.Beatmaps) { - if (b.Metadata == null) - b.Metadata = beatmapSet.Metadata; + if (b.BeatmapMetadata == null) + b.BeatmapMetadata = beatmapSet.BeatmapMetadata; } return new BeatmapGroup(beatmapSet, manager) diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index aa37705cdf..7bd50e1fea 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Select public BeatmapDeleteDialog(BeatmapSetInfo beatmap) { - BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; + BodyText = $@"{beatmap.BeatmapMetadata?.Artist} - {beatmap.BeatmapMetadata?.Title}"; Icon = FontAwesome.fa_trash_o; HeaderText = @"Confirm deletion of"; diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a98362e89c..9d8c76c1fe 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -188,8 +188,8 @@ namespace osu.Game.Screens.Select ratingsContainer.FadeIn(transition_duration); advanced.Beatmap = Beatmap; description.Text = Beatmap.Version; - source.Text = Beatmap.Metadata.Source; - tags.Text = Beatmap.Metadata.Tags; + source.Text = Beatmap.BeatmapMetadata.Source; + tags.Text = Beatmap.BeatmapMetadata.Tags; var requestedBeatmap = Beatmap; if (requestedBeatmap.Metrics == null) @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Select advanced.Beatmap = new BeatmapInfo { StarDifficulty = 0, - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 0, DrainRate = 0, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 76c384b84c..a940c49b07 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select public BufferedWedgeInfo(WorkingBeatmap beatmap) { BeatmapInfo beatmapInfo = beatmap.BeatmapInfo; - BeatmapMetadata metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + BeatmapMetadata metadata = beatmapInfo.BeatmapMetadata ?? beatmap.BeatmapSetInfo?.BeatmapMetadata ?? new BeatmapMetadata(); List labels = new List(); @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select })); //get statistics from the current ruleset. - labels.AddRange(beatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); + labels.AddRange(beatmapInfo.RulesetInfo.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); } PixelSnapping = true; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f1215ab33d..551f67c51b 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -29,20 +29,20 @@ namespace osu.Game.Screens.Select.Details beatmap = value; //mania specific - if ((Beatmap?.Ruleset?.ID ?? 0) == 3) + if ((Beatmap?.RulesetInfo?.Id ?? 0) == 3) { firstValue.Title = "Key Amount"; - firstValue.Value = (int)Math.Round(Beatmap?.Difficulty?.CircleSize ?? 0); + firstValue.Value = (int)Math.Round(Beatmap?.BeatmapDifficulty?.CircleSize ?? 0); } else { firstValue.Title = "Circle Size"; - firstValue.Value = Beatmap?.Difficulty?.CircleSize ?? 0; + firstValue.Value = Beatmap?.BeatmapDifficulty?.CircleSize ?? 0; } - hpDrain.Value = beatmap.Difficulty?.DrainRate ?? 0; - accuracy.Value = beatmap.Difficulty?.OverallDifficulty ?? 0; - approachRate.Value = beatmap.Difficulty?.ApproachRate ?? 0; + hpDrain.Value = beatmap.BeatmapDifficulty?.DrainRate ?? 0; + accuracy.Value = beatmap.BeatmapDifficulty?.OverallDifficulty ?? 0; + approachRate.Value = beatmap.BeatmapDifficulty?.ApproachRate ?? 0; starDifficulty.Value = (float)beatmap.StarDifficulty; } } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index a1fea4a41d..f0bf6c94e7 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -23,12 +23,12 @@ namespace osu.Game.Screens.Select { var set = g.BeatmapSet; - bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); + bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetInfoId == (Ruleset?.Id ?? 0)); bool match = hasCurrentMode; if (!string.IsNullOrEmpty(SearchText)) - match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); + match &= set.BeatmapMetadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); switch (g.State) { @@ -45,13 +45,13 @@ namespace osu.Game.Screens.Select { default: case SortMode.Artist: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); + groups.Sort((x, y) => string.Compare(x.BeatmapSet.BeatmapMetadata.Artist, y.BeatmapSet.BeatmapMetadata.Artist, StringComparison.InvariantCultureIgnoreCase)); break; case SortMode.Title: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); + groups.Sort((x, y) => string.Compare(x.BeatmapSet.BeatmapMetadata.Title, y.BeatmapSet.BeatmapMetadata.Title, StringComparison.InvariantCultureIgnoreCase)); break; case SortMode.Author: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author, y.BeatmapSet.Metadata.Author, StringComparison.InvariantCultureIgnoreCase)); + groups.Sort((x, y) => string.Compare(x.BeatmapSet.BeatmapMetadata.Author, y.BeatmapSet.BeatmapMetadata.Author, StringComparison.InvariantCultureIgnoreCase)); break; case SortMode.Difficulty: groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 7d65b8b648..df08d4e6f6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; getScoresRequest?.Cancel(); - if (api == null || Beatmap?.OnlineBeatmapID == null) return; + if (api == null || Beatmap?.BeatmapOnlineInfoId == null) return; loading.Show(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 836ed465c3..6b0d9f1eb7 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select // In these cases, the other component has already loaded the beatmap, so we don't need to do so again. if (beatmap?.Equals(Beatmap.Value.BeatmapInfo) != true) { - bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value.BeatmapInfo.BeatmapSetInfoID; + bool preview = beatmap?.BeatmapSetInfoId != Beatmap.Value.BeatmapInfo.BeatmapSetInfoId; Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap); ensurePlayingSelected(preview); @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Select } else { - if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) + if (beatmap.BeatmapSetInfoId == beatmapNoDebounce?.BeatmapSetInfoId) sampleChangeDifficulty.Play(); else sampleChangeBeatmap.Play(); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 59cbe74650..bad31ee55a 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -37,7 +37,7 @@ namespace osu.Game.Storyboards /// public bool ReplacesBackground(BeatmapInfo beatmapInfo) { - var backgroundPath = beatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); + var backgroundPath = beatmapInfo.BeatmapSetInfo?.BeatmapMetadata?.BackgroundFile?.ToLowerInvariant(); if (backgroundPath == null) return false; diff --git a/osu.Game/Tests/Platform/TestStorage.cs b/osu.Game/Tests/Platform/TestStorage.cs index 9f76df2a58..be893f7e9c 100644 --- a/osu.Game/Tests/Platform/TestStorage.cs +++ b/osu.Game/Tests/Platform/TestStorage.cs @@ -1,12 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; +using Microsoft.Data.Sqlite; using osu.Framework.Platform; -using SQLite.Net; -using SQLite.Net.Interop; -using SQLite.Net.Platform.Generic; -using SQLite.Net.Platform.Win32; namespace osu.Game.Tests.Platform { @@ -16,14 +12,9 @@ namespace osu.Game.Tests.Platform { } - public override SQLiteConnection GetDatabase(string name) + public override string GetDatabaseConnectionString(string name) { - ISQLitePlatform platform; - if (RuntimeInfo.IsWindows) - platform = new SQLitePlatformWin32(); - else - platform = new SQLitePlatformGeneric(); - return new SQLiteConnection(platform, @":memory:"); + return "DataSource=:memory:"; } } -} \ No newline at end of file +} diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs index cd4d97425b..e126980edf 100644 --- a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs @@ -24,12 +24,12 @@ namespace osu.Game.Tests.Visual AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo { Version = "All Metrics", - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has all the metrics", }, - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 7, DrainRate = 1, @@ -48,12 +48,12 @@ namespace osu.Game.Tests.Visual AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo { Version = "Only Ratings", - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has ratings metrics but not retries or fails", }, - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 6, DrainRate = 9, @@ -70,12 +70,12 @@ namespace osu.Game.Tests.Visual AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo { Version = "Only Retries and Fails", - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has retries and fails but no ratings", }, - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 3.7f, DrainRate = 6, @@ -93,12 +93,12 @@ namespace osu.Game.Tests.Visual AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo { Version = "No Metrics", - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Source = "osu!lazer", Tags = "this beatmap has no metrics", }, - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 5, diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs index 76ed9979ca..e8027978b4 100644 --- a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual { overlay.ShowBeatmapSet(new BeatmapSetInfo { - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Title = @"Lachryma ", Artist = @"Kaneko Chiharu", @@ -64,8 +64,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 1.36, Version = @"BASIC", - Ruleset = mania, - Difficulty = new BeatmapDifficulty + RulesetInfo = mania, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 6.5f, @@ -92,8 +92,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 2.22, Version = @"NOVICE", - Ruleset = mania, - Difficulty = new BeatmapDifficulty + RulesetInfo = mania, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 7, @@ -120,8 +120,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 3.49, Version = @"ADVANCED", - Ruleset = mania, - Difficulty = new BeatmapDifficulty + RulesetInfo = mania, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 7.5f, @@ -148,8 +148,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 4.24, Version = @"EXHAUST", - Ruleset = mania, - Difficulty = new BeatmapDifficulty + RulesetInfo = mania, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 8, @@ -176,8 +176,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 5.26, Version = @"GRAVITY", - Ruleset = mania, - Difficulty = new BeatmapDifficulty + RulesetInfo = mania, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 8.5f, @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Visual { overlay.ShowBeatmapSet(new BeatmapSetInfo { - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { Title = @"Soumatou Labyrinth", Artist = @"Yunomi with Momobako&miko", @@ -238,8 +238,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 1.40, Version = @"yzrin's Kantan", - Ruleset = taiko, - Difficulty = new BeatmapDifficulty + RulesetInfo = taiko, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 7, @@ -266,8 +266,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 2.23, Version = @"Futsuu", - Ruleset = taiko, - Difficulty = new BeatmapDifficulty + RulesetInfo = taiko, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 6, @@ -294,8 +294,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 3.19, Version = @"Muzukashii", - Ruleset = taiko, - Difficulty = new BeatmapDifficulty + RulesetInfo = taiko, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 6, @@ -322,8 +322,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 3.97, Version = @"Charlotte's Oni", - Ruleset = taiko, - Difficulty = new BeatmapDifficulty + RulesetInfo = taiko, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 6, @@ -350,8 +350,8 @@ namespace osu.Game.Tests.Visual { StarDifficulty = 5.08, Version = @"Labyrinth Oni", - Ruleset = taiko, - Difficulty = new BeatmapDifficulty + RulesetInfo = taiko, + BeatmapDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 5, diff --git a/osu.Game/Tests/Visual/TestCaseDirect.cs b/osu.Game/Tests/Visual/TestCaseDirect.cs index 1fb9dbea8f..cb34d28578 100644 --- a/osu.Game/Tests/Visual/TestCaseDirect.cs +++ b/osu.Game/Tests/Visual/TestCaseDirect.cs @@ -41,8 +41,8 @@ namespace osu.Game.Tests.Visual { new BeatmapSetInfo { - OnlineBeatmapSetID = 578332, - Metadata = new BeatmapMetadata + BeatmapSetOnlineInfoId = 578332, + BeatmapMetadata = new BeatmapMetadata { Title = @"OrVid", Artist = @"An", @@ -65,16 +65,16 @@ namespace osu.Game.Tests.Visual { new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 5.35f, - Metadata = new BeatmapMetadata(), + BeatmapMetadata = new BeatmapMetadata(), }, }, }, new BeatmapSetInfo { - OnlineBeatmapSetID = 599627, - Metadata = new BeatmapMetadata + BeatmapSetOnlineInfoId = 599627, + BeatmapMetadata = new BeatmapMetadata { Title = @"tiny lamp", Artist = @"fhana", @@ -97,16 +97,16 @@ namespace osu.Game.Tests.Visual { new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 5.81f, - Metadata = new BeatmapMetadata(), + BeatmapMetadata = new BeatmapMetadata(), }, }, }, new BeatmapSetInfo { - OnlineBeatmapSetID = 513268, - Metadata = new BeatmapMetadata + BeatmapSetOnlineInfoId = 513268, + BeatmapMetadata = new BeatmapMetadata { Title = @"At Gwanghwamun", Artist = @"KYUHYUN", @@ -129,31 +129,31 @@ namespace osu.Game.Tests.Visual { new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 0.9f, - Metadata = new BeatmapMetadata(), + BeatmapMetadata = new BeatmapMetadata(), }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 1.1f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 2.02f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 3.49f, }, }, }, new BeatmapSetInfo { - OnlineBeatmapSetID = 586841, - Metadata = new BeatmapMetadata + BeatmapSetOnlineInfoId = 586841, + BeatmapMetadata = new BeatmapMetadata { Title = @"RHAPSODY OF BLUE SKY", Artist = @"fhana", @@ -176,43 +176,43 @@ namespace osu.Game.Tests.Visual { new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 1.26f, - Metadata = new BeatmapMetadata(), + BeatmapMetadata = new BeatmapMetadata(), }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 2.01f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 2.87f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 3.76f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 3.93f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 4.37f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 5.13f, }, new BeatmapInfo { - Ruleset = ruleset, + RulesetInfo = ruleset, StarDifficulty = 5.42f, }, }, diff --git a/osu.Game/Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game/Tests/Visual/TestCaseDrawableRoom.cs index 7113bcbff5..a57bf64c9a 100644 --- a/osu.Game/Tests/Visual/TestCaseDrawableRoom.cs +++ b/osu.Game/Tests/Visual/TestCaseDrawableRoom.cs @@ -43,13 +43,13 @@ namespace osu.Game.Tests.Visual Value = new BeatmapInfo { StarDifficulty = 4.65, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata + RulesetInfo = rulesets.GetRuleset(3), + BeatmapMetadata = new BeatmapMetadata { Title = @"Critical Crystal", Artist = @"Seiryu", }, - BeatmapSet = new BeatmapSetInfo + BeatmapSetInfo = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo { @@ -81,13 +81,13 @@ namespace osu.Game.Tests.Visual Value = new BeatmapInfo { StarDifficulty = 1.96, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata + RulesetInfo = rulesets.GetRuleset(0), + BeatmapMetadata = new BeatmapMetadata { Title = @"Serendipity", Artist = @"ZAQ", }, - BeatmapSet = new BeatmapSetInfo + BeatmapSetInfo = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo { diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index feff7497d8..12ac7240d6 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -37,12 +38,11 @@ namespace osu.Game.Tests.Visual { var storage = new TestStorage(@"TestCasePlaySongSelect"); - var backingDatabase = storage.GetDatabase(@"client"); - backingDatabase.CreateTable(); + var dbConnectionString = storage.GetDatabaseConnectionString(@"client"); - dependencies.Cache(rulesets = new RulesetStore(backingDatabase)); - dependencies.Cache(files = new FileStore(backingDatabase, storage)); - dependencies.Cache(manager = new BeatmapManager(storage, files, backingDatabase, rulesets, null)); + dependencies.Cache(rulesets = new RulesetStore(new OsuDbContext(dbConnectionString))); + dependencies.Cache(files = new FileStore(new OsuDbContext(dbConnectionString), storage)); + dependencies.Cache(manager = new BeatmapManager(storage, files, new OsuDbContext(dbConnectionString), rulesets, null)); for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); @@ -60,11 +60,11 @@ namespace osu.Game.Tests.Visual { return new BeatmapSetInfo { - OnlineBeatmapSetID = 1234 + i, + BeatmapSetOnlineInfoId = 1234 + i, Hash = "d8e8fca2dc0f896fd7cb4cb0031ba249", - Metadata = new BeatmapMetadata + BeatmapMetadata = new BeatmapMetadata { - OnlineBeatmapSetID = 1234 + i, + BeatmapSetOnlineInfoId = 1234 + i, // Create random metadata, then we can check if sorting works based on these Artist = "MONACA " + RNG.Next(0, 9), Title = "Black Song " + RNG.Next(0, 9), @@ -74,33 +74,33 @@ namespace osu.Game.Tests.Visual { new BeatmapInfo { - OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.Query().First(), + BeatmapOnlineInfoId = 1234 + i, + RulesetInfo = rulesets.QueryRulesets().First(), Path = "normal.osu", Version = "Normal", - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, } }, new BeatmapInfo { - OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.Query().First(), + BeatmapOnlineInfoId = 1235 + i, + RulesetInfo = rulesets.QueryRulesets().First(), Path = "hard.osu", Version = "Hard", - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { OverallDifficulty = 5, } }, new BeatmapInfo { - OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.Query().First(), + BeatmapOnlineInfoId = 1236 + i, + RulesetInfo = rulesets.QueryRulesets().First(), Path = "insane.osu", Version = "Insane", - Difficulty = new BeatmapDifficulty + BeatmapDifficulty = new BeatmapDifficulty { OverallDifficulty = 7, } diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 4a25a52e36..98c573f62c 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -39,10 +39,11 @@ namespace osu.Game.Tests.Visual Colour = Color4.Black, }); - foreach (var r in rulesets.Query()) + var queryRulesets = rulesets.QueryRulesets(r=>true); + foreach (var r in queryRulesets) AddStep(r.Name, () => loadPlayerFor(r)); - loadPlayerFor(rulesets.Query().First()); + loadPlayerFor(queryRulesets.First()); } protected virtual Beatmap CreateBeatmap() @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual { var beatmap = CreateBeatmap(); - beatmap.BeatmapInfo.Ruleset = r; + beatmap.BeatmapInfo.RulesetInfo = r; var instance = r.CreateInstance(); diff --git a/osu.Game/Tests/Visual/TestCaseResults.cs b/osu.Game/Tests/Visual/TestCaseResults.cs index 62154a535a..a0146de7d0 100644 --- a/osu.Game/Tests/Visual/TestCaseResults.cs +++ b/osu.Game/Tests/Visual/TestCaseResults.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual if (beatmap == null) { - var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); + var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetInfoId == 0); if (beatmapInfo != null) beatmap = beatmaps.GetWorkingBeatmap(beatmapInfo); } diff --git a/osu.Game/Tests/Visual/TestCaseRoomInspector.cs b/osu.Game/Tests/Visual/TestCaseRoomInspector.cs index dd773b361a..41b722841c 100644 --- a/osu.Game/Tests/Visual/TestCaseRoomInspector.cs +++ b/osu.Game/Tests/Visual/TestCaseRoomInspector.cs @@ -32,14 +32,14 @@ namespace osu.Game.Tests.Visual Value = new BeatmapInfo { StarDifficulty = 3.7, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata + RulesetInfo = rulesets.GetRuleset(3), + BeatmapMetadata = new BeatmapMetadata { Title = @"Platina", Artist = @"Maaya Sakamoto", Author = @"uwutm8", }, - BeatmapSet = new BeatmapSetInfo + BeatmapSetInfo = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo { @@ -99,14 +99,14 @@ namespace osu.Game.Tests.Visual Value = new BeatmapInfo { StarDifficulty = 7.07, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata + RulesetInfo = rulesets.GetRuleset(0), + BeatmapMetadata = new BeatmapMetadata { Title = @"FREEDOM DIVE", Artist = @"xi", Author = @"Nakagawa-Kanon", }, - BeatmapSet = new BeatmapSetInfo + BeatmapSetInfo = new BeatmapSetInfo { OnlineInfo = new BeatmapSetOnlineInfo { diff --git a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs index d0761e5841..29623fb05f 100644 --- a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs +++ b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs @@ -48,8 +48,8 @@ namespace osu.Game.Tests.Visual HitObjects = objects, BeatmapInfo = new BeatmapInfo { - Difficulty = new BeatmapDifficulty(), - Metadata = new BeatmapMetadata() + BeatmapDifficulty = new BeatmapDifficulty(), + BeatmapMetadata = new BeatmapMetadata() } }; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bdca48ccdf..afccbe9dc1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -124,6 +124,49 @@ $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True + + + ..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll + + + ..\packages\Microsoft.EntityFrameworkCore.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll + + + ..\packages\Microsoft.EntityFrameworkCore.Design.2.0.0\lib\net461\Microsoft.EntityFrameworkCore.Design.dll + + + ..\packages\Microsoft.EntityFrameworkCore.Relational.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll + + + ..\packages\Microsoft.EntityFrameworkCore.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Sqlite.dll + + + ..\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll True @@ -157,6 +200,9 @@ $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True + + ..\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll + $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True @@ -165,29 +211,43 @@ $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll True - - $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll - True + + ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_green.dll - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net40\SQLite.Net.Platform.Generic.dll - True + + ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_v2.dll - - $(SolutionDir)\packages\SQLite.Net-PCL.3.1.1\lib\net4\SQLite.Net.Platform.Win32.dll - True + + ..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll - - $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll - True + + ..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll True + + ..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll + + + ..\packages\System.ComponentModel.Annotations.4.4.0\lib\net461\System.ComponentModel.Annotations.dll + + + + + + ..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll + + + ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + @@ -287,7 +347,7 @@ - + @@ -800,6 +860,9 @@ + + + + + + + + + + diff --git a/osu.Game/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs similarity index 95% rename from osu.Game/OsuGameDesktop.cs rename to osu.Desktop/OsuGameDesktop.cs index 47e64a0d5b..f4fb10a496 100644 --- a/osu.Game/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -9,12 +9,13 @@ using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; +using osu.Desktop.Overlays; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; -using osu.Game.Overlays; +using osu.Game; using osu.Game.Screens.Menu; -namespace osu.Game +namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { diff --git a/osu.Game/OsuTestBrowser.cs b/osu.Desktop/OsuTestBrowser.cs similarity index 92% rename from osu.Game/OsuTestBrowser.cs rename to osu.Desktop/OsuTestBrowser.cs index b0864e441f..23617de1c0 100644 --- a/osu.Game/OsuTestBrowser.cs +++ b/osu.Desktop/OsuTestBrowser.cs @@ -3,9 +3,10 @@ using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game; using osu.Game.Screens.Backgrounds; -namespace osu.Game +namespace osu.Desktop { internal class OsuTestBrowser : OsuGameBase { diff --git a/osu.Game/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs similarity index 96% rename from osu.Game/Overlays/VersionManager.cs rename to osu.Desktop/Overlays/VersionManager.cs index 7b0b3520cb..e7c3370354 100644 --- a/osu.Game/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -13,15 +13,17 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; +using osu.Game; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using OpenTK; using OpenTK.Graphics; using Squirrel; -namespace osu.Game.Overlays +namespace osu.Desktop.Overlays { public class VersionManager : OverlayContainer { diff --git a/osu.Game/Program.cs b/osu.Desktop/Program.cs similarity index 95% rename from osu.Game/Program.cs rename to osu.Desktop/Program.cs index 8044e9fa87..720b38144c 100644 --- a/osu.Game/Program.cs +++ b/osu.Desktop/Program.cs @@ -8,7 +8,7 @@ using osu.Framework; using osu.Framework.Platform; using osu.Game.IPC; -namespace osu.Game +namespace osu.Desktop { public static class Program { diff --git a/osu.Desktop/Properties/AssemblyInfo.cs b/osu.Desktop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fe7ad20124 --- /dev/null +++ b/osu.Desktop/Properties/AssemblyInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("osu!lazer")] +[assembly: AssemblyDescription("click the circles. to the beat.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("ppy Pty Ltd")] +[assembly: AssemblyProduct("osu!lazer")] +[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("55e28cb2-7b6c-4595-8dcc-9871d8aad7e9")] + +[assembly: AssemblyVersion("0.0.0")] +[assembly: AssemblyFileVersion("0.0.0")] diff --git a/osu.Desktop/Properties/app.manifest b/osu.Desktop/Properties/app.manifest new file mode 100644 index 0000000000..555db8513d --- /dev/null +++ b/osu.Desktop/Properties/app.manifest @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config new file mode 100644 index 0000000000..824430b24a --- /dev/null +++ b/osu.Desktop/app.config @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/osu.Game/lazer.ico b/osu.Desktop/lazer.ico similarity index 100% rename from osu.Game/lazer.ico rename to osu.Desktop/lazer.ico diff --git a/osu.Desktop/osu!.res b/osu.Desktop/osu!.res new file mode 100644 index 0000000000000000000000000000000000000000..7c70e30401fc50ef59dc7b34bb4c834175a957a9 GIT binary patch literal 156596 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 z
M;zWr$UbMIZbG-27T%o(X~~T;tjug4DeSSQJa! zCOl-23AhFaah+Fn}T;BA_UelSCB(MI}d(oO6x>iX@4WbIy54wN=AA z%JK8O@7Zs6|NVEatLUbur@QVaKTlOxpMNcJf|qRix~M}*WrWm)3WCC+xR^TYL?R_q zQ>@6^$|5kgw9Hi_a6h{0&j1c}q&{exJAAj)LQ?;ADos!Gz9_A1wjC8eGU=&yUFCsZ zOZvXU_tSD$y7a3%NMFQNG8^x`qkWAdk2q|U*4D*K_)>s#%kps^4xS)thf0CkLI)X5 z1$U`IX6ax@fvx8*^6jzuHe)2l!6QlIA~PReB{i6;UsLdI(fXtB&bKdP`o#K{Uy7r=!BnhlzvC`}~ z*G`4c&$b_oXi?<0<~$>{Zlf^$o~l45z{z^ju*>*`mLq&x%GW^e)=6-n$iO7qv99bFFZVVD2d!u@T7cWzUd*`)OwSjz&NUb*+K=dWNkl_$#O>_}vNiYBXa@O~jf!=3Wnb3FYL%QqQs zuz0xl*RvlW?vGll=PF-iI7Y^;QlCV8lcB&cmVo<$vU_Oh^F=pfYs_l9)^;x!7mbig zxs>fh&RN`2%MU}xxukv5KJL$>F`Zn+^q3K8v=q6wGzIfxdgr=Xd5i2znH{B4{rqI; zRy7PIM#Smec{)u8{Y_C!#g#IIsb|nL=Hut#;n5nb^0z*xpkU?xP*p%Gckzf|oEX)O z5W-#0X0Nbqqbj<|(-m2{&r||g-$rNPzDw=BY(-8nRWQT6We+apJhO1A9zF)iAjc_{)k z9qM8}_{2YA?%iX~2VV}lKQrRH5 zuWeGNWPZ%XC4HWgclj}k&ccl}?A455`Z)KoL3PXPc{V#qqmP3{%bG=I^w%C%iyanU*-IQ4Hb%IEg2ki9f?AhEo< z(!KYy>&I;>1ra=#K7S1z5#uY+=?1esm5;JYSNO8f-Xh5KkV{Qd>R>yo8+m@ahsT}s zW~XGYetUGMi=UOC?S!(C*?wov2dAD4)excS3>dK@>(`@+r{brot=Ekf#n*-vZ!@N9 zV_JPB2Nj4sg#ALp>Wi5b?4uP5uBCFs7VLV%%yuo~%8(yJ&H22>2No}0iw`fcI%hxd z^xYW|9r66YWDH5|Q??)VyP{l!>YN9b6emWP*cF%#6%q$Agt?vxYG>UOq)JFLs%f*Z zmU>Uk=Iw21X3XyP({GJ}>T({v{>bvJD|+oQKa<<7u#yT3#S1A8-*~houDpJbFZ_hO zF@!e6n23D`YV*-5THEGKd< z(Lp&{bbcaxvg35UH7mhDdO*Oq?ksYZMoUxO*DhVY%yEy6x?>J!3{rAl7Dg;(cx zy}8*I_Jb?@Ky)yTg!lG{e7hqyI>D|4Cw9=&Kh2$y-t}WPu9X>6!_97#Zc=Wd`{|se z_+Im1Q>V~My5oF76!yi*oDZh2IJH+|!ihs^i&PBTXN^Kg=?GkOS;V!1TSx;dD6bn8 zo68Jy)b5vJ(tY*DW@Gs7t%d7;^br#>MlepqarK$&1i2oN9)*}M4Yz^S4tQIg43 z@*dLNd^9~R=VUW)GII3~GZz@~D(aswBjFXtl59Pd_?*DY+01*U>^i5jLEKQWuw9g5 zn(h4RF}ZiU7}5@@Vur1ANMycLw%7%KB-IsPBFWJ8b$_&U-d?NRS$i% zG3;j=BgF3x~;G9+J^Mt8v6@qdW zPtu$0egpDc#;nrksYd8dc}^xB^H?&l&$Ji{dMh7B&%t_!EB^A6GEO?Tk?nL2Q4J%a z)#6~)GSFh?%>Je6^`qo{XLK&rz?IMf#~{>meGZAQ Y67-Oxvlp}X1soSQ}Ql{ox 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⩔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 T`Fx#}9bfvNhl?OJ7=!*lN4_$= literal 0 HcmV?d00001 diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj new file mode 100644 index 0000000000..fad297fa0a --- /dev/null +++ b/osu.Desktop/osu.Desktop.csproj @@ -0,0 +1,260 @@ + + + + {419659FD-72EA-4678-9EB8-B22A746CED70} + Debug + AnyCPU + WinExe + Properties + osu.Desktop + osu! + 3CF060CD28877D0E3112948951A64B2A7CEEC909 + codesigning.pfx + false + false + false + + + 3.5 + + + osu.Desktop.Program + OnOutputUpdated + false + LocalIntranet + v4.6.1 + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 2 + 1.0.0.%2a + false + true + 12.0.0 + 2.0 + + + + + + + true + full + false + bin\Debug\ + DEBUG + prompt + 0 + true + false + AnyCPU + true + AllRules.ruleset + false + false + false + + + 6 + + + none + true + bin\Release\ + CuttingEdge NoUpdate + prompt + 4 + true + false + AnyCPU + true + AllRules.ruleset + false + false + + + + + + + lazer.ico + + + Properties\app.manifest + + + true + bin\Debug\ + DEBUG + true + 0 + true + full + AnyCPU + false + 6 + prompt + AllRules.ruleset + --tests + + + + $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.dll + True + + + $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.MsDelta.dll + True + + + $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll + True + + + $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll + True + + + $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll + True + + + $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll + True + + + $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll + True + + + + ..\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll + True + + + ..\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll + True + + + ..\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll + True + + + $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll + True + + + ..\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll + True + + + + + + + + + osu.licenseheader + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + true + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + false + + + False + .NET Framework 3.5 SP1 + false + + + + + + + + + + + + + + + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} + osu.Framework + + + {d9a367c9-4c1a-489f-9b05-a0cea2b53b58} + osu.Game.Resources + + + {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} + osu.Game.Rulesets.Catch + + + {48f4582b-7687-4621-9cbe-5c24197cb536} + osu.Game.Rulesets.Mania + + + {c92a607b-1fdd-4954-9f92-03ff547d9080} + osu.Game.Rulesets.Osu + + + {f167e17a-7de6-4af5-b920-a5112296c695} + osu.Game.Rulesets.Taiko + + + {2a66dd92-adb1-4994-89e2-c94e04acda0d} + osu.Game + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec new file mode 100644 index 0000000000..4c529f57e5 --- /dev/null +++ b/osu.Desktop/osu.nuspec @@ -0,0 +1,26 @@ + + + + osulazer + 0.0.0 + osulazer + ppy Pty Ltd + Dean Herbert + https://osu.ppy.sh/ + https://puu.sh/tYyXZ/9a01a5d1b0.ico + false + click the circles. to the beat. +

click the circles. + testing + Copyright ppy Pty Ltd 2007-2017 + en-AU + + + + + + + + + + diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config new file mode 100644 index 0000000000..0ec2cc196d --- /dev/null +++ b/osu.Desktop/packages.config @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index ba2c7a5f2e..83b16997a7 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -16,7 +16,7 @@ true full false - ..\osu.Game\bin\Debug\ + bin\Debug\ DEBUG;TRACE prompt 4 @@ -26,7 +26,7 @@ pdbonly true - ..\osu.Game\bin\Release\ + bin\Release\ TRACE prompt 4 diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 967f23bfd3..bacb4185b2 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -16,7 +16,7 @@ true full false - ..\osu.Game\bin\Debug\ + bin\Debug\ DEBUG;TRACE prompt 4 @@ -26,7 +26,7 @@ pdbonly true - ..\osu.Game\bin\Release\ + bin\Release\ TRACE prompt 4 diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 6bad45b8ca..f812132dc0 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -17,7 +17,7 @@ true full false - ..\osu.Game\bin\Debug\ + bin\Debug\ DEBUG;TRACE prompt 4 @@ -27,7 +27,7 @@ pdbonly true - ..\osu.Game\bin\Release\ + bin\Release\ TRACE prompt 4 diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 2c49be287b..d38b24f933 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -16,7 +16,7 @@ true full false - ..\osu.Game\bin\Debug\ + bin\Debug\ DEBUG;TRACE prompt 4 @@ -26,7 +26,7 @@ pdbonly true - ..\osu.Game\bin\Release\ + bin\Release\ TRACE prompt 4 diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ebedf32da0..a84970b4ee 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -4,10 +4,10 @@ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} Debug AnyCPU - WinExe + Library Properties osu.Game - osu! + osu.Game 3CF060CD28877D0E3112948951A64B2A7CEEC909 codesigning.pfx false @@ -18,7 +18,6 @@ 3.5 - osu.Game.Program OnOutputUpdated false LocalIntranet @@ -83,9 +82,6 @@ - - lazer.ico - Properties\app.manifest @@ -105,47 +101,15 @@ false - - $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.dll - True - - - $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.MsDelta.dll - True - - - $(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll - True - $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True - - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll - True - - - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Mdb.dll - True - - - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Pdb.dll - True - - - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.Rocks.dll - True - $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll True - - $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\NuGet.Squirrel.dll - True - $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True @@ -158,10 +122,6 @@ $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True - - $(SolutionDir)\packages\Splat.2.0.0\lib\Net45\Splat.dll - True - $(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll True @@ -178,14 +138,7 @@ $(SolutionDir)\packages\SQLiteNetExtensions.1.3.0\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\SQLiteNetExtensions.dll True - - $(SolutionDir)\packages\squirrel.windows.1.7.8\lib\Net45\Squirrel.dll - True - - - - @@ -389,8 +342,6 @@ - - @@ -518,9 +469,7 @@ - - @@ -821,9 +770,6 @@ - - - - - - - \ No newline at end of file diff --git a/osu.sln b/osu.sln index 2b1a0aa0e5..b1341051f9 100644 --- a/osu.sln +++ b/osu.sln @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop.Deploy", "osu.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests", "osu.Game.Tests\osu.Game.Tests.csproj", "{54377672-20B1-40AF-8087-5CF73BF3953A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Desktop", "osu.Desktop\osu.Desktop.csproj", "{419659FD-72EA-4678-9EB8-B22A746CED70}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,6 +81,12 @@ Global {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.Build.0 = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.VisualTests|Any CPU.Build.0 = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Debug|Any CPU.Build.0 = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.ActiveCfg = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.Release|Any CPU.Build.0 = Release|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU + {419659FD-72EA-4678-9EB8-B22A746CED70}.VisualTests|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 4011e3991f..c236ce82b0 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -645,6 +645,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True True True True From 179542daf10453e0199c375e817e2cc81984f711 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Oct 2017 15:02:20 +0900 Subject: [PATCH 239/344] Standardise AssemblyInfos --- osu.Desktop/Properties/AssemblyInfo.cs | 2 +- .../Properties/AssemblyInfo.cs | 16 +----- .../Properties/AssemblyInfo.cs | 16 +----- .../Properties/AssemblyInfo.cs | 16 +----- .../Properties/AssemblyInfo.cs | 16 +----- osu.Game/Properties/AssemblyInfo.cs | 8 +-- osu.Game/Properties/app.manifest | 57 ------------------- osu.Game/osu.Game.csproj | 5 +- 8 files changed, 18 insertions(+), 118 deletions(-) delete mode 100644 osu.Game/Properties/app.manifest diff --git a/osu.Desktop/Properties/AssemblyInfo.cs b/osu.Desktop/Properties/AssemblyInfo.cs index fe7ad20124..2ed304ebd7 100644 --- a/osu.Desktop/Properties/AssemblyInfo.cs +++ b/osu.Desktop/Properties/AssemblyInfo.cs @@ -22,7 +22,7 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("55e28cb2-7b6c-4595-8dcc-9871d8aad7e9")] +[assembly: Guid("b0cb1d48-e4c2-4612-a347-beea7b1a71e7")] [assembly: AssemblyVersion("0.0.0")] [assembly: AssemblyFileVersion("0.0.0")] diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index 42fbc7e082..dd2006c60c 100644 --- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Rulesets.Catch")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("catch the fruit. to the beat.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ppy Pty Ltd")] [assembly: AssemblyProduct("osu.Game.Rulesets.Catch")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -24,15 +24,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("58f6c80c-1253-4a0e-a465-b8c85ebeadf3")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index 790002acd7..85a8f95b14 100644 --- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Rulesets.Mania")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("smash the keys. to the beat.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ppy Pty Ltd")] [assembly: AssemblyProduct("osu.Game.Rulesets.Mania")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -24,15 +24,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("48f4582b-7687-4621-9cbe-5c24197cb536")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs index 791c9b594d..b6cf47071a 100644 --- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Mode.Osu")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("click the circles. to the beat.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ppy Pty Ltd")] [assembly: AssemblyProduct("osu.Game.Mode.Osu")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -24,15 +24,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("c92a607b-1fdd-4954-9f92-03ff547d9080")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index 89c07517ca..f6a9c8f101 100644 --- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -8,11 +8,11 @@ using System.Runtime.InteropServices; // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("osu.Game.Rulesets.Taiko")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("bash the drum. to the beat.")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("ppy Pty Ltd")] [assembly: AssemblyProduct("osu.Game.Rulesets.Taiko")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -24,15 +24,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("f167e17a-7de6-4af5-b920-a5112296c695")] -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs index fe7ad20124..e28f8a3873 100644 --- a/osu.Game/Properties/AssemblyInfo.cs +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -7,11 +7,11 @@ using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("osu!lazer")] +[assembly: AssemblyTitle("osu.Game")] [assembly: AssemblyDescription("click the circles. to the beat.")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("ppy Pty Ltd")] -[assembly: AssemblyProduct("osu!lazer")] +[assembly: AssemblyProduct("osu.Game")] [assembly: AssemblyCopyright("ppy Pty Ltd 2007-2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -24,5 +24,5 @@ using System.Runtime.InteropServices; // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("55e28cb2-7b6c-4595-8dcc-9871d8aad7e9")] -[assembly: AssemblyVersion("0.0.0")] -[assembly: AssemblyFileVersion("0.0.0")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/osu.Game/Properties/app.manifest b/osu.Game/Properties/app.manifest deleted file mode 100644 index 555db8513d..0000000000 --- a/osu.Game/Properties/app.manifest +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a84970b4ee..4b9297b963 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -82,9 +82,7 @@ - - Properties\app.manifest - + true bin\Debug\ @@ -148,7 +146,6 @@ - From 7ecf4b2154ff7977523f44a1ea70c5feb8f01e7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Oct 2017 15:05:03 +0900 Subject: [PATCH 240/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 3760443ea9..0bc71f95b4 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 3760443ea9bf682a1bbf6cfa6aa00ea9541c12aa +Subproject commit 0bc71f95b455d3829b2abf662b5fe25989e6c43c From 97b14f7d81549d90c9ad94992e0fb8fa9b33c12d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Oct 2017 15:06:43 +0900 Subject: [PATCH 241/344] Update vscode launch configuration --- .vscode/launch.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index d17dc33669..506915f462 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game/bin/Debug/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe", "args": [ "--tests" ], @@ -24,7 +24,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game/bin/Release/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe", "args": [ "--tests" ], @@ -41,7 +41,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game/bin/Debug/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Debug/osu!.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", "runtimeExecutable": null, @@ -55,7 +55,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/osu.Game/bin/Release/osu!.exe", + "program": "${workspaceRoot}/osu.Desktop/bin/Release/osu!.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", "runtimeExecutable": null, From 167eefa39781a12e4421a5b5d40cbce32764fd3d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Oct 2017 15:16:08 +0900 Subject: [PATCH 242/344] Add logging --- osu.Game/Database/OsuDbContext.cs | 37 ++++++++++++++++++- osu.Game/Migrations/20171014052545_Init.cs | 2 - .../Migrations/OsuDbContextModelSnapshot.cs | 4 -- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index cd4a780979..9d5111d5d8 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -1,8 +1,12 @@ -using Microsoft.EntityFrameworkCore; +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace osu.Game.Database { @@ -30,6 +34,7 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlite(connectionString); + optionsBuilder.UseLoggerFactory(new OsuDbLoggerFactory()); } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -45,5 +50,35 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.InstantiationInfo).IsUnique(); modelBuilder.Entity().HasIndex(b => b.Available); } + + private class OsuDbLoggerFactory : ILoggerFactory + { + public void Dispose() + { + } + + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); + + public void AddProvider(ILoggerProvider provider) => new OsuDbLoggerProvider(); + + private class OsuDbLoggerProvider : ILoggerProvider + { + public void Dispose() + { + } + + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); + } + + private class OsuDbLogger : ILogger + { + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + => Logger.Log(formatter(state, exception), LoggingTarget.Database, Framework.Logging.LogLevel.Debug); + + public bool IsEnabled(LogLevel logLevel) => true; + + public IDisposable BeginScope(TState state) => null; + } + } } } diff --git a/osu.Game/Migrations/20171014052545_Init.cs b/osu.Game/Migrations/20171014052545_Init.cs index 3b098ed2a6..6792f79e3d 100644 --- a/osu.Game/Migrations/20171014052545_Init.cs +++ b/osu.Game/Migrations/20171014052545_Init.cs @@ -2,8 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using Microsoft.EntityFrameworkCore.Migrations; -using System; -using System.Collections.Generic; namespace osu.Game.Migrations { diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index bfcd09e309..f3a6c5a520 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -4,11 +4,7 @@ // using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; using osu.Game.Database; -using System; namespace osu.Game.Migrations { From 9226456f3cf2c4c717238f0615d23ac6df1be5a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 14 Oct 2017 16:36:43 +0900 Subject: [PATCH 243/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 244775160f..27d78a17d6 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 244775160fd3d9cbb97c70e9213c2e732d65bb0e +Subproject commit 27d78a17d606ccbbd67f679f52133aaf5ae4df63 From 89c17ed13e13f0e02d9ab752104ea91db4f7c699 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 14 Oct 2017 21:40:26 +0900 Subject: [PATCH 244/344] Initialise batteries Note that this is in the wrong place. But so is the rest of this code. --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7d9d3517bd..590007494b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -83,6 +83,7 @@ namespace osu.Game private OsuDbContext createDbContext() { + SQLitePCL.Batteries.Init(); var connectionString = Host.Storage.GetDatabaseConnectionString(@"client"); var context = new OsuDbContext(connectionString); var connection = context.Database.GetDbConnection(); From 5f083f10a7bf5f21999f8af7ac8bc822a3985e84 Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Sat, 14 Oct 2017 16:19:03 +0300 Subject: [PATCH 245/344] Add System.ValueTuple NuGet package --- osu.Desktop/osu.Desktop.csproj | 3 +++ osu.Desktop/packages.config | 1 + 2 files changed, 4 insertions(+) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index fad297fa0a..859ff42dad 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -158,6 +158,9 @@ + + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 0ec2cc196d..7c4e9632e0 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -11,4 +11,5 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + \ No newline at end of file From 7cf5d63cd32bde05ee0bcbc262e5dac7595036f4 Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Sun, 15 Oct 2017 00:40:41 +0300 Subject: [PATCH 246/344] Return back DatabaseBackedStore's query and populate functions --- osu.Game/Beatmaps/BeatmapInfo.cs | 9 +++- osu.Game/Beatmaps/BeatmapManager.cs | 46 ++++++++++++------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 24 +++++++++- osu.Game/Beatmaps/BeatmapStore.cs | 42 ----------------- osu.Game/Database/DatabaseBackedStore.cs | 41 +++++++++++++++++ osu.Game/Database/IPopulate.cs | 7 +++ osu.Game/Rulesets/RulesetStore.cs | 19 ++------ .../Tests/Visual/TestCasePlaySongSelect.cs | 6 +-- osu.Game/Tests/Visual/TestCasePlayer.cs | 2 +- osu.Game/osu.Game.csproj | 1 + 10 files changed, 117 insertions(+), 80 deletions(-) create mode 100644 osu.Game/Database/IPopulate.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c1516f17c9..621bc59786 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -6,12 +6,13 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Rulesets; namespace osu.Game.Beatmaps { - public class BeatmapInfo : IEquatable, IJsonSerializable + public class BeatmapInfo : IEquatable, IJsonSerializable, IPopulate { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -124,5 +125,11 @@ namespace osu.Game.Beatmaps public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && BeatmapSet.Hash == other.BeatmapSet.Hash && (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; + + public void Populate(OsuDbContext connection) + { + var entry = connection.Entry(this); + entry.Reference(nameof(Difficulty)); + } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 955c6f4b17..d364a8fbe4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Ionic.Zip; using osu.Framework.Audio.Track; @@ -260,10 +259,13 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - if (!beatmaps.Delete(beatmapSet)) return; + lock (beatmaps) + { + if (!beatmaps.Delete(beatmapSet)) return; - if (!beatmapSet.Protected) - files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + if (!beatmapSet.Protected) + files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + } } /// @@ -302,6 +304,9 @@ namespace osu.Game.Beatmaps if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; + lock (beatmaps) + beatmaps.Populate(beatmapInfo); + if (beatmapInfo.BeatmapSet == null) throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); @@ -333,7 +338,10 @@ namespace osu.Game.Beatmaps { lock (beatmaps) { - BeatmapSetInfo set = beatmaps.QueryBeatmapSet(query); + BeatmapSetInfo set = beatmaps.Query(query).FirstOrDefault(); + + if (set != null) + beatmaps.Populate(set); return set; } @@ -351,9 +359,9 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmapSets(Expression> query) + public List QueryBeatmapSets(Func query) { - return beatmaps.QueryBeatmapSets(query); + return beatmaps.QueryAndPopulate(query); } /// @@ -363,9 +371,15 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo QueryBeatmap(Func query) { - BeatmapInfo set = beatmaps.QueryBeatmap(query); + lock (beatmaps) + { + BeatmapInfo set = beatmaps.Query(query).FirstOrDefault(); - return set; + if (set != null) + beatmaps.Populate(set); + + return set; + } } /// @@ -373,9 +387,9 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmaps(Expression> query) + public List QueryBeatmaps(Func query) { - lock (beatmaps) return beatmaps.QueryBeatmaps(query); + lock (beatmaps) return beatmaps.Query(query); } /// @@ -414,7 +428,7 @@ namespace osu.Game.Beatmaps // check if this beatmap has already been imported and exit early if so. BeatmapSetInfo beatmapSet; lock (beatmaps) - beatmapSet = beatmaps.QueryBeatmapSet(b => b.Hash == hash); + beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault(); if (beatmapSet != null) { @@ -478,8 +492,8 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Metadata = null; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.Ruleset = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID); - beatmap.BeatmapInfo.StarDifficulty = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) + beatmap.BeatmapInfo.Ruleset = rulesets.Query(r => r.ID == beatmap.BeatmapInfo.RulesetID).FirstOrDefault(); + beatmap.BeatmapInfo.StarDifficulty = rulesets.Query(r => r.ID == beatmap.BeatmapInfo.RulesetID).FirstOrDefault()?.CreateInstance()?.CreateDifficultyCalculator(beatmap) .Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); @@ -493,11 +507,11 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets() + public List GetAllUsableBeatmapSets(bool populate = true) { lock (beatmaps) { - return beatmaps.QueryBeatmapSets(b => !b.DeletePending); + return populate ? beatmaps.QueryAndPopulate(b => !b.DeletePending) : beatmaps.Query(b => !b.DeletePending); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 404add2fa5..ddbc5d0dfb 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,10 +4,12 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; +using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetInfo + public class BeatmapSetInfo : IPopulate { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -34,5 +36,25 @@ namespace osu.Game.Beatmaps public List Files { get; set; } public bool Protected { get; set; } + + public void Populate(OsuDbContext connection) + { + var entry = connection.Entry(this); + entry.Collection(nameof(Beatmaps)).Load(); + entry.Reference(nameof(Metadata)).Load(); + entry.Collection(nameof(Files)).Load(); + + foreach (var beatmap in Beatmaps) + { + var beatmapEntry = connection.Entry(beatmap); + beatmapEntry.Reference(nameof(beatmap.Difficulty)).Load(); + } + + foreach (var file in Files) + { + var fileEntry = connection.Entry(file); + fileEntry.Reference(nameof(file.FileInfo)).Load(); + } + } } } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 9c5a1a9dfc..0be502132f 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -2,9 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; @@ -134,45 +132,5 @@ namespace osu.Game.Beatmaps { Connection.BeatmapSetInfo.RemoveRange(Connection.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); } - - public BeatmapSetInfo QueryBeatmapSet(Func query) - { - return Connection.BeatmapSetInfo - .Include(b => b.Metadata) - .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) - .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) - .Include(b => b.Files).ThenInclude(f => f.FileInfo) - .FirstOrDefault(query); - } - - public List QueryBeatmapSets(Expression> query) - { - return Connection.BeatmapSetInfo - .Include(b => b.Metadata) - .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) - .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) - .Include(b => b.Files).ThenInclude(f => f.FileInfo) - .Where(query).ToList(); - } - - public BeatmapInfo QueryBeatmap(Func query) - { - return Connection.BeatmapInfo - .Include(b => b.BeatmapSet) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.Difficulty) - .FirstOrDefault(query); - } - - public List QueryBeatmaps(Expression> query) - { - return Connection.BeatmapInfo - .Include(b => b.BeatmapSet) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.Difficulty) - .Where(query).ToList(); - } } } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index f4b6a866dc..fd891ca825 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; @@ -49,6 +50,46 @@ namespace osu.Game.Database /// public void Reset() => Prepare(true); + public List Query(Func filter = null) where T : class + { + checkType(typeof(T)); + + var dbSet = Connection.GetType().GetProperties().Single(property => property.PropertyType == typeof(DbSet)).GetValue(Connection) as DbSet; + var query = dbSet.ToList(); + + if (filter != null) + query = query.Where(filter).ToList(); + + return query; + } + + /// + /// Query and populate results. + /// + /// An filter to refine results. + /// + public List QueryAndPopulate(Func filter) + where T : class, IPopulate + { + checkType(typeof(T)); + + var query = Query(filter); + foreach (var item in query) + Populate(item); + return query; + } + + /// + /// Populate a database-backed item. + /// + /// + public void Populate(IPopulate item) + { + checkType(item.GetType()); + + item.Populate(Connection); + } + private void checkType(Type type) { if (!ValidTypes.Contains(type)) diff --git a/osu.Game/Database/IPopulate.cs b/osu.Game/Database/IPopulate.cs new file mode 100644 index 0000000000..24cabf6d5a --- /dev/null +++ b/osu.Game/Database/IPopulate.cs @@ -0,0 +1,7 @@ +namespace osu.Game.Database +{ + public interface IPopulate + { + void Populate(OsuDbContext connection); + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5a4b33df0b..8a8bbd9244 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -42,14 +42,14 @@ namespace osu.Game.Rulesets { Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } - + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { var rulesetInfo = createRulesetInfo(r); - if (Connection.RulesetInfo.SingleOrDefault(rsi=>rsi.ID==rulesetInfo.ID)==null) + if (Connection.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) { Connection.RulesetInfo.Add(rulesetInfo); } @@ -108,19 +108,6 @@ namespace osu.Game.Rulesets protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) }; - public RulesetInfo GetRuleset(int id) => Connection.RulesetInfo.First(r => r.ID == id); - - public RulesetInfo QueryRulesetInfo(Func query) - { - return Connection.RulesetInfo.FirstOrDefault(query); - } - - public List QueryRulesets(Func query = null) - { - var rulesets = Connection.RulesetInfo; - if (query != null) - return rulesets.Where(query).ToList(); - return rulesets.ToList(); - } + public RulesetInfo GetRuleset(int id) => Query().First(r => r.ID == id); } } diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index 1b7f2def4d..f4c0d0b1b6 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.Query().First(), Path = "normal.osu", Version = "Normal", Difficulty = new BeatmapDifficulty @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.Query().First(), Path = "hard.osu", Version = "Hard", Difficulty = new BeatmapDifficulty @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.Query().First(), Path = "insane.osu", Version = "Insane", Difficulty = new BeatmapDifficulty diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 811e240cee..b0953ceb7e 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual string instantiation = ruleset?.AssemblyQualifiedName; - foreach (var r in rulesets.QueryRulesets(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) + foreach (var r in rulesets.Query(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) AddStep(r.Name, () => loadPlayerFor(r)); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8773119f26..b9154f7e77 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -273,6 +273,7 @@ + 20171014052545_Init.cs From bed5a64ee2182e3d2c99b87fa57dd3d53512c926 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:15:35 +0800 Subject: [PATCH 247/344] Construct DwarableScore using null weight. --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index d7df239003..3b2c9d83ed 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -155,7 +155,7 @@ namespace osu.Game.Overlays.Profile.Sections { missing.Hide(); foreach (OnlineScore score in scores) - scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : -1) + scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : (double?)null) { RelativeSizeAxes = Axes.X, Height = 60, From 192ebe776f3d12e73606ad7b0255babdc8e257ae Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:30:52 +0800 Subject: [PATCH 248/344] Use localisation engine instead of asking current culture directly. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 973769a114..b033503141 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -402,7 +402,7 @@ namespace osu.Game.Overlays.Profile scoreText.Add(createScoreText("Ranked Score")); scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##", CultureInfo.CurrentCulture)}%")); + scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##")}%")); scoreText.Add(createScoreText("Play Count")); scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); scoreText.Add(createScoreText("Total Score")); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 52b68e7b30..b2961aad59 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"weighted: {Math.Round(score.PP * weight ?? 0)}pp ({weight.Value.ToString("0%", CultureInfo.CurrentCulture)})", + Current = locale.Format($"weighted: {score.PP * weight ?? 0:0}pp ({weight:0%})"), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, From 9b3676c5623970c8a9a99b34647339c7ea6c5e4e Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:44:15 +0800 Subject: [PATCH 249/344] Use format string for double instead of Math.Round. --- osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 2 +- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 7 ++++--- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 26335aac9b..c2e1a7b660 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet beatmap = value; var rate = (float)beatmap.OnlineInfo.PassCount / beatmap.OnlineInfo.PlayCount; - successPercent.Text = $"{Math.Round(rate * 100)}%"; + successPercent.Text = rate.ToString("P0"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index b2961aad59..5162c350e8 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -79,9 +79,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) { + double pp = score.PP ?? 0; stats.Add(new OsuSpriteText { - Text = $"{Math.Round(score.PP ?? 0)}pp", + Text = $"{pp:0}pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 18, @@ -92,7 +93,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Current = locale.Format($"weighted: {score.PP * weight ?? 0:0}pp ({weight:0%})"), + Text = $"weighted: {pp * weight:0}pp ({weight:P0})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, @@ -103,7 +104,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks stats.Add(new OsuSpriteText { - Text = "accuracy: " + score.Accuracy.ToString("0.00%"), + Text = $"accuracy: {score.Accuracy:P2}", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3b26f7bffc..dc3b012328 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -232,9 +232,9 @@ namespace osu.Game.Screens.Select double bpmMax = beatmap.ControlPointInfo.BPMMaximum; double bpmMin = beatmap.ControlPointInfo.BPMMinimum; - if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; + if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}bpm"; - return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)"; + return $"{bpmMin:0}-{bpmMax:0}bpm (mostly {beatmap.ControlPointInfo.BPMMode:0}bpm)"; } public class InfoLabel : Container From 06fe8745945ce602666a1947c4638928debfaab8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:53:30 +0800 Subject: [PATCH 250/344] CI fixes. --- osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 1 - osu.Game/Overlays/Profile/ProfileHeader.cs | 3 +-- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index c2e1a7b660..9402ed82f4 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index b033503141..22e34be34c 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using System.Diagnostics; -using System.Globalization; using System.Collections.Generic; using osu.Framework.Graphics.Cursor; @@ -402,7 +401,7 @@ namespace osu.Game.Overlays.Profile scoreText.Add(createScoreText("Ranked Score")); scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##")}%")); + scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); scoreText.Add(createScoreText("Play Count")); scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); scoreText.Add(createScoreText("Total Score")); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 5162c350e8..af336c2529 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -13,9 +13,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Leaderboards; using System.Linq; using osu.Framework.Localisation; -using System.Globalization; using osu.Game.Rulesets.Scoring; -using System; using osu.Game.Rulesets.UI; namespace osu.Game.Overlays.Profile.Sections.Ranks From db2750592d0e946de8f07a4269c2adf14abaf1d7 Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Sun, 15 Oct 2017 14:01:35 +0300 Subject: [PATCH 251/344] Fix removal of FileInfo, BeatmapMetadata, BeatmapDifficulty objects --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 1 + osu.Game/Beatmaps/BeatmapMetadata.cs | 1 + osu.Game/Beatmaps/BeatmapStore.cs | 5 + ...ner.cs => 20171015101238_Init.Designer.cs} | 56 +++-- ...4052545_Init.cs => 20171015101238_Init.cs} | 235 +++++++++--------- .../Migrations/OsuDbContextModelSnapshot.cs | 54 ++-- osu.Game/osu.Game.csproj | 11 +- osu.Game/packages.config | 1 + 8 files changed, 192 insertions(+), 172 deletions(-) rename osu.Game/Migrations/{20171014052545_Init.designer.cs => 20171015101238_Init.Designer.cs} (85%) rename osu.Game/Migrations/{20171014052545_Init.cs => 20171015101238_Init.cs} (90%) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 25e212f3c5..0c2299e5e6 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -14,6 +14,7 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } + public int BeatmapInfoId { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 5e47d6c13d..5e1a77a5bf 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -15,6 +15,7 @@ namespace osu.Game.Beatmaps [NotMapped] public int? OnlineBeatmapSetID { get; set; } + public int BeatmapSetInfoId { get; set; } public string Title { get; set; } public string TitleUnicode { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 0be502132f..19e30a23ce 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -73,6 +73,11 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; + + // We can't use one to one relationship with its cascade delete because FileInfo can be used not only inside of BeatmapSetFileInfo + var files = beatmapSet.Files.Select(beatmapSetFileInfo => beatmapSetFileInfo.FileInfo); + Connection.FileInfo.RemoveRange(files); + Connection.BeatmapSetInfo.Remove(beatmapSet); Connection.SaveChanges(); diff --git a/osu.Game/Migrations/20171014052545_Init.designer.cs b/osu.Game/Migrations/20171015101238_Init.Designer.cs similarity index 85% rename from osu.Game/Migrations/20171014052545_Init.designer.cs rename to osu.Game/Migrations/20171015101238_Init.Designer.cs index f151131882..2d37d1d07f 100644 --- a/osu.Game/Migrations/20171014052545_Init.designer.cs +++ b/osu.Game/Migrations/20171015101238_Init.Designer.cs @@ -1,7 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -// +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -13,7 +10,7 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171014052545_Init")] + [Migration("20171015101238_Init")] partial class Init { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -29,6 +26,8 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); + b.Property("BeatmapInfoId"); + b.Property("CircleSize"); b.Property("DrainRate"); @@ -41,6 +40,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoId") + .IsUnique(); + b.ToTable("BeatmapDifficulty"); }); @@ -59,8 +61,6 @@ namespace osu.Game.Migrations b.Property("Countdown"); - b.Property("DifficultyID"); - b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -97,8 +97,6 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); - b.HasIndex("DifficultyID"); - b.HasIndex("MD5Hash"); b.HasIndex("MetadataID"); @@ -119,10 +117,13 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("Author"); + b.Property("AuthorString") + .HasColumnName("Author"); b.Property("BackgroundFile"); + b.Property("BeatmapSetInfoId"); + b.Property("PreviewTime"); b.Property("Source"); @@ -135,6 +136,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapSetInfoId") + .IsUnique(); + b.ToTable("BeatmapMetadata"); }); @@ -168,16 +172,12 @@ namespace osu.Game.Migrations b.Property("Hash"); - b.Property("MetadataID"); - b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); - b.HasIndex("MetadataID"); - b.ToTable("BeatmapSetInfo"); }); @@ -248,6 +248,14 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo") + .WithOne("Difficulty") + .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -255,11 +263,6 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") - .WithMany() - .HasForeignKey("DifficultyID") - .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") .WithMany() .HasForeignKey("MetadataID"); @@ -270,6 +273,14 @@ namespace osu.Game.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -282,13 +293,6 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/Migrations/20171014052545_Init.cs b/osu.Game/Migrations/20171015101238_Init.cs similarity index 90% rename from osu.Game/Migrations/20171014052545_Init.cs rename to osu.Game/Migrations/20171015101238_Init.cs index 6792f79e3d..afa83a7fff 100644 --- a/osu.Game/Migrations/20171014052545_Init.cs +++ b/osu.Game/Migrations/20171015101238_Init.cs @@ -1,7 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { @@ -10,43 +7,18 @@ namespace osu.Game.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "BeatmapDifficulty", + name: "BeatmapSetInfo", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); }); migrationBuilder.CreateTable( @@ -95,83 +67,32 @@ namespace osu.Game.Migrations }); migrationBuilder.CreateTable( - name: "BeatmapSetInfo", + name: "BeatmapMetadata", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + BeatmapSetInfoId = table.Column(type: "INTEGER", nullable: false), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) }, constraints: table => { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); table.ForeignKey( - name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DifficultyID = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, + name: "FK_BeatmapMetadata_BeatmapSetInfo_BeatmapSetInfoId", + column: x => x.BeatmapSetInfoId, principalTable: "BeatmapSetInfo", principalColumn: "ID", onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapDifficulty_DifficultyID", - column: x => x.DifficultyID, - principalTable: "BeatmapDifficulty", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -201,16 +122,93 @@ namespace osu.Game.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + BeatmapInfoId = table.Column(type: "INTEGER", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapDifficulty_BeatmapInfo_BeatmapInfoId", + column: x => x.BeatmapInfoId, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapDifficulty_BeatmapInfoId", + table: "BeatmapDifficulty", + column: "BeatmapInfoId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_BeatmapSetInfoID", table: "BeatmapInfo", column: "BeatmapSetInfoID"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_DifficultyID", - table: "BeatmapInfo", - column: "DifficultyID"); - migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_MD5Hash", table: "BeatmapInfo", @@ -226,6 +224,12 @@ namespace osu.Game.Migrations table: "BeatmapInfo", column: "RulesetID"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapMetadata_BeatmapSetInfoId", + table: "BeatmapMetadata", + column: "BeatmapSetInfoId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", table: "BeatmapSetFileInfo", @@ -241,11 +245,6 @@ namespace osu.Game.Migrations table: "BeatmapSetInfo", column: "DeletePending"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_MetadataID", - table: "BeatmapSetInfo", - column: "MetadataID"); - migrationBuilder.CreateIndex( name: "IX_FileInfo_Hash", table: "FileInfo", @@ -288,7 +287,7 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "BeatmapInfo"); + name: "BeatmapDifficulty"); migrationBuilder.DropTable( name: "BeatmapSetFileInfo"); @@ -297,19 +296,19 @@ namespace osu.Game.Migrations name: "KeyBinding"); migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "RulesetInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetInfo"); + name: "BeatmapInfo"); migrationBuilder.DropTable( name: "FileInfo"); migrationBuilder.DropTable( name: "BeatmapMetadata"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); + + migrationBuilder.DropTable( + name: "BeatmapSetInfo"); } } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index f3a6c5a520..162119408b 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,7 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -// +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using osu.Game.Database; @@ -24,6 +21,8 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); + b.Property("BeatmapInfoId"); + b.Property("CircleSize"); b.Property("DrainRate"); @@ -36,6 +35,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoId") + .IsUnique(); + b.ToTable("BeatmapDifficulty"); }); @@ -54,8 +56,6 @@ namespace osu.Game.Migrations b.Property("Countdown"); - b.Property("DifficultyID"); - b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -92,8 +92,6 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); - b.HasIndex("DifficultyID"); - b.HasIndex("MD5Hash"); b.HasIndex("MetadataID"); @@ -114,10 +112,13 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("Author"); + b.Property("AuthorString") + .HasColumnName("Author"); b.Property("BackgroundFile"); + b.Property("BeatmapSetInfoId"); + b.Property("PreviewTime"); b.Property("Source"); @@ -130,6 +131,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapSetInfoId") + .IsUnique(); + b.ToTable("BeatmapMetadata"); }); @@ -163,16 +167,12 @@ namespace osu.Game.Migrations b.Property("Hash"); - b.Property("MetadataID"); - b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); - b.HasIndex("MetadataID"); - b.ToTable("BeatmapSetInfo"); }); @@ -243,6 +243,14 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo") + .WithOne("Difficulty") + .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -250,11 +258,6 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") - .WithMany() - .HasForeignKey("DifficultyID") - .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") .WithMany() .HasForeignKey("MetadataID"); @@ -265,6 +268,14 @@ namespace osu.Game.Migrations .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -277,13 +288,6 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b9154f7e77..404576e832 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,6 +23,7 @@ LocalIntranet v4.6.1 true + true publish\ true Disk @@ -197,6 +198,10 @@ ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + True + @@ -274,9 +279,9 @@ - - - 20171014052545_Init.cs + + + 20171015101238_Init.cs diff --git a/osu.Game/packages.config b/osu.Game/packages.config index 8691e7062a..ae7b74ef16 100644 --- a/osu.Game/packages.config +++ b/osu.Game/packages.config @@ -49,4 +49,5 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + \ No newline at end of file From 298349ba77c22b8dff7226eda23811b4c1445369 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 00:16:15 +0900 Subject: [PATCH 252/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 27d78a17d6..4489791c8f 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 27d78a17d606ccbbd67f679f52133aaf5ae4df63 +Subproject commit 4489791c8f1299ee4cc045454f76bfd34940aabf From a232033469bf7a8e59bfaece9ea511753ff80111 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 00:53:59 +0900 Subject: [PATCH 253/344] Fix some file headers and unused files --- osu.Game/Database/DbContextBase.cs | 8 -------- osu.Game/Database/IPopulate.cs | 5 ++++- osu.Game/Database/OsuDbContext.cs | 24 +++++++++++++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) delete mode 100644 osu.Game/Database/DbContextBase.cs diff --git a/osu.Game/Database/DbContextBase.cs b/osu.Game/Database/DbContextBase.cs deleted file mode 100644 index 93f4ac4c23..0000000000 --- a/osu.Game/Database/DbContextBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.EntityFrameworkCore; - -namespace osu.Game.Database -{ - public abstract class DbContextBase:DbContext - { - } -} diff --git a/osu.Game/Database/IPopulate.cs b/osu.Game/Database/IPopulate.cs index 24cabf6d5a..b07312f81c 100644 --- a/osu.Game/Database/IPopulate.cs +++ b/osu.Game/Database/IPopulate.cs @@ -1,4 +1,7 @@ -namespace osu.Game.Database +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database { public interface IPopulate { diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 9d5111d5d8..3f019a07fa 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using osu.Framework.Logging; @@ -12,6 +15,11 @@ namespace osu.Game.Database { public class OsuDbContext : DbContext { + public DbSet BeatmapInfo { get; set; } + public DbSet BeatmapSetInfo { get; set; } + public DbSet DatabasedKeyBinding { get; set; } + public DbSet FileInfo { get; set; } + public DbSet RulesetInfo { get; set; } private readonly string connectionString; public OsuDbContext() @@ -24,12 +32,6 @@ namespace osu.Game.Database this.connectionString = connectionString; } - public DbSet BeatmapInfo { get; set; } - public DbSet BeatmapSetInfo { get; set; } - public DbSet DatabasedKeyBinding { get; set; } - public DbSet FileInfo { get; set; } - public DbSet RulesetInfo { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); @@ -53,20 +55,28 @@ namespace osu.Game.Database private class OsuDbLoggerFactory : ILoggerFactory { + #region Disposal + public void Dispose() { } + #endregion + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); public void AddProvider(ILoggerProvider provider) => new OsuDbLoggerProvider(); private class OsuDbLoggerProvider : ILoggerProvider { + #region Disposal + public void Dispose() { } + #endregion + public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); } From 0a8b3ad61933b7bb5fdf7f655773c652a866765f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 00:58:56 +0900 Subject: [PATCH 254/344] Add one more licence header --- osu.Game/Migrations/20171015101238_Init.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Migrations/20171015101238_Init.cs b/osu.Game/Migrations/20171015101238_Init.cs index afa83a7fff..61ad66fd25 100644 --- a/osu.Game/Migrations/20171015101238_Init.cs +++ b/osu.Game/Migrations/20171015101238_Init.cs @@ -1,4 +1,7 @@ -using Microsoft.EntityFrameworkCore.Migrations; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Microsoft.EntityFrameworkCore.Migrations; namespace osu.Game.Migrations { From 76ee6ed8faec069383f4f598f4d2a3b1eb0579d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 01:21:17 +0900 Subject: [PATCH 255/344] Update CodeFileSanity to ignore migrations folder --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 21c15724e6..7d41a6deb4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,7 @@ install: - cmd: git submodule update --init --recursive - cmd: choco install resharper-clt -y - cmd: choco install nvika -y - - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.2/CodeFileSanity.exe + - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe before_build: - cmd: CodeFileSanity.exe - cmd: nuget restore From ba4cfd6a2f8c187721c9d6c76b8aaa6c428eec07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 01:25:58 +0900 Subject: [PATCH 256/344] Update appveyor worker image to vs2017 --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 7d41a6deb4..9048428590 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,7 @@ # 2017-09-14 clone_depth: 1 version: '{branch}-{build}' +image: Visual Studio 2017 configuration: Debug cache: - C:\ProgramData\chocolatey\bin -> appveyor.yml From e7c7ebdd9d8e4397efeadab5b1e7c077b6fffd42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 02:15:05 +0900 Subject: [PATCH 257/344] Ignore migrations directory for inspections --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c236ce82b0..4eeaa23a32 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -3,6 +3,7 @@ True True True + ExplicitlyExcluded ExplicitlyExcluded SOLUTION HINT From 4a064da30f6349a7d2f327aa2b2769635fe02037 Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Sun, 15 Oct 2017 21:56:33 +0300 Subject: [PATCH 258/344] Fix inconsistent lock usage in BeatmapManager --- .../Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- osu.Game/Beatmaps/BeatmapManager.cs | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index f2ab00bead..cd9e765e7f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -138,8 +138,9 @@ namespace osu.Game.Tests.Beatmaps.IO var set = queryBeatmapSets().First(); foreach (BeatmapInfo b in set.Beatmaps) + Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); - Assert.IsTrue(set.Beatmaps.Count > 0); + Assert.IsTrue(set.Beatmaps.Count > 0); var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d364a8fbe4..30da9ceae9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -59,8 +59,6 @@ namespace osu.Game.Beatmaps private readonly FileStore files; - private readonly OsuDbContext connection; - private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; @@ -92,7 +90,6 @@ namespace osu.Game.Beatmaps this.storage = storage; this.files = files; - this.connection = connection; this.rulesets = rulesets; this.api = api; @@ -163,7 +160,7 @@ namespace osu.Game.Beatmaps /// The beatmap to be imported. public BeatmapSetInfo Import(ArchiveReader archiveReader) { - BeatmapSetInfo set = null; + BeatmapSetInfo set; // let's only allow one concurrent import at a time for now. lock (importLock) @@ -181,7 +178,8 @@ namespace osu.Game.Beatmaps // If we have an ID then we already exist in the database. if (beatmapSetInfo.ID != 0) return; - beatmaps.Add(beatmapSetInfo); + lock (beatmaps) + beatmaps.Add(beatmapSetInfo); } /// @@ -272,13 +270,21 @@ namespace osu.Game.Beatmaps /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); + public void Hide(BeatmapInfo beatmap) + { + lock (beatmaps) + beatmaps.Hide(beatmap); + } /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); + public void Restore(BeatmapInfo beatmap) + { + lock (beatmaps) + beatmaps.Restore(beatmap); + } /// /// Returns a to a usable state if it has previously been deleted but not yet purged. @@ -287,7 +293,8 @@ namespace osu.Game.Beatmaps /// The beatmap to restore. public void Undelete(BeatmapSetInfo beatmapSet) { - if (!beatmaps.Undelete(beatmapSet)) return; + lock (beatmaps) + if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -361,7 +368,8 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public List QueryBeatmapSets(Func query) { - return beatmaps.QueryAndPopulate(query); + lock (beatmaps) + return beatmaps.QueryAndPopulate(query); } /// From 90592b075768b9d0ad38463b2568e6131ed0e8f8 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:15:35 +0800 Subject: [PATCH 259/344] Construct DwarableScore using null weight. --- osu.Game/Overlays/Profile/Sections/RanksSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index d7df239003..3b2c9d83ed 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -155,7 +155,7 @@ namespace osu.Game.Overlays.Profile.Sections { missing.Hide(); foreach (OnlineScore score in scores) - scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : -1) + scoreContainer.Add(new DrawableScore(score, includeWeigth ? Math.Pow(0.95, scoreContainer.Count) : (double?)null) { RelativeSizeAxes = Axes.X, Height = 60, From f837117495ae7263d9040592b49d6ecdca2fcc06 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:30:52 +0800 Subject: [PATCH 260/344] Use localisation engine instead of asking current culture directly. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 973769a114..b033503141 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -402,7 +402,7 @@ namespace osu.Game.Overlays.Profile scoreText.Add(createScoreText("Ranked Score")); scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##", CultureInfo.CurrentCulture)}%")); + scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##")}%")); scoreText.Add(createScoreText("Play Count")); scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); scoreText.Add(createScoreText("Total Score")); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 52b68e7b30..b2961aad59 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Text = $"weighted: {Math.Round(score.PP * weight ?? 0)}pp ({weight.Value.ToString("0%", CultureInfo.CurrentCulture)})", + Current = locale.Format($"weighted: {score.PP * weight ?? 0:0}pp ({weight:0%})"), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, From c2836a8393894212f95e49c8a63b3cdba8e1bc18 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:44:15 +0800 Subject: [PATCH 261/344] Use format string for double instead of Math.Round. --- osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 2 +- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 7 ++++--- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 26335aac9b..c2e1a7b660 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapSet beatmap = value; var rate = (float)beatmap.OnlineInfo.PassCount / beatmap.OnlineInfo.PlayCount; - successPercent.Text = $"{Math.Round(rate * 100)}%"; + successPercent.Text = rate.ToString("P0"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index b2961aad59..5162c350e8 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -79,9 +79,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay) { + double pp = score.PP ?? 0; stats.Add(new OsuSpriteText { - Text = $"{Math.Round(score.PP ?? 0)}pp", + Text = $"{pp:0}pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, TextSize = 18, @@ -92,7 +93,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { stats.Add(new OsuSpriteText { - Current = locale.Format($"weighted: {score.PP * weight ?? 0:0}pp ({weight:0%})"), + Text = $"weighted: {pp * weight:0}pp ({weight:P0})", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, @@ -103,7 +104,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks stats.Add(new OsuSpriteText { - Text = "accuracy: " + score.Accuracy.ToString("0.00%"), + Text = $"accuracy: {score.Accuracy:P2}", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3b26f7bffc..dc3b012328 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -232,9 +232,9 @@ namespace osu.Game.Screens.Select double bpmMax = beatmap.ControlPointInfo.BPMMaximum; double bpmMin = beatmap.ControlPointInfo.BPMMinimum; - if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; + if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}bpm"; - return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.ControlPointInfo.BPMMode) + "bpm)"; + return $"{bpmMin:0}-{bpmMax:0}bpm (mostly {beatmap.ControlPointInfo.BPMMode:0}bpm)"; } public class InfoLabel : Container From 5a684f926f6c05c3fb900f9e3c841cbfae5c368f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sun, 15 Oct 2017 16:53:30 +0800 Subject: [PATCH 262/344] CI fixes. --- osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 1 - osu.Game/Overlays/Profile/ProfileHeader.cs | 3 +-- osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index c2e1a7b660..9402ed82f4 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index b033503141..22e34be34c 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using System.Diagnostics; -using System.Globalization; using System.Collections.Generic; using osu.Framework.Graphics.Cursor; @@ -402,7 +401,7 @@ namespace osu.Game.Overlays.Profile scoreText.Add(createScoreText("Ranked Score")); scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy.ToString("0.##")}%")); + scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); scoreText.Add(createScoreText("Play Count")); scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); scoreText.Add(createScoreText("Total Score")); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs index 5162c350e8..af336c2529 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableScore.cs @@ -13,9 +13,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Leaderboards; using System.Linq; using osu.Framework.Localisation; -using System.Globalization; using osu.Game.Rulesets.Scoring; -using System; using osu.Game.Rulesets.UI; namespace osu.Game.Overlays.Profile.Sections.Ranks From 129cca07043e8874c0990ff90c461f9c4a70a38e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 11:06:19 +0900 Subject: [PATCH 263/344] Tidy up context creation --- osu.Game/Database/OsuDbContext.cs | 21 ++++++++++++++++++--- osu.Game/OsuGameBase.cs | 16 +--------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 3f019a07fa..51fabb56fe 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -22,14 +22,29 @@ namespace osu.Game.Database public DbSet RulesetInfo { get; set; } private readonly string connectionString; - public OsuDbContext() + static OsuDbContext() { - connectionString = "DataSource=:memory:"; + // required to initialise native SQLite libraries on some platforms. + SQLitePCL.Batteries_V2.Init(); } - public OsuDbContext(string connectionString) + /// + /// Create a new OsuDbContext instance. + /// + /// A valid SQLite connection string. If not provided, an in-memory instance will be created. + public OsuDbContext(string connectionString = "DataSource=:memory:") { this.connectionString = connectionString; + + Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10)); + + var connection = Database.GetDbConnection(); + connection.Open(); + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL;"; + cmd.ExecuteNonQuery(); + } } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 590007494b..d1e684499c 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -81,21 +81,7 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - private OsuDbContext createDbContext() - { - SQLitePCL.Batteries.Init(); - var connectionString = Host.Storage.GetDatabaseConnectionString(@"client"); - var context = new OsuDbContext(connectionString); - var connection = context.Database.GetDbConnection(); - connection.Open(); - using (var command = connection.CreateCommand()) - { - command.CommandText = "PRAGMA journal_mode=WAL;"; - command.ExecuteNonQuery(); - } - context.Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10)); - return context; - } + private OsuDbContext createDbContext() => new OsuDbContext(Host.Storage.GetDatabaseConnectionString(@"client")); [BackgroundDependencyLoader] private void load() From 845c5ef39a5bdc86ecd6fff8518043aa021a190f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 11:15:54 +0900 Subject: [PATCH 264/344] Update framework --- osu-framework | 2 +- osu.sln.DotSettings | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 4489791c8f..f7aab38362 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 4489791c8f1299ee4cc045454f76bfd34940aabf +Subproject commit f7aab38362a8f510b32340d45d35f8015c3a76b5 diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 4eeaa23a32..fdfbf25144 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -194,6 +194,8 @@ IPC LTRB MD5 + NS + OS RGB RNG SHA From e3d31bdd10c2c64a7b6bf122ac39943ee23e7144 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 12:51:46 +0900 Subject: [PATCH 265/344] Not implemented instead of incorrect --- osu.Game/Database/OsuDbContext.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 51fabb56fe..5f4e42ed6b 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -80,7 +80,10 @@ namespace osu.Game.Database public ILogger CreateLogger(string categoryName) => new OsuDbLogger(); - public void AddProvider(ILoggerProvider provider) => new OsuDbLoggerProvider(); + public void AddProvider(ILoggerProvider provider) + { + throw new NotImplementedException(); + } private class OsuDbLoggerProvider : ILoggerProvider { From 56e8c7303ce402400f87b6b42c66f9477b02272c Mon Sep 17 00:00:00 2001 From: TocoToucan Date: Sun, 15 Oct 2017 21:56:33 +0300 Subject: [PATCH 266/344] Revert "Fix inconsistent lock usage in BeatmapManager" This reverts commit 4a064da30f6349a7d2f327aa2b2769635fe02037. --- .../Beatmaps/IO/ImportBeatmapTest.cs | 3 +-- osu.Game/Beatmaps/BeatmapManager.cs | 26 +++++++------------ 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cd9e765e7f..f2ab00bead 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -138,9 +138,8 @@ namespace osu.Game.Tests.Beatmaps.IO var set = queryBeatmapSets().First(); foreach (BeatmapInfo b in set.Beatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); - Assert.IsTrue(set.Beatmaps.Count > 0); + Assert.IsTrue(set.Beatmaps.Count > 0); var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 30da9ceae9..d364a8fbe4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps private readonly FileStore files; + private readonly OsuDbContext connection; + private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; @@ -90,6 +92,7 @@ namespace osu.Game.Beatmaps this.storage = storage; this.files = files; + this.connection = connection; this.rulesets = rulesets; this.api = api; @@ -160,7 +163,7 @@ namespace osu.Game.Beatmaps /// The beatmap to be imported. public BeatmapSetInfo Import(ArchiveReader archiveReader) { - BeatmapSetInfo set; + BeatmapSetInfo set = null; // let's only allow one concurrent import at a time for now. lock (importLock) @@ -178,8 +181,7 @@ namespace osu.Game.Beatmaps // If we have an ID then we already exist in the database. if (beatmapSetInfo.ID != 0) return; - lock (beatmaps) - beatmaps.Add(beatmapSetInfo); + beatmaps.Add(beatmapSetInfo); } /// @@ -270,21 +272,13 @@ namespace osu.Game.Beatmaps /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) - { - lock (beatmaps) - beatmaps.Hide(beatmap); - } + public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) - { - lock (beatmaps) - beatmaps.Restore(beatmap); - } + public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); /// /// Returns a to a usable state if it has previously been deleted but not yet purged. @@ -293,8 +287,7 @@ namespace osu.Game.Beatmaps /// The beatmap to restore. public void Undelete(BeatmapSetInfo beatmapSet) { - lock (beatmaps) - if (!beatmaps.Undelete(beatmapSet)) return; + if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -368,8 +361,7 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public List QueryBeatmapSets(Func query) { - lock (beatmaps) - return beatmaps.QueryAndPopulate(query); + return beatmaps.QueryAndPopulate(query); } /// From 04e5f764a34e807db1a25de355bd79709f3aac74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 12:55:36 +0900 Subject: [PATCH 267/344] Revert "Fix removal of FileInfo, BeatmapMetadata, BeatmapDifficulty objects" --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 1 - osu.Game/Beatmaps/BeatmapMetadata.cs | 1 - osu.Game/Beatmaps/BeatmapStore.cs | 5 - ...5101238_Init.cs => 20171014052545_Init.cs} | 230 +++++++++--------- ...ner.cs => 20171014052545_Init.designer.cs} | 56 ++--- .../Migrations/OsuDbContextModelSnapshot.cs | 54 ++-- osu.Game/osu.Game.csproj | 7 +- 7 files changed, 168 insertions(+), 186 deletions(-) rename osu.Game/Migrations/{20171015101238_Init.cs => 20171014052545_Init.cs} (91%) rename osu.Game/Migrations/{20171015101238_Init.Designer.cs => 20171014052545_Init.designer.cs} (85%) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 0c2299e5e6..25e212f3c5 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -14,7 +14,6 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } - public int BeatmapInfoId { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 5e1a77a5bf..5e47d6c13d 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -15,7 +15,6 @@ namespace osu.Game.Beatmaps [NotMapped] public int? OnlineBeatmapSetID { get; set; } - public int BeatmapSetInfoId { get; set; } public string Title { get; set; } public string TitleUnicode { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 19e30a23ce..0be502132f 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -73,11 +73,6 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - - // We can't use one to one relationship with its cascade delete because FileInfo can be used not only inside of BeatmapSetFileInfo - var files = beatmapSet.Files.Select(beatmapSetFileInfo => beatmapSetFileInfo.FileInfo); - Connection.FileInfo.RemoveRange(files); - Connection.BeatmapSetInfo.Remove(beatmapSet); Connection.SaveChanges(); diff --git a/osu.Game/Migrations/20171015101238_Init.cs b/osu.Game/Migrations/20171014052545_Init.cs similarity index 91% rename from osu.Game/Migrations/20171015101238_Init.cs rename to osu.Game/Migrations/20171014052545_Init.cs index 61ad66fd25..6792f79e3d 100644 --- a/osu.Game/Migrations/20171015101238_Init.cs +++ b/osu.Game/Migrations/20171014052545_Init.cs @@ -10,18 +10,43 @@ namespace osu.Game.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "BeatmapSetInfo", + name: "BeatmapDifficulty", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); }); migrationBuilder.CreateTable( @@ -70,32 +95,83 @@ namespace osu.Game.Migrations }); migrationBuilder.CreateTable( - name: "BeatmapMetadata", + name: "BeatmapSetInfo", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - BeatmapSetInfoId = table.Column(type: "INTEGER", nullable: false), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); table.ForeignKey( - name: "FK_BeatmapMetadata_BeatmapSetInfo_BeatmapSetInfoId", - column: x => x.BeatmapSetInfoId, + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DifficultyID = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, principalTable: "BeatmapSetInfo", principalColumn: "ID", onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_DifficultyID", + column: x => x.DifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateTable( @@ -125,93 +201,16 @@ namespace osu.Game.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapDifficulty", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - BeatmapInfoId = table.Column(type: "INTEGER", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapDifficulty_BeatmapInfo_BeatmapInfoId", - column: x => x.BeatmapInfoId, - principalTable: "BeatmapInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapDifficulty_BeatmapInfoId", - table: "BeatmapDifficulty", - column: "BeatmapInfoId", - unique: true); - migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_BeatmapSetInfoID", table: "BeatmapInfo", column: "BeatmapSetInfoID"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_DifficultyID", + table: "BeatmapInfo", + column: "DifficultyID"); + migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_MD5Hash", table: "BeatmapInfo", @@ -227,12 +226,6 @@ namespace osu.Game.Migrations table: "BeatmapInfo", column: "RulesetID"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapMetadata_BeatmapSetInfoId", - table: "BeatmapMetadata", - column: "BeatmapSetInfoId", - unique: true); - migrationBuilder.CreateIndex( name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", table: "BeatmapSetFileInfo", @@ -248,6 +241,11 @@ namespace osu.Game.Migrations table: "BeatmapSetInfo", column: "DeletePending"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + migrationBuilder.CreateIndex( name: "IX_FileInfo_Hash", table: "FileInfo", @@ -290,7 +288,7 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "BeatmapDifficulty"); + name: "BeatmapInfo"); migrationBuilder.DropTable( name: "BeatmapSetFileInfo"); @@ -299,19 +297,19 @@ namespace osu.Game.Migrations name: "KeyBinding"); migrationBuilder.DropTable( - name: "BeatmapInfo"); - - migrationBuilder.DropTable( - name: "FileInfo"); - - migrationBuilder.DropTable( - name: "BeatmapMetadata"); + name: "BeatmapDifficulty"); migrationBuilder.DropTable( name: "RulesetInfo"); migrationBuilder.DropTable( name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); } } } diff --git a/osu.Game/Migrations/20171015101238_Init.Designer.cs b/osu.Game/Migrations/20171014052545_Init.designer.cs similarity index 85% rename from osu.Game/Migrations/20171015101238_Init.Designer.cs rename to osu.Game/Migrations/20171014052545_Init.designer.cs index 2d37d1d07f..f151131882 100644 --- a/osu.Game/Migrations/20171015101238_Init.Designer.cs +++ b/osu.Game/Migrations/20171014052545_Init.designer.cs @@ -1,4 +1,7 @@ -// +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -10,7 +13,7 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171015101238_Init")] + [Migration("20171014052545_Init")] partial class Init { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,8 +29,6 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); - b.Property("BeatmapInfoId"); - b.Property("CircleSize"); b.Property("DrainRate"); @@ -40,9 +41,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoId") - .IsUnique(); - b.ToTable("BeatmapDifficulty"); }); @@ -61,6 +59,8 @@ namespace osu.Game.Migrations b.Property("Countdown"); + b.Property("DifficultyID"); + b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -97,6 +97,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); + b.HasIndex("DifficultyID"); + b.HasIndex("MD5Hash"); b.HasIndex("MetadataID"); @@ -117,13 +119,10 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("AuthorString") - .HasColumnName("Author"); + b.Property("Author"); b.Property("BackgroundFile"); - b.Property("BeatmapSetInfoId"); - b.Property("PreviewTime"); b.Property("Source"); @@ -136,9 +135,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapSetInfoId") - .IsUnique(); - b.ToTable("BeatmapMetadata"); }); @@ -172,12 +168,16 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("MetadataID"); + b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); + b.HasIndex("MetadataID"); + b.ToTable("BeatmapSetInfo"); }); @@ -248,14 +248,6 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo") - .WithOne("Difficulty") - .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -263,6 +255,11 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") + .WithMany() + .HasForeignKey("DifficultyID") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") .WithMany() .HasForeignKey("MetadataID"); @@ -273,14 +270,6 @@ namespace osu.Game.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -293,6 +282,13 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany() + .HasForeignKey("MetadataID"); + }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 162119408b..f3a6c5a520 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,4 +1,7 @@ -// +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using osu.Game.Database; @@ -21,8 +24,6 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); - b.Property("BeatmapInfoId"); - b.Property("CircleSize"); b.Property("DrainRate"); @@ -35,9 +36,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoId") - .IsUnique(); - b.ToTable("BeatmapDifficulty"); }); @@ -56,6 +54,8 @@ namespace osu.Game.Migrations b.Property("Countdown"); + b.Property("DifficultyID"); + b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -92,6 +92,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); + b.HasIndex("DifficultyID"); + b.HasIndex("MD5Hash"); b.HasIndex("MetadataID"); @@ -112,13 +114,10 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("AuthorString") - .HasColumnName("Author"); + b.Property("Author"); b.Property("BackgroundFile"); - b.Property("BeatmapSetInfoId"); - b.Property("PreviewTime"); b.Property("Source"); @@ -131,9 +130,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapSetInfoId") - .IsUnique(); - b.ToTable("BeatmapMetadata"); }); @@ -167,12 +163,16 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("MetadataID"); + b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); + b.HasIndex("MetadataID"); + b.ToTable("BeatmapSetInfo"); }); @@ -243,14 +243,6 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo") - .WithOne("Difficulty") - .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -258,6 +250,11 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") + .WithMany() + .HasForeignKey("DifficultyID") + .OnDelete(DeleteBehavior.Cascade); + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") .WithMany() .HasForeignKey("MetadataID"); @@ -268,14 +265,6 @@ namespace osu.Game.Migrations .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -288,6 +277,13 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany() + .HasForeignKey("MetadataID"); + }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 404576e832..03ca32539c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,6 @@ LocalIntranet v4.6.1 true - true publish\ true Disk @@ -279,9 +278,9 @@ - - - 20171015101238_Init.cs + + + 20171014052545_Init.cs From 8a0b184dd6c5321789d8428f00f2fd7b0a1cddea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 12:56:58 +0900 Subject: [PATCH 268/344] Revert "Return back DatabaseBackedStore's query and populate functions" This reverts commit 7cf5d63cd32bde05ee0bcbc262e5dac7595036f4. --- osu.Game/Beatmaps/BeatmapInfo.cs | 9 +--- osu.Game/Beatmaps/BeatmapManager.cs | 46 +++++++------------ osu.Game/Beatmaps/BeatmapSetInfo.cs | 24 +--------- osu.Game/Beatmaps/BeatmapStore.cs | 42 +++++++++++++++++ osu.Game/Database/DatabaseBackedStore.cs | 41 ----------------- osu.Game/Database/IPopulate.cs | 10 ---- osu.Game/Rulesets/RulesetStore.cs | 19 ++++++-- .../Tests/Visual/TestCasePlaySongSelect.cs | 6 +-- osu.Game/Tests/Visual/TestCasePlayer.cs | 2 +- osu.Game/osu.Game.csproj | 1 - 10 files changed, 80 insertions(+), 120 deletions(-) delete mode 100644 osu.Game/Database/IPopulate.cs diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 621bc59786..c1516f17c9 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -6,13 +6,12 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; -using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Rulesets; namespace osu.Game.Beatmaps { - public class BeatmapInfo : IEquatable, IJsonSerializable, IPopulate + public class BeatmapInfo : IEquatable, IJsonSerializable { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -125,11 +124,5 @@ namespace osu.Game.Beatmaps public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && BeatmapSet.Hash == other.BeatmapSet.Hash && (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; - - public void Populate(OsuDbContext connection) - { - var entry = connection.Entry(this); - entry.Reference(nameof(Difficulty)); - } } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d364a8fbe4..955c6f4b17 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Ionic.Zip; using osu.Framework.Audio.Track; @@ -259,13 +260,10 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - lock (beatmaps) - { - if (!beatmaps.Delete(beatmapSet)) return; + if (!beatmaps.Delete(beatmapSet)) return; - if (!beatmapSet.Protected) - files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); - } + if (!beatmapSet.Protected) + files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); } /// @@ -304,9 +302,6 @@ namespace osu.Game.Beatmaps if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - lock (beatmaps) - beatmaps.Populate(beatmapInfo); - if (beatmapInfo.BeatmapSet == null) throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); @@ -338,10 +333,7 @@ namespace osu.Game.Beatmaps { lock (beatmaps) { - BeatmapSetInfo set = beatmaps.Query(query).FirstOrDefault(); - - if (set != null) - beatmaps.Populate(set); + BeatmapSetInfo set = beatmaps.QueryBeatmapSet(query); return set; } @@ -359,9 +351,9 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmapSets(Func query) + public List QueryBeatmapSets(Expression> query) { - return beatmaps.QueryAndPopulate(query); + return beatmaps.QueryBeatmapSets(query); } /// @@ -371,15 +363,9 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo QueryBeatmap(Func query) { - lock (beatmaps) - { - BeatmapInfo set = beatmaps.Query(query).FirstOrDefault(); + BeatmapInfo set = beatmaps.QueryBeatmap(query); - if (set != null) - beatmaps.Populate(set); - - return set; - } + return set; } /// @@ -387,9 +373,9 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmaps(Func query) + public List QueryBeatmaps(Expression> query) { - lock (beatmaps) return beatmaps.Query(query); + lock (beatmaps) return beatmaps.QueryBeatmaps(query); } /// @@ -428,7 +414,7 @@ namespace osu.Game.Beatmaps // check if this beatmap has already been imported and exit early if so. BeatmapSetInfo beatmapSet; lock (beatmaps) - beatmapSet = beatmaps.QueryAndPopulate(b => b.Hash == hash).FirstOrDefault(); + beatmapSet = beatmaps.QueryBeatmapSet(b => b.Hash == hash); if (beatmapSet != null) { @@ -492,8 +478,8 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Metadata = null; // TODO: this should be done in a better place once we actually need to dynamically update it. - beatmap.BeatmapInfo.Ruleset = rulesets.Query(r => r.ID == beatmap.BeatmapInfo.RulesetID).FirstOrDefault(); - beatmap.BeatmapInfo.StarDifficulty = rulesets.Query(r => r.ID == beatmap.BeatmapInfo.RulesetID).FirstOrDefault()?.CreateInstance()?.CreateDifficultyCalculator(beatmap) + beatmap.BeatmapInfo.Ruleset = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID); + beatmap.BeatmapInfo.StarDifficulty = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) .Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); @@ -507,11 +493,11 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(bool populate = true) + public List GetAllUsableBeatmapSets() { lock (beatmaps) { - return populate ? beatmaps.QueryAndPopulate(b => !b.DeletePending) : beatmaps.Query(b => !b.DeletePending); + return beatmaps.QueryBeatmapSets(b => !b.DeletePending); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ddbc5d0dfb..404add2fa5 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -4,12 +4,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; -using osu.Game.Database; -using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetInfo : IPopulate + public class BeatmapSetInfo { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -36,25 +34,5 @@ namespace osu.Game.Beatmaps public List Files { get; set; } public bool Protected { get; set; } - - public void Populate(OsuDbContext connection) - { - var entry = connection.Entry(this); - entry.Collection(nameof(Beatmaps)).Load(); - entry.Reference(nameof(Metadata)).Load(); - entry.Collection(nameof(Files)).Load(); - - foreach (var beatmap in Beatmaps) - { - var beatmapEntry = connection.Entry(beatmap); - beatmapEntry.Reference(nameof(beatmap.Difficulty)).Load(); - } - - foreach (var file in Files) - { - var fileEntry = connection.Entry(file); - fileEntry.Reference(nameof(file.FileInfo)).Load(); - } - } } } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 0be502132f..9c5a1a9dfc 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -2,7 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; @@ -132,5 +134,45 @@ namespace osu.Game.Beatmaps { Connection.BeatmapSetInfo.RemoveRange(Connection.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); } + + public BeatmapSetInfo QueryBeatmapSet(Func query) + { + return Connection.BeatmapSetInfo + .Include(b => b.Metadata) + .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) + .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) + .Include(b => b.Files).ThenInclude(f => f.FileInfo) + .FirstOrDefault(query); + } + + public List QueryBeatmapSets(Expression> query) + { + return Connection.BeatmapSetInfo + .Include(b => b.Metadata) + .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) + .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) + .Include(b => b.Files).ThenInclude(f => f.FileInfo) + .Where(query).ToList(); + } + + public BeatmapInfo QueryBeatmap(Func query) + { + return Connection.BeatmapInfo + .Include(b => b.BeatmapSet) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.Difficulty) + .FirstOrDefault(query); + } + + public List QueryBeatmaps(Expression> query) + { + return Connection.BeatmapInfo + .Include(b => b.BeatmapSet) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.Difficulty) + .Where(query).ToList(); + } } } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index fd891ca825..f4b6a866dc 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; @@ -50,46 +49,6 @@ namespace osu.Game.Database /// public void Reset() => Prepare(true); - public List Query(Func filter = null) where T : class - { - checkType(typeof(T)); - - var dbSet = Connection.GetType().GetProperties().Single(property => property.PropertyType == typeof(DbSet)).GetValue(Connection) as DbSet; - var query = dbSet.ToList(); - - if (filter != null) - query = query.Where(filter).ToList(); - - return query; - } - - /// - /// Query and populate results. - /// - /// An filter to refine results. - /// - public List QueryAndPopulate(Func filter) - where T : class, IPopulate - { - checkType(typeof(T)); - - var query = Query(filter); - foreach (var item in query) - Populate(item); - return query; - } - - /// - /// Populate a database-backed item. - /// - /// - public void Populate(IPopulate item) - { - checkType(item.GetType()); - - item.Populate(Connection); - } - private void checkType(Type type) { if (!ValidTypes.Contains(type)) diff --git a/osu.Game/Database/IPopulate.cs b/osu.Game/Database/IPopulate.cs deleted file mode 100644 index b07312f81c..0000000000 --- a/osu.Game/Database/IPopulate.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Database -{ - public interface IPopulate - { - void Populate(OsuDbContext connection); - } -} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 8a8bbd9244..5a4b33df0b 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -42,14 +42,14 @@ namespace osu.Game.Rulesets { Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } - + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { var rulesetInfo = createRulesetInfo(r); - if (Connection.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) + if (Connection.RulesetInfo.SingleOrDefault(rsi=>rsi.ID==rulesetInfo.ID)==null) { Connection.RulesetInfo.Add(rulesetInfo); } @@ -108,6 +108,19 @@ namespace osu.Game.Rulesets protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) }; - public RulesetInfo GetRuleset(int id) => Query().First(r => r.ID == id); + public RulesetInfo GetRuleset(int id) => Connection.RulesetInfo.First(r => r.ID == id); + + public RulesetInfo QueryRulesetInfo(Func query) + { + return Connection.RulesetInfo.FirstOrDefault(query); + } + + public List QueryRulesets(Func query = null) + { + var rulesets = Connection.RulesetInfo; + if (query != null) + return rulesets.Where(query).ToList(); + return rulesets.ToList(); + } } } diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index f4c0d0b1b6..1b7f2def4d 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.Query().First(), + Ruleset = rulesets.QueryRulesets().First(), Path = "normal.osu", Version = "Normal", Difficulty = new BeatmapDifficulty @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.Query().First(), + Ruleset = rulesets.QueryRulesets().First(), Path = "hard.osu", Version = "Hard", Difficulty = new BeatmapDifficulty @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.Query().First(), + Ruleset = rulesets.QueryRulesets().First(), Path = "insane.osu", Version = "Insane", Difficulty = new BeatmapDifficulty diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index b0953ceb7e..811e240cee 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual string instantiation = ruleset?.AssemblyQualifiedName; - foreach (var r in rulesets.Query(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) + foreach (var r in rulesets.QueryRulesets(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) AddStep(r.Name, () => loadPlayerFor(r)); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 03ca32539c..6fbd169e2a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -277,7 +277,6 @@ - 20171014052545_Init.cs From d73c0c0c98c9af6a4561ecc72e62854f7753eee5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 13:09:01 +0900 Subject: [PATCH 269/344] Fix test regression --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index f2ab00bead..cd9e765e7f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -138,8 +138,9 @@ namespace osu.Game.Tests.Beatmaps.IO var set = queryBeatmapSets().First(); foreach (BeatmapInfo b in set.Beatmaps) + Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); - Assert.IsTrue(set.Beatmaps.Count > 0); + Assert.IsTrue(set.Beatmaps.Count > 0); var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); From 0a6dcdd405d1bf27bc457dcea64bfb2802fbea3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 13:09:10 +0900 Subject: [PATCH 270/344] Fix remaining warnings --- osu.Game/Beatmaps/BeatmapManager.cs | 5 +---- osu.Game/IO/FileStore.cs | 15 +++++++-------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 955c6f4b17..e4c53cec94 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -60,8 +60,6 @@ namespace osu.Game.Beatmaps private readonly FileStore files; - private readonly OsuDbContext connection; - private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; @@ -93,7 +91,6 @@ namespace osu.Game.Beatmaps this.storage = storage; this.files = files; - this.connection = connection; this.rulesets = rulesets; this.api = api; @@ -164,7 +161,7 @@ namespace osu.Game.Beatmaps /// The beatmap to be imported. public BeatmapSetInfo Import(ArchiveReader archiveReader) { - BeatmapSetInfo set = null; + BeatmapSetInfo set; // let's only allow one concurrent import at a time for now. lock (importLock) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 680ecedf52..c15ebd264e 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -85,23 +85,22 @@ namespace osu.Game.IO public void Reference(params FileInfo[] files) { - var incrementedFiles = files.GroupBy(f => f.ID).Select(f => + foreach (var f in files.GroupBy(f => f.ID)) { - var accurateRefCount = Connection.Find(f.First().ID); - accurateRefCount.ReferenceCount += f.Count(); - return accurateRefCount; - }); + var refetch = Connection.Find(f.First().ID); + refetch.ReferenceCount += f.Count(); + } + Connection.SaveChanges(); } public void Dereference(params FileInfo[] files) { - var incrementedFiles = files.GroupBy(f => f.ID).Select(f => + foreach (var f in files.GroupBy(f => f.ID)) { var accurateRefCount = Connection.Find(f.First().ID); accurateRefCount.ReferenceCount -= f.Count(); - return accurateRefCount; - }); + } Connection.SaveChanges(); } From 4981630131a3343aed2004cb3549c97669a33d4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 13:11:35 +0900 Subject: [PATCH 271/344] Fix formatting regressions --- osu.Game/Database/DatabaseBackedStore.cs | 1 - osu.Game/Input/KeyBindingStore.cs | 2 +- osu.Game/Rulesets/RulesetStore.cs | 11 +++++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index f4b6a866dc..6b3fe232b2 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -36,7 +36,6 @@ namespace osu.Game.Database /// protected virtual void StartupTasks() { - } /// diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 41bdc40a88..e2b6c1dc87 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -67,7 +67,7 @@ namespace osu.Game.Input public void Update(KeyBinding keyBinding) { var dbKeyBinding = Connection.DatabasedKeyBinding.FirstOrDefault(kb => kb.ToString() == keyBinding.ToString()); - if (dbKeyBinding!=null) + if (dbKeyBinding != null) { dbKeyBinding.KeyCombination = keyBinding.KeyCombination; dbKeyBinding.Action = keyBinding.Action; diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5a4b33df0b..068997e732 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets public IEnumerable AllRulesets => Connection.RulesetInfo.Where(r => r.Available); - public RulesetStore(OsuDbContext connection) : base(connection) + public RulesetStore(OsuDbContext connection) + : base(connection) { } @@ -42,14 +43,14 @@ namespace osu.Game.Rulesets { Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } - + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { var rulesetInfo = createRulesetInfo(r); - if (Connection.RulesetInfo.SingleOrDefault(rsi=>rsi.ID==rulesetInfo.ID)==null) + if (Connection.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) { Connection.RulesetInfo.Add(rulesetInfo); } @@ -96,7 +97,9 @@ namespace osu.Game.Rulesets var assembly = Assembly.LoadFrom(file); loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsSubclassOf(typeof(Ruleset))); } - catch (Exception) { } + catch (Exception) + { + } } private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo From 29b46a5c5e22a2f0ec9e81344a1bac9cada1c6f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 13:23:41 +0900 Subject: [PATCH 272/344] Add missing reference to System.ValueTuple --- osu.Game/osu.Game.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6fbd169e2a..4923c399c5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -201,6 +201,9 @@ ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True + + ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + From a5ccf12e4a91c40046a72956d142b002c3e61e8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 14:04:04 +0900 Subject: [PATCH 273/344] Fix ValueTuple mess --- osu.Desktop/osu.Desktop.csproj | 3 --- osu.Desktop/packages.config | 27 +++++++++++++-------------- osu.Game/osu.Game.csproj | 3 ++- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 859ff42dad..fad297fa0a 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -158,9 +158,6 @@ - - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll - diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 7c4e9632e0..9cb31a6e25 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -1,15 +1,14 @@ - - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4923c399c5..2dd159188f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -202,7 +202,8 @@ True - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + True From 5b6c331434faf50fea6663f58ff36ad272f2e67c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 14:12:08 +0900 Subject: [PATCH 274/344] Fix all keybindings being reset every startup --- osu.Game/Input/KeyBindingStore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index e2b6c1dc87..f9578d150b 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -30,7 +30,8 @@ namespace osu.Game.Input protected override void Prepare(bool reset = false) { - Connection.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); + if (reset) + Connection.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) From 1a16784db9f8857ef69f20a0a82333ac7411147d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 14:18:01 +0900 Subject: [PATCH 275/344] Add BindingRedirects rule to fix dependency when running via nunit See https://github.com/ErikEJ/EntityFramework.SqlServerCompact/issues/463 --- osu.Game/osu.Game.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2dd159188f..6c447f560e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,6 +23,7 @@ LocalIntranet v4.6.1 true + true publish\ true Disk From 7049a73490cf17a75c7938cbecd55fe4925f39bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 14:47:33 +0900 Subject: [PATCH 276/344] Remove ValidTypes from databased stored; explicitly expose query methods instead --- osu.Game/Beatmaps/BeatmapStore.cs | 8 -------- osu.Game/Database/DatabaseBackedStore.cs | 9 --------- osu.Game/IO/FileStore.cs | 4 ---- osu.Game/Input/KeyBindingStore.cs | 6 ------ osu.Game/Rulesets/RulesetStore.cs | 2 -- osu.Game/Rulesets/Scoring/ScoreStore.cs | 3 --- 6 files changed, 32 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 9c5a1a9dfc..817b46041f 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -26,14 +26,6 @@ namespace osu.Game.Beatmaps { } - protected override Type[] ValidTypes => new[] - { - typeof(BeatmapSetInfo), - typeof(BeatmapInfo), - typeof(BeatmapMetadata), - typeof(BeatmapDifficulty), - }; - protected override void Prepare(bool reset = false) { if (reset) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 6b3fe232b2..46fc596584 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Linq; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; @@ -47,13 +46,5 @@ namespace osu.Game.Database /// Reset this database to a default state. Undo all changes to database and storage backings. /// public void Reset() => Prepare(true); - - private void checkType(Type type) - { - if (!ValidTypes.Contains(type)) - throw new InvalidOperationException($"The requested operation specified a type of {type}, which is invalid for this {nameof(DatabaseBackedStore)}."); - } - - protected abstract Type[] ValidTypes { get; } } } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index c15ebd264e..55b00b51d9 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -27,10 +27,6 @@ namespace osu.Game.IO Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); } - protected override Type[] ValidTypes => new[] { - typeof(FileInfo), - }; - protected override void Prepare(bool reset = false) { if (reset) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index f9578d150b..088e3bd75b 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; @@ -57,11 +56,6 @@ namespace osu.Game.Input } } - protected override Type[] ValidTypes => new[] - { - typeof(DatabasedKeyBinding) - }; - public List Query(int? rulesetId = null, int? variant = null) => new List(Connection.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant)); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 068997e732..fe9dfd7a11 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -109,8 +109,6 @@ namespace osu.Game.Rulesets ID = ruleset.LegacyID }; - protected override Type[] ValidTypes => new[] { typeof(RulesetInfo) }; - public RulesetInfo GetRuleset(int id) => Connection.RulesetInfo.First(r => r.ID == id); public RulesetInfo QueryRulesetInfo(Func query) diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 1604e8cb1c..66fcfb5d67 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using System.Collections.Generic; using System.IO; using osu.Framework.Platform; @@ -147,7 +146,5 @@ namespace osu.Game.Rulesets.Scoring protected override void Prepare(bool reset = false) { } - - protected override Type[] ValidTypes => new[] { typeof(Score) }; } } From 1f4a943f74dfeb8340296e0187c4ca865866ecd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 15:09:43 +0900 Subject: [PATCH 277/344] Fix test case runs not being correctly isolated on mono --- osu.Game/Tests/Visual/OsuTestCase.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index d722f7d711..90c6e427c4 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -11,8 +11,15 @@ namespace osu.Game.Tests.Visual { public override void RunTest() { - using (var host = new HeadlessGameHost(AppDomain.CurrentDomain.FriendlyName.Replace(' ', '-'), realtime: false)) + Storage storage; + using (var host = new HeadlessGameHost($"test-{Guid.NewGuid()}", realtime: false)) + { + storage = host.Storage; host.Run(new OsuTestCaseTestRunner(this)); + } + + // clean up after each run + storage.DeleteDirectory(string.Empty); } public class OsuTestCaseTestRunner : OsuGameBase From ec51314e37a52d7880d2353df55eae151940bdf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 16:11:34 +0900 Subject: [PATCH 278/344] Remove duplicate command --- osu.Game/Database/DatabaseBackedStore.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 46fc596584..adddad6122 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; @@ -17,7 +16,6 @@ namespace osu.Game.Database { Storage = storage; Connection = connection; - Connection.Database.SetCommandTimeout(new TimeSpan(TimeSpan.TicksPerSecond * 10)); try { From 767775988eaf2779805f12f2f337ca0d02386241 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Oct 2017 16:54:14 +0900 Subject: [PATCH 279/344] Add NS and OS as abbreviations Satisfies Jetbrains Rider --- osu-framework | 2 +- osu.sln.DotSettings | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 0bc71f95b4..48ea66774c 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 0bc71f95b455d3829b2abf662b5fe25989e6c43c +Subproject commit 48ea66774c76427b871b71c6c5f4e01ebecf1af2 diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index c236ce82b0..7808ed200b 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -193,6 +193,8 @@ IPC LTRB MD5 + NS + OS RGB RNG SHA From acc299c7b97b24dc234e27fae049a421c609ab1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 17:02:31 +0900 Subject: [PATCH 280/344] Correct and simplify RulesetStore --- osu.Game/Beatmaps/BeatmapManager.cs | 7 +-- osu.Game/Input/KeyBindingStore.cs | 2 +- osu.Game/Overlays/Direct/FilterControl.cs | 2 +- osu.Game/Overlays/KeyBindingOverlay.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 +- .../Settings/Sections/GameplaySection.cs | 2 +- osu.Game/Overlays/Settings/SettingsFooter.cs | 2 +- .../Overlays/Toolbar/ToolbarModeSelector.cs | 2 +- osu.Game/Rulesets/RulesetStore.cs | 44 +++++++++---------- osu.Game/Tests/Visual/TestCaseMods.cs | 2 +- .../Tests/Visual/TestCasePlaySongSelect.cs | 6 +-- osu.Game/Tests/Visual/TestCasePlayer.cs | 2 +- 12 files changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e4c53cec94..985d2c7a15 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -474,10 +474,11 @@ namespace osu.Game.Beatmaps // TODO: Diff beatmap metadata with set metadata and leave it here if necessary beatmap.BeatmapInfo.Metadata = null; + 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 = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID); - beatmap.BeatmapInfo.StarDifficulty = rulesets.QueryRulesetInfo(r => r.ID == beatmap.BeatmapInfo.RulesetID)?.CreateInstance()?.CreateDifficultyCalculator(beatmap) - .Calculate() ?? 0; + beatmap.BeatmapInfo.Ruleset = ruleset; + beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0; beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 088e3bd75b..5cc018ff2c 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -17,7 +17,7 @@ namespace osu.Game.Input public KeyBindingStore(OsuDbContext connection, RulesetStore rulesets, Storage storage = null) : base(connection, storage) { - foreach (var info in rulesets.AllRulesets) + foreach (var info in rulesets.AvailableRulesets) { var ruleset = info.CreateInstance(); foreach (var variant in ruleset.AvailableVariants) diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 28d26d0641..9b52cfd367 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Direct DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; Ruleset.BindTo(game?.Ruleset ?? new Bindable { Value = rulesets.GetRuleset(0) }); - foreach (var r in rulesets.AllRulesets) + foreach (var r in rulesets.AvailableRulesets) { modeButtons.Add(new RulesetToggleButton(Ruleset, r)); } diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingOverlay.cs index 72c653030c..4394d0fec0 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingOverlay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays { AddSection(new GlobalKeyBindingsSection(global)); - foreach (var ruleset in rulesets.AllRulesets) + foreach (var ruleset in rulesets.AvailableRulesets) AddSection(new RulesetBindingsSection(ruleset)); } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index eb643f390f..9ff21dfdd4 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Mods if (osu != null) Ruleset.BindTo(osu.Ruleset); else - Ruleset.Value = rulesets.AllRulesets.First(); + Ruleset.Value = rulesets.AvailableRulesets.First(); Ruleset.ValueChanged += rulesetChanged; Ruleset.TriggerChange(); diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 326cb582e2..035a3c7a13 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - foreach(Ruleset ruleset in rulesets.AllRulesets.Select(info => info.CreateInstance())) + foreach(Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance())) { SettingsSubsection section = ruleset.CreateSettings(); if (section != null) diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index cb1c861ee7..bf417a2fac 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings var modes = new List(); - foreach (var ruleset in rulesets.AllRulesets) + foreach (var ruleset in rulesets.AvailableRulesets) { var icon = new ConstrainedIconContainer { diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index 60c1261190..da72ae0347 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader] private void load(RulesetStore rulesets, OsuGame game) { - foreach (var r in rulesets.AllRulesets) + foreach (var r in rulesets.AvailableRulesets) { modeButtons.Add(new ToolbarModeButton { diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index fe9dfd7a11..c39312205c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -18,13 +18,6 @@ namespace osu.Game.Rulesets { private static readonly Dictionary loaded_assemblies = new Dictionary(); - public IEnumerable AllRulesets => Connection.RulesetInfo.Where(r => r.Available); - - public RulesetStore(OsuDbContext connection) - : base(connection) - { - } - static RulesetStore() { AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; @@ -33,6 +26,23 @@ namespace osu.Game.Rulesets loadRulesetFromFile(file); } + public RulesetStore(OsuDbContext connection) + : base(connection) + { + } + + /// + /// Retrieve a ruleset using a known ID. + /// + /// The ruleset's internal ID. + /// A ruleset, if available, else null. + public RulesetInfo GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.ID == id); + + /// + /// All available rulesets. + /// + public IEnumerable AvailableRulesets => Connection.RulesetInfo.Where(r => r.Available); + private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); private const string ruleset_library_prefix = "osu.Game.Rulesets"; @@ -44,7 +54,7 @@ namespace osu.Game.Rulesets Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } - var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())); + var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())).ToList(); //add all legacy modes in correct order foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) @@ -55,6 +65,7 @@ namespace osu.Game.Rulesets Connection.RulesetInfo.Add(rulesetInfo); } } + Connection.SaveChanges(); //add any other modes @@ -67,6 +78,7 @@ namespace osu.Game.Rulesets if (existing == null) Connection.RulesetInfo.Add(us); } + Connection.SaveChanges(); //perform a consistency check @@ -82,6 +94,7 @@ namespace osu.Game.Rulesets r.Available = false; } } + Connection.SaveChanges(); } @@ -108,20 +121,5 @@ namespace osu.Game.Rulesets InstantiationInfo = ruleset.GetType().AssemblyQualifiedName, ID = ruleset.LegacyID }; - - public RulesetInfo GetRuleset(int id) => Connection.RulesetInfo.First(r => r.ID == id); - - public RulesetInfo QueryRulesetInfo(Func query) - { - return Connection.RulesetInfo.FirstOrDefault(query); - } - - public List QueryRulesets(Func query = null) - { - var rulesets = Connection.RulesetInfo; - if (query != null) - return rulesets.Where(query).ToList(); - return rulesets.ToList(); - } } } diff --git a/osu.Game/Tests/Visual/TestCaseMods.cs b/osu.Game/Tests/Visual/TestCaseMods.cs index ef250edcc3..0447d6582d 100644 --- a/osu.Game/Tests/Visual/TestCaseMods.cs +++ b/osu.Game/Tests/Visual/TestCaseMods.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("Toggle", modSelect.ToggleVisibility); - foreach (var ruleset in rulesets.AllRulesets) + foreach (var ruleset in rulesets.AvailableRulesets) AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); } } diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index 1b7f2def4d..a8a00e9a0d 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1234 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.AvailableRulesets.First(), Path = "normal.osu", Version = "Normal", Difficulty = new BeatmapDifficulty @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1235 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.AvailableRulesets.First(), Path = "hard.osu", Version = "Hard", Difficulty = new BeatmapDifficulty @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual new BeatmapInfo { OnlineBeatmapID = 1236 + i, - Ruleset = rulesets.QueryRulesets().First(), + Ruleset = rulesets.AvailableRulesets.First(), Path = "insane.osu", Version = "Insane", Difficulty = new BeatmapDifficulty diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index 811e240cee..dfbdaf1d9d 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual string instantiation = ruleset?.AssemblyQualifiedName; - foreach (var r in rulesets.QueryRulesets(rs => rs.Available && (instantiation == null || rs.InstantiationInfo == instantiation))) + foreach (var r in rulesets.AvailableRulesets.Where(rs => instantiation == null || rs.InstantiationInfo == instantiation)) AddStep(r.Name, () => loadPlayerFor(r)); } From f9aba6fa27f10eefb6e431c62e7f4d52fed5c22b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 16 Oct 2017 17:11:54 +0900 Subject: [PATCH 281/344] Update framework once more --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 48ea66774c..b1f36efca5 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 48ea66774c76427b871b71c6c5f4e01ebecf1af2 +Subproject commit b1f36efca59840da65df788a52107b1674a904c6 From 9ee6d1e3f9d9274cf7d37ab6f17b9eea8c93438c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 17:58:17 +0900 Subject: [PATCH 282/344] Correct and simplify KeyBindingStore --- osu.Game/Input/KeyBindingStore.cs | 30 ++++++++++--------- .../KeyBinding/KeyBindingsSubsection.cs | 4 ++- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 5cc018ff2c..9edab896b3 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -38,11 +38,13 @@ namespace osu.Game.Input // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count; - while (group.Count() > (count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key))) - { - var insertable = group.Skip(count).First(); + int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); + int aimCount = group.Count(); + if (aimCount <= count) + continue; + + foreach (var insertable in group.Skip(count).Take(aimCount - count)) // insert any defaults which are missing. Connection.DatabasedKeyBinding.Add(new DatabasedKeyBinding { @@ -51,22 +53,22 @@ namespace osu.Game.Input RulesetID = rulesetId, Variant = variant }); - Connection.SaveChanges(); - } } + + Connection.SaveChanges(); } - public List Query(int? rulesetId = null, int? variant = null) => - new List(Connection.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant)); + /// + /// Retrieve s for a specified ruleset/variant content. + /// + /// The ruleset's internal ID. + /// An optional variant. + /// + public IEnumerable Query(int? rulesetId = null, int? variant = null) => Connection.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); public void Update(KeyBinding keyBinding) { - var dbKeyBinding = Connection.DatabasedKeyBinding.FirstOrDefault(kb => kb.ToString() == keyBinding.ToString()); - if (dbKeyBinding != null) - { - dbKeyBinding.KeyCombination = keyBinding.KeyCombination; - dbKeyBinding.Action = keyBinding.Action; - } + Connection.Update(keyBinding); Connection.SaveChanges(); } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index bd69403831..128b5e2f09 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -37,8 +37,10 @@ namespace osu.Game.Overlays.KeyBinding foreach (var defaultGroup in Defaults.GroupBy(d => d.Action)) { + int intKey = (int)defaultGroup.Key; + // one row per valid action. - Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals((int)defaultGroup.Key))) + Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => ((int)b.Action).Equals(intKey))) { AllowMainMouseButtons = Ruleset != null, Defaults = defaultGroup.Select(d => d.KeyCombination) From e378d0685db0847a33d4de796a26f6ba8879b7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 20:28:49 +0900 Subject: [PATCH 283/344] Remove weird additions --- osu.Game.Tests/app.config | 4 ---- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 3 +-- osu.Game/osu.Game.csproj | 13 ------------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config index 11af32e2cf..faeaf001de 100644 --- a/osu.Game.Tests/app.config +++ b/osu.Game.Tests/app.config @@ -6,10 +6,6 @@ - - - - \ No newline at end of file diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 2568af6ce1..27d1f057ca 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.ComponentModel.DataAnnotations; using Newtonsoft.Json; namespace osu.Game.Beatmaps @@ -30,7 +29,7 @@ namespace osu.Game.Beatmaps /// /// The different sizes of cover art for this beatmap set. /// - [Required, JsonProperty(@"covers")] + [JsonProperty(@"covers")] public BeatmapSetOnlineCovers Covers { get; set; } /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6c447f560e..ced7ca7318 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,6 @@ LocalIntranet v4.6.1 true - true publish\ true Disk @@ -202,10 +201,6 @@ ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True - - ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll - True - @@ -862,14 +857,6 @@ - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - From eff1c20e38cd9fd8583bc6637bea61c53e5476d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 20:50:34 +0900 Subject: [PATCH 284/344] CI fixing --- osu.Desktop/app.config | 4 ++++ osu.Desktop/osu.Desktop.csproj | 4 ++++ osu.Desktop/packages.config | 1 + osu.Game.Tests/osu.Game.Tests.csproj | 4 ++++ osu.Game.Tests/packages.config | 17 +++++++++-------- osu.Game/app.config | 4 ++++ osu.Game/osu.Game.csproj | 8 ++++++++ 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/osu.Desktop/app.config b/osu.Desktop/app.config index 824430b24a..0841541f3d 100644 --- a/osu.Desktop/app.config +++ b/osu.Desktop/app.config @@ -11,6 +11,10 @@ + + + + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index fad297fa0a..7b2ec3b24c 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -158,6 +158,10 @@ + + ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + True + diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 9cb31a6e25..f47fe8ac62 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -11,4 +11,5 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e188c82e79..7a3bbab176 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -39,6 +39,10 @@ True + + ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + True + diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index b94c0c6e2d..ecc44f0c70 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -1,9 +1,10 @@ - - - - - + + + + + + \ No newline at end of file diff --git a/osu.Game/app.config b/osu.Game/app.config index a704cc3750..7f2ad68041 100644 --- a/osu.Game/app.config +++ b/osu.Game/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ced7ca7318..a442d1cef6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -201,6 +201,10 @@ ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True + + ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + True + @@ -857,6 +861,10 @@ + + true + true + From 81476ebe7543a921b962c70f8e7b065596a9e629 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Oct 2017 22:04:22 +0900 Subject: [PATCH 285/344] Correct and simplify BeatmapStore and BeatmapManager --- osu.Game/Beatmaps/BeatmapManager.cs | 64 ++++++++++++++++++----------- osu.Game/Beatmaps/BeatmapStore.cs | 59 ++++++++------------------ 2 files changed, 57 insertions(+), 66 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 985d2c7a15..af88f22e40 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; using System.Threading.Tasks; using Ionic.Zip; using osu.Framework.Audio.Track; @@ -179,7 +178,8 @@ namespace osu.Game.Beatmaps // If we have an ID then we already exist in the database. if (beatmapSetInfo.ID != 0) return; - beatmaps.Add(beatmapSetInfo); + lock (beatmaps) + beatmaps.Add(beatmapSetInfo); } /// @@ -257,7 +257,8 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - if (!beatmaps.Delete(beatmapSet)) return; + lock (beatmaps) + if (!beatmaps.Delete(beatmapSet)) return; if (!beatmapSet.Protected) files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -267,13 +268,21 @@ namespace osu.Game.Beatmaps /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); + public void Hide(BeatmapInfo beatmap) + { + lock (beatmaps) + beatmaps.Hide(beatmap); + } /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); + public void Restore(BeatmapInfo beatmap) + { + lock (beatmaps) + beatmaps.Restore(beatmap); + } /// /// Returns a to a usable state if it has previously been deleted but not yet purged. @@ -282,7 +291,8 @@ namespace osu.Game.Beatmaps /// The beatmap to restore. public void Undelete(BeatmapSetInfo beatmapSet) { - if (!beatmaps.Undelete(beatmapSet)) return; + lock (beatmaps) + if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -329,11 +339,7 @@ namespace osu.Game.Beatmaps public BeatmapSetInfo QueryBeatmapSet(Func query) { lock (beatmaps) - { - BeatmapSetInfo set = beatmaps.QueryBeatmapSet(query); - - return set; - } + return beatmaps.BeatmapSets.FirstOrDefault(query); } /// @@ -348,9 +354,10 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmapSets(Expression> query) + public List QueryBeatmapSets(Func query) { - return beatmaps.QueryBeatmapSets(query); + lock (beatmaps) + return beatmaps.BeatmapSets.Where(query).ToList(); } /// @@ -360,9 +367,8 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapInfo QueryBeatmap(Func query) { - BeatmapInfo set = beatmaps.QueryBeatmap(query); - - return set; + lock (beatmaps) + return beatmaps.Beatmaps.FirstOrDefault(query); } /// @@ -370,9 +376,10 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmaps(Expression> query) + public List QueryBeatmaps(Func query) { - lock (beatmaps) return beatmaps.QueryBeatmaps(query); + lock (beatmaps) + return beatmaps.Beatmaps.Where(query).ToList(); } /// @@ -411,7 +418,7 @@ namespace osu.Game.Beatmaps // check if this beatmap has already been imported and exit early if so. BeatmapSetInfo beatmapSet; lock (beatmaps) - beatmapSet = beatmaps.QueryBeatmapSet(b => b.Hash == hash); + beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash); if (beatmapSet != null) { @@ -494,9 +501,7 @@ namespace osu.Game.Beatmaps public List GetAllUsableBeatmapSets() { lock (beatmaps) - { - return beatmaps.QueryBeatmapSets(b => !b.DeletePending); - } + return beatmaps.BeatmapSets.ToList(); } protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap @@ -531,7 +536,10 @@ namespace osu.Game.Beatmaps return beatmap; } - catch { return null; } + catch + { + return null; + } } private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; @@ -545,7 +553,10 @@ namespace osu.Game.Beatmaps { return new TextureStore(new RawTextureLoaderStore(store), false).Get(getPathForFile(Metadata.BackgroundFile)); } - catch { return null; } + catch + { + return null; + } } protected override Track GetTrack() @@ -555,7 +566,10 @@ namespace osu.Game.Beatmaps var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); return trackData == null ? null : new TrackBass(trackData); } - catch { return new TrackVirtual(); } + catch + { + return new TrackVirtual(); + } } protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 817b46041f..6464db86d4 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; @@ -51,7 +50,7 @@ namespace osu.Game.Beatmaps /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { - Connection.BeatmapSetInfo.Update(beatmapSet); + Connection.BeatmapSetInfo.Add(beatmapSet); Connection.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); @@ -67,7 +66,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - Connection.BeatmapSetInfo.Remove(beatmapSet); + Connection.BeatmapSetInfo.Update(beatmapSet); Connection.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); @@ -85,6 +84,7 @@ namespace osu.Game.Beatmaps beatmapSet.DeletePending = false; Connection.BeatmapSetInfo.Update(beatmapSet); + Connection.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); return true; @@ -101,6 +101,7 @@ namespace osu.Game.Beatmaps beatmap.Hidden = true; Connection.BeatmapInfo.Update(beatmap); + Connection.SaveChanges(); BeatmapHidden?.Invoke(beatmap); return true; @@ -117,6 +118,7 @@ namespace osu.Game.Beatmaps beatmap.Hidden = false; Connection.BeatmapInfo.Update(beatmap); + Connection.SaveChanges(); BeatmapRestored?.Invoke(beatmap); return true; @@ -125,46 +127,21 @@ namespace osu.Game.Beatmaps private void cleanupPendingDeletions() { Connection.BeatmapSetInfo.RemoveRange(Connection.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); + Connection.SaveChanges(); } - public BeatmapSetInfo QueryBeatmapSet(Func query) - { - return Connection.BeatmapSetInfo - .Include(b => b.Metadata) - .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) - .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) - .Include(b => b.Files).ThenInclude(f => f.FileInfo) - .FirstOrDefault(query); - } + public IEnumerable BeatmapSets => Connection.BeatmapSetInfo + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Where(s => !s.DeletePending); - public List QueryBeatmapSets(Expression> query) - { - return Connection.BeatmapSetInfo - .Include(b => b.Metadata) - .Include(b => b.Beatmaps).ThenInclude(b => b.Ruleset) - .Include(b => b.Beatmaps).ThenInclude(b => b.Difficulty) - .Include(b => b.Files).ThenInclude(f => f.FileInfo) - .Where(query).ToList(); - } - - public BeatmapInfo QueryBeatmap(Func query) - { - return Connection.BeatmapInfo - .Include(b => b.BeatmapSet) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.Difficulty) - .FirstOrDefault(query); - } - - public List QueryBeatmaps(Expression> query) - { - return Connection.BeatmapInfo - .Include(b => b.BeatmapSet) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.Difficulty) - .Where(query).ToList(); - } + public IEnumerable Beatmaps => Connection.BeatmapInfo + .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.Difficulty); } } From 799f51021700d394d5e19a5f329816ca018bd9a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 10:25:18 +0900 Subject: [PATCH 286/344] FileStore logic fixes --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 -- osu.Game/IO/FileStore.cs | 16 +++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cd9e765e7f..087fb54b5f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -95,8 +95,6 @@ namespace osu.Game.Tests.Beatmaps.IO private OsuGameBase loadOsu(GameHost host) { - host.Storage.DeleteDatabase(@"client"); - var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 55b00b51d9..ae0cfb30c8 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -31,10 +31,6 @@ namespace osu.Game.IO { if (reset) { - // in earlier versions we stored beatmaps as solid archives, but not any more. - if (Storage.ExistsDirectory("beatmaps")) - Storage.DeleteDirectory("beatmaps"); - if (Storage.ExistsDirectory(prefix)) Storage.DeleteDirectory(prefix); @@ -70,12 +66,14 @@ namespace osu.Game.IO } if (existing == null) + { Connection.FileInfo.Add(info); + Connection.SaveChanges(); + } if (reference || existing == null) Reference(info); - Connection.SaveChanges(); return info; } @@ -85,6 +83,7 @@ namespace osu.Game.IO { var refetch = Connection.Find(f.First().ID); refetch.ReferenceCount += f.Count(); + Connection.Update(refetch); } Connection.SaveChanges(); @@ -94,8 +93,9 @@ namespace osu.Game.IO { foreach (var f in files.GroupBy(f => f.ID)) { - var accurateRefCount = Connection.Find(f.First().ID); - accurateRefCount.ReferenceCount -= f.Count(); + var refetch = Connection.Find(f.First().ID); + refetch.ReferenceCount -= f.Count(); + Connection.Update(refetch); } Connection.SaveChanges(); @@ -115,6 +115,8 @@ namespace osu.Game.IO Logger.Error(e, $@"Could not delete beatmap {f}"); } } + + Connection.SaveChanges(); } } } From ef10bb73db05785a42def26ca2820e1d567a1b68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 10:25:25 +0900 Subject: [PATCH 287/344] osu.Game csproj fixes --- osu.Game/osu.Game.csproj | 62 +++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a442d1cef6..731e065a48 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -105,46 +105,46 @@ - ..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll + $(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll - ..\packages\Microsoft.EntityFrameworkCore.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.dll - ..\packages\Microsoft.EntityFrameworkCore.Design.2.0.0\lib\net461\Microsoft.EntityFrameworkCore.Design.dll + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Design.2.0.0\lib\net461\Microsoft.EntityFrameworkCore.Design.dll - ..\packages\Microsoft.EntityFrameworkCore.Relational.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Relational.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Relational.dll - ..\packages\Microsoft.EntityFrameworkCore.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Sqlite.dll + $(SolutionDir)\packages\Microsoft.EntityFrameworkCore.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.EntityFrameworkCore.Sqlite.dll - ..\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll + $(SolutionDir)\packages\Microsoft.Extensions.Caching.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll - ..\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll + $(SolutionDir)\packages\Microsoft.Extensions.Caching.Memory.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll - ..\packages\Microsoft.Extensions.Configuration.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + $(SolutionDir)\packages\Microsoft.Extensions.Configuration.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll - ..\packages\Microsoft.Extensions.DependencyInjection.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll + $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.dll - ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + $(SolutionDir)\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll - ..\packages\Microsoft.Extensions.Logging.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + $(SolutionDir)\packages\Microsoft.Extensions.Logging.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.dll - ..\packages\Microsoft.Extensions.Logging.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + $(SolutionDir)\packages\Microsoft.Extensions.Logging.Abstractions.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll - ..\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + $(SolutionDir)\packages\Microsoft.Extensions.Options.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll - ..\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + $(SolutionDir)\packages\Microsoft.Extensions.Primitives.2.0.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll @@ -160,49 +160,53 @@ True - ..\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll + $(SolutionDir)\packages\Remotion.Linq.2.1.2\lib\net45\Remotion.Linq.dll $(SolutionDir)\packages\SharpCompress.0.18.1\lib\net45\SharpCompress.dll True - ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_green.dll + $(SolutionDir)\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_green.dll + True - ..\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_v2.dll + $(SolutionDir)\packages\SQLitePCLRaw.bundle_green.1.1.8\lib\net45\SQLitePCLRaw.batteries_v2.dll + True - ..\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll + $(SolutionDir)\packages\SQLitePCLRaw.core.1.1.8\lib\net45\SQLitePCLRaw.core.dll + True - ..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + $(SolutionDir)\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.8\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + True - ..\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll + $(SolutionDir)\packages\System.Collections.Immutable.1.4.0\lib\netstandard2.0\System.Collections.Immutable.dll - ..\packages\System.ComponentModel.Annotations.4.4.0\lib\net461\System.ComponentModel.Annotations.dll + $(SolutionDir)\packages\System.ComponentModel.Annotations.4.4.0\lib\net461\System.ComponentModel.Annotations.dll - ..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll + $(SolutionDir)\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll - ..\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll + $(SolutionDir)\packages\System.Interactive.Async.3.1.1\lib\net46\System.Interactive.Async.dll - ..\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + $(SolutionDir)\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll + $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True - ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True @@ -865,7 +869,7 @@ true true - - - + + + \ No newline at end of file From 31dc5c97f2e0399795767332bddab53a5959c24f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 11:10:55 +0900 Subject: [PATCH 288/344] Fix intro and duplicate inserts --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapStore.cs | 5 ++-- osu.Game/IO/FileStore.cs | 4 +--- osu.Game/Screens/Menu/Intro.cs | 1 - osu.Game/Screens/Select/SongSelect.cs | 34 +++++++++++++-------------- 5 files changed, 21 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index af88f22e40..f546172bfe 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -501,7 +501,7 @@ namespace osu.Game.Beatmaps public List GetAllUsableBeatmapSets() { lock (beatmaps) - return beatmaps.BeatmapSets.ToList(); + return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList(); } protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 6464db86d4..ea5c5f7155 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { - Connection.BeatmapSetInfo.Add(beatmapSet); + Connection.BeatmapSetInfo.Attach(beatmapSet); Connection.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); @@ -135,8 +135,7 @@ namespace osu.Game.Beatmaps .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Where(s => !s.DeletePending); + .Include(s => s.Files).ThenInclude(f => f.FileInfo); public IEnumerable Beatmaps => Connection.BeatmapInfo .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index ae0cfb30c8..19237c6063 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -66,10 +66,8 @@ namespace osu.Game.IO } if (existing == null) - { + // SaveChanges is performed in Reference. Connection.FileInfo.Add(info); - Connection.SaveChanges(); - } if (reference || existing == null) Reference(info); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index b597fc11e4..ee84cf2d30 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -89,7 +89,6 @@ namespace osu.Game.Screens.Menu { // we need to import the default menu background beatmap setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"))); - setInfo.Protected = true; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b11613634a..e11eed7040 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { - private BeatmapManager manager; + private BeatmapManager beatmaps; protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); private readonly BeatmapCarousel carousel; @@ -108,9 +108,9 @@ namespace osu.Game.Screens.Select SelectionChanged = carouselSelectionChanged, BeatmapsChanged = carouselBeatmapsLoaded, DeleteRequested = promptDelete, - RestoreRequested = s => { foreach (var b in s.Beatmaps) manager.Restore(b); }, + RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); }, EditRequested = editRequested, - HideDifficultyRequested = b => manager.Hide(b), + HideDifficultyRequested = b => beatmaps.Hide(b), StartRequested = () => carouselRaisedStart(), }); Add(FilterControl = new FilterControl @@ -171,16 +171,16 @@ namespace osu.Game.Screens.Select BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); } - if (manager == null) - manager = beatmaps; + if (this.beatmaps == null) + this.beatmaps = beatmaps; if (osu != null) Ruleset.BindTo(osu.Ruleset); - manager.BeatmapSetAdded += onBeatmapSetAdded; - manager.BeatmapSetRemoved += onBeatmapSetRemoved; - manager.BeatmapHidden += onBeatmapHidden; - manager.BeatmapRestored += onBeatmapRestored; + this.beatmaps.BeatmapSetAdded += onBeatmapSetAdded; + this.beatmaps.BeatmapSetRemoved += onBeatmapSetRemoved; + this.beatmaps.BeatmapHidden += onBeatmapHidden; + this.beatmaps.BeatmapRestored += onBeatmapRestored; dialogOverlay = dialog; @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); - carousel.Beatmaps = manager.GetAllUsableBeatmapSets(); + carousel.Beatmaps = this.beatmaps.GetAllUsableBeatmapSets(); Beatmap.ValueChanged += beatmap_ValueChanged; @@ -199,7 +199,7 @@ namespace osu.Game.Screens.Select private void editRequested(BeatmapInfo beatmap) { - Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); Push(new Editor()); } @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Select { bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = manager.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); ensurePlayingSelected(preview); } @@ -357,12 +357,12 @@ namespace osu.Game.Screens.Select { base.Dispose(isDisposing); - if (manager != null) + if (beatmaps != null) { - manager.BeatmapSetAdded -= onBeatmapSetAdded; - manager.BeatmapSetRemoved -= onBeatmapSetRemoved; - manager.BeatmapHidden -= onBeatmapHidden; - manager.BeatmapRestored -= onBeatmapRestored; + beatmaps.BeatmapSetAdded -= onBeatmapSetAdded; + beatmaps.BeatmapSetRemoved -= onBeatmapSetRemoved; + beatmaps.BeatmapHidden -= onBeatmapHidden; + beatmaps.BeatmapRestored -= onBeatmapRestored; } initialAddSetsTask?.Cancel(); From 0df474accb7a10ec10aa9c2fa17b71c1e1bdd4f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 11:22:50 +0900 Subject: [PATCH 289/344] Simplify file storing --- osu.Game/IO/FileStore.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 19237c6063..ede85ad42f 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -65,10 +65,6 @@ namespace osu.Game.IO data.Seek(0, SeekOrigin.Begin); } - if (existing == null) - // SaveChanges is performed in Reference. - Connection.FileInfo.Add(info); - if (reference || existing == null) Reference(info); @@ -79,9 +75,9 @@ namespace osu.Game.IO { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = Connection.Find(f.First().ID); + var refetch = Connection.Find(f.First().ID) ?? f.First(); refetch.ReferenceCount += f.Count(); - Connection.Update(refetch); + Connection.FileInfo.Update(refetch); } Connection.SaveChanges(); From c92e0e2dc19f56fe770ddfbf75e658f67feac35f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 11:24:59 +0900 Subject: [PATCH 290/344] Fix username display on beatmap panels --- osu.Game/Beatmaps/Drawables/BeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index e216f1b83e..c0705d8f61 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -135,7 +135,7 @@ namespace osu.Game.Beatmaps.Drawables new OsuSpriteText { Font = @"Exo2.0-MediumItalic", - Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author}", + Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}", TextSize = 16, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft From 66894d11ea1c1b8358fbe81dc01752a6fea67361 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 11:39:47 +0900 Subject: [PATCH 291/344] Connection -> context --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +-- osu.Game/Beatmaps/BeatmapStore.cs | 42 ++++++++++++------------ osu.Game/Database/DatabaseBackedStore.cs | 6 ++-- osu.Game/IO/FileStore.cs | 24 +++++++------- osu.Game/Input/KeyBindingStore.cs | 16 ++++----- osu.Game/Rulesets/RulesetStore.cs | 24 +++++++------- osu.Game/Rulesets/Scoring/ScoreStore.cs | 2 +- 7 files changed, 59 insertions(+), 59 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f546172bfe..7d174e6a28 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -80,9 +80,9 @@ namespace osu.Game.Beatmaps /// public Func GetStableStorage { private get; set; } - public BeatmapManager(Storage storage, FileStore files, OsuDbContext connection, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) + public BeatmapManager(Storage storage, FileStore files, OsuDbContext context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) { - beatmaps = new BeatmapStore(connection); + beatmaps = new BeatmapStore(context); beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index ea5c5f7155..134059fd07 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -20,8 +20,8 @@ namespace osu.Game.Beatmaps public event Action BeatmapHidden; public event Action BeatmapRestored; - public BeatmapStore(OsuDbContext connection) - : base(connection) + public BeatmapStore(OsuDbContext context) + : base(context) { } @@ -30,11 +30,11 @@ namespace osu.Game.Beatmaps if (reset) { // https://stackoverflow.com/a/10450893 - Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata"); - Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty"); - Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo"); - Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo"); - Connection.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo"); + Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata"); + Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty"); + Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo"); + Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo"); + Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo"); } } @@ -50,8 +50,8 @@ namespace osu.Game.Beatmaps /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { - Connection.BeatmapSetInfo.Attach(beatmapSet); - Connection.SaveChanges(); + Context.BeatmapSetInfo.Attach(beatmapSet); + Context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); } @@ -66,8 +66,8 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - Connection.BeatmapSetInfo.Update(beatmapSet); - Connection.SaveChanges(); + Context.BeatmapSetInfo.Update(beatmapSet); + Context.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); return true; @@ -83,8 +83,8 @@ namespace osu.Game.Beatmaps if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; - Connection.BeatmapSetInfo.Update(beatmapSet); - Connection.SaveChanges(); + Context.BeatmapSetInfo.Update(beatmapSet); + Context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); return true; @@ -100,8 +100,8 @@ namespace osu.Game.Beatmaps if (beatmap.Hidden) return false; beatmap.Hidden = true; - Connection.BeatmapInfo.Update(beatmap); - Connection.SaveChanges(); + Context.BeatmapInfo.Update(beatmap); + Context.SaveChanges(); BeatmapHidden?.Invoke(beatmap); return true; @@ -117,8 +117,8 @@ namespace osu.Game.Beatmaps if (!beatmap.Hidden) return false; beatmap.Hidden = false; - Connection.BeatmapInfo.Update(beatmap); - Connection.SaveChanges(); + Context.BeatmapInfo.Update(beatmap); + Context.SaveChanges(); BeatmapRestored?.Invoke(beatmap); return true; @@ -126,18 +126,18 @@ namespace osu.Game.Beatmaps private void cleanupPendingDeletions() { - Connection.BeatmapSetInfo.RemoveRange(Connection.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); - Connection.SaveChanges(); + Context.BeatmapSetInfo.RemoveRange(Context.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); + Context.SaveChanges(); } - public IEnumerable BeatmapSets => Connection.BeatmapSetInfo + public IEnumerable BeatmapSets => Context.BeatmapSetInfo .Include(s => s.Metadata) .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Files).ThenInclude(f => f.FileInfo); - public IEnumerable Beatmaps => Connection.BeatmapInfo + public IEnumerable Beatmaps => Context.BeatmapInfo .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) .Include(b => b.Metadata) .Include(b => b.Ruleset) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index adddad6122..0cfe2adb44 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -10,12 +10,12 @@ namespace osu.Game.Database public abstract class DatabaseBackedStore { protected readonly Storage Storage; - protected readonly OsuDbContext Connection; + protected readonly OsuDbContext Context; - protected DatabaseBackedStore(OsuDbContext connection, Storage storage = null) + protected DatabaseBackedStore(OsuDbContext context, Storage storage = null) { Storage = storage; - Connection = connection; + Context = context; try { diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index ede85ad42f..ed295c76b2 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -22,7 +22,7 @@ namespace osu.Game.IO public readonly ResourceStore Store; - public FileStore(OsuDbContext connection, Storage storage) : base(connection, storage) + public FileStore(OsuDbContext context, Storage storage) : base(context, storage) { Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); } @@ -34,7 +34,7 @@ namespace osu.Game.IO if (Storage.ExistsDirectory(prefix)) Storage.DeleteDirectory(prefix); - Connection.Database.ExecuteSqlCommand("DELETE FROM FileInfo"); + Context.Database.ExecuteSqlCommand("DELETE FROM FileInfo"); } } @@ -48,7 +48,7 @@ namespace osu.Game.IO { string hash = data.ComputeSHA2Hash(); - var existing = Connection.FileInfo.FirstOrDefault(f => f.Hash == hash); + var existing = Context.FileInfo.FirstOrDefault(f => f.Hash == hash); var info = existing ?? new FileInfo { Hash = hash }; @@ -75,34 +75,34 @@ namespace osu.Game.IO { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = Connection.Find(f.First().ID) ?? f.First(); + var refetch = Context.Find(f.First().ID) ?? f.First(); refetch.ReferenceCount += f.Count(); - Connection.FileInfo.Update(refetch); + Context.FileInfo.Update(refetch); } - Connection.SaveChanges(); + Context.SaveChanges(); } public void Dereference(params FileInfo[] files) { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = Connection.Find(f.First().ID); + var refetch = Context.Find(f.First().ID); refetch.ReferenceCount -= f.Count(); - Connection.Update(refetch); + Context.Update(refetch); } - Connection.SaveChanges(); + Context.SaveChanges(); } private void deletePending() { - foreach (var f in Connection.FileInfo.Where(f => f.ReferenceCount < 1)) + foreach (var f in Context.FileInfo.Where(f => f.ReferenceCount < 1)) { try { Storage.Delete(Path.Combine(prefix, f.StoragePath)); - Connection.FileInfo.Remove(f); + Context.FileInfo.Remove(f); } catch (Exception e) { @@ -110,7 +110,7 @@ namespace osu.Game.IO } } - Connection.SaveChanges(); + Context.SaveChanges(); } } } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 9edab896b3..2d0aabdd7f 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -14,8 +14,8 @@ namespace osu.Game.Input { public class KeyBindingStore : DatabaseBackedStore { - public KeyBindingStore(OsuDbContext connection, RulesetStore rulesets, Storage storage = null) - : base(connection, storage) + public KeyBindingStore(OsuDbContext context, RulesetStore rulesets, Storage storage = null) + : base(context, storage) { foreach (var info in rulesets.AvailableRulesets) { @@ -30,7 +30,7 @@ namespace osu.Game.Input protected override void Prepare(bool reset = false) { if (reset) - Connection.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); + Context.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) @@ -46,7 +46,7 @@ namespace osu.Game.Input foreach (var insertable in group.Skip(count).Take(aimCount - count)) // insert any defaults which are missing. - Connection.DatabasedKeyBinding.Add(new DatabasedKeyBinding + Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding { KeyCombination = insertable.KeyCombination, Action = insertable.Action, @@ -55,7 +55,7 @@ namespace osu.Game.Input }); } - Connection.SaveChanges(); + Context.SaveChanges(); } /// @@ -64,12 +64,12 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public IEnumerable Query(int? rulesetId = null, int? variant = null) => Connection.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); + public IEnumerable Query(int? rulesetId = null, int? variant = null) => Context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); public void Update(KeyBinding keyBinding) { - Connection.Update(keyBinding); - Connection.SaveChanges(); + Context.Update(keyBinding); + Context.SaveChanges(); } } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index c39312205c..82252b76fa 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets loadRulesetFromFile(file); } - public RulesetStore(OsuDbContext connection) - : base(connection) + public RulesetStore(OsuDbContext context) + : base(context) { } @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets /// /// All available rulesets. /// - public IEnumerable AvailableRulesets => Connection.RulesetInfo.Where(r => r.Available); + public IEnumerable AvailableRulesets => Context.RulesetInfo.Where(r => r.Available); private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets { if (reset) { - Connection.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); + Context.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())).ToList(); @@ -60,29 +60,29 @@ namespace osu.Game.Rulesets foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { var rulesetInfo = createRulesetInfo(r); - if (Connection.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) + if (Context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) { - Connection.RulesetInfo.Add(rulesetInfo); + Context.RulesetInfo.Add(rulesetInfo); } } - Connection.SaveChanges(); + Context.SaveChanges(); //add any other modes foreach (var r in instances.Where(r => r.LegacyID < 0)) { var us = createRulesetInfo(r); - var existing = Connection.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo); + var existing = Context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo); if (existing == null) - Connection.RulesetInfo.Add(us); + Context.RulesetInfo.Add(us); } - Connection.SaveChanges(); + Context.SaveChanges(); //perform a consistency check - foreach (var r in Connection.RulesetInfo) + foreach (var r in Context.RulesetInfo) { try { @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets } } - Connection.SaveChanges(); + Context.SaveChanges(); } private static void loadRulesetFromFile(string file) diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 66fcfb5d67..02dd5c40ac 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; - public ScoreStore(Storage storage, OsuDbContext connection, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(connection) + public ScoreStore(Storage storage, OsuDbContext context, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(context) { this.storage = storage; this.beatmaps = beatmaps; From fe44a28d48ead86d187ee2ee83aaf3b43ba1d4fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 12:46:38 +0900 Subject: [PATCH 292/344] Add back startup tasks runner --- osu.Game/Database/DatabaseBackedStore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 0cfe2adb44..cba334d58a 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -26,6 +26,8 @@ namespace osu.Game.Database Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database..."); Prepare(true); } + + StartupTasks(); } /// From cd41862e3bcfd579b40eea94c84cb00c8d1d62ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 15:00:27 +0900 Subject: [PATCH 293/344] Add back transaction support for beatmap importing --- osu.Game/Beatmaps/BeatmapManager.cs | 56 +++++++++++-------- osu.Game/Beatmaps/BeatmapStore.cs | 4 +- osu.Game/Database/DatabaseBackedStore.cs | 9 ++- osu.Game/Database/DatabaseContextFactory.cs | 19 +++++++ osu.Game/IO/FileStore.cs | 2 +- osu.Game/Input/KeyBindingStore.cs | 5 +- osu.Game/OsuGameBase.cs | 18 +++--- osu.Game/Rulesets/RulesetStore.cs | 4 +- osu.Game/Rulesets/Scoring/ScoreStore.cs | 3 +- .../Tests/Visual/TestCasePlaySongSelect.cs | 11 ++-- osu.Game/osu.Game.csproj | 1 + 11 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Database/DatabaseContextFactory.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7d174e6a28..1423e9138f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -57,6 +57,18 @@ namespace osu.Game.Beatmaps private readonly Storage storage; + private BeatmapStore createBeatmapStore(Func context) + { + var store = new BeatmapStore(context); + store.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); + store.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); + store.BeatmapHidden += b => BeatmapHidden?.Invoke(b); + store.BeatmapRestored += b => BeatmapRestored?.Invoke(b); + return store; + } + + private readonly Func createContext; + private readonly FileStore files; private readonly RulesetStore rulesets; @@ -80,16 +92,13 @@ namespace osu.Game.Beatmaps /// public Func GetStableStorage { private get; set; } - public BeatmapManager(Storage storage, FileStore files, OsuDbContext context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) + public BeatmapManager(Storage storage, Func context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) { - beatmaps = new BeatmapStore(context); - beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); - beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); - beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); - beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); + createContext = context; + beatmaps = createBeatmapStore(context); + files = new FileStore(context, storage); this.storage = storage; - this.files = files; this.rulesets = rulesets; this.api = api; @@ -160,13 +169,24 @@ namespace osu.Game.Beatmaps /// The beatmap to be imported. public BeatmapSetInfo Import(ArchiveReader archiveReader) { - BeatmapSetInfo set; - // let's only allow one concurrent import at a time for now. lock (importLock) - Import(set = importToStorage(archiveReader)); + { + var context = createContext(); - return set; + using (var transaction = context.Database.BeginTransaction()) + { + // create local stores so we can isolate and thread safely, and share a context/transaction. + var filesForImport = new FileStore(() => context, storage); + var beatmapsForImport = createBeatmapStore(() => context); + + BeatmapSetInfo set = importToStorage(filesForImport, archiveReader); + beatmapsForImport.Add(set); + context.SaveChanges(); + transaction.Commit(); + return set; + } + } } /// @@ -178,8 +198,7 @@ namespace osu.Game.Beatmaps // If we have an ID then we already exist in the database. if (beatmapSetInfo.ID != 0) return; - lock (beatmaps) - beatmaps.Add(beatmapSetInfo); + createBeatmapStore(createContext).Add(beatmapSetInfo); } /// @@ -322,15 +341,6 @@ namespace osu.Game.Beatmaps return working; } - /// - /// Reset the manager to an empty state. - /// - public void Reset() - { - lock (beatmaps) - beatmaps.Reset(); - } - /// /// Perform a lookup query on available s. /// @@ -400,7 +410,7 @@ namespace osu.Game.Beatmaps /// /// The beatmap archive to be read. /// The imported beatmap, or an existing instance if it is already present. - private BeatmapSetInfo importToStorage(ArchiveReader reader) + private BeatmapSetInfo importToStorage(FileStore files, ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 134059fd07..f3d3caeb0f 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -20,8 +20,8 @@ namespace osu.Game.Beatmaps public event Action BeatmapHidden; public event Action BeatmapRestored; - public BeatmapStore(OsuDbContext context) - : base(context) + public BeatmapStore(Func factory) + : base(factory) { } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index cba334d58a..9d3d020250 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -10,12 +10,15 @@ namespace osu.Game.Database public abstract class DatabaseBackedStore { protected readonly Storage Storage; - protected readonly OsuDbContext Context; - protected DatabaseBackedStore(OsuDbContext context, Storage storage = null) + private readonly Func contextSource; + + protected OsuDbContext Context => contextSource(); + + protected DatabaseBackedStore(Func contextSource, Storage storage = null) { Storage = storage; - Context = context; + this.contextSource = contextSource; try { diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs new file mode 100644 index 0000000000..3fc5141880 --- /dev/null +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + public class DatabaseContextFactory + { + private readonly GameHost host; + + public DatabaseContextFactory(GameHost host) + { + this.host = host; + } + + public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client")); + } +} diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index ed295c76b2..d715ccd0a7 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -22,7 +22,7 @@ namespace osu.Game.IO public readonly ResourceStore Store; - public FileStore(OsuDbContext context, Storage storage) : base(context, storage) + public FileStore(Func contextSource, Storage storage) : base(contextSource, storage) { Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 2d0aabdd7f..5c41179418 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; @@ -14,8 +15,8 @@ namespace osu.Game.Input { public class KeyBindingStore : DatabaseBackedStore { - public KeyBindingStore(OsuDbContext context, RulesetStore rulesets, Storage storage = null) - : base(context, storage) + public KeyBindingStore(Func contextSource, RulesetStore rulesets, Storage storage = null) + : base(contextSource, storage) { foreach (var info in rulesets.AvailableRulesets) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index d1e684499c..5ecc7279da 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -81,16 +81,18 @@ namespace osu.Game protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - private OsuDbContext createDbContext() => new OsuDbContext(Host.Storage.GetDatabaseConnectionString(@"client")); + private DatabaseContextFactory contextFactory; [BackgroundDependencyLoader] private void load() { + dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); + dependencies.Cache(this); dependencies.Cache(LocalConfig); - using (var dbContext = createDbContext()) - dbContext.Database.Migrate(); + using (var context = contextFactory.GetContext()) + context.Database.Migrate(); dependencies.Cache(API = new APIAccess { @@ -98,11 +100,11 @@ namespace osu.Game Token = LocalConfig.Get(OsuSetting.Token) }); - dependencies.Cache(RulesetStore = new RulesetStore(createDbContext())); - dependencies.Cache(FileStore = new FileStore(createDbContext(), Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, createDbContext(), RulesetStore, API, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, createDbContext(), Host, BeatmapManager, RulesetStore)); - dependencies.Cache(KeyBindingStore = new KeyBindingStore(createDbContext(), RulesetStore)); + dependencies.Cache(RulesetStore = new RulesetStore(contextFactory.GetContext)); + dependencies.Cache(FileStore = new FileStore(contextFactory.GetContext, Host.Storage)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host)); + dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore)); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore)); dependencies.Cache(new OsuColour()); //this completely overrides the framework default. will need to change once we make a proper FontStore. diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 82252b76fa..bd3c22fc42 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -26,8 +26,8 @@ namespace osu.Game.Rulesets loadRulesetFromFile(file); } - public RulesetStore(OsuDbContext context) - : base(context) + public RulesetStore(Func factory) + : base(factory) { } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 02dd5c40ac..67a8e5372e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.IO; using osu.Framework.Platform; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Scoring // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; - public ScoreStore(Storage storage, OsuDbContext context, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(context) + public ScoreStore(Storage storage, Func factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) { this.storage = storage; this.beatmaps = beatmaps; diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index a8a00e9a0d..9ca6aa2fb5 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -1,13 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Filter; @@ -25,8 +25,6 @@ namespace osu.Game.Tests.Visual private DependencyContainer dependencies; - private FileStore files; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); [BackgroundDependencyLoader] @@ -40,9 +38,10 @@ namespace osu.Game.Tests.Visual var dbConnectionString = storage.GetDatabaseConnectionString(@"client"); - dependencies.Cache(rulesets = new RulesetStore(new OsuDbContext(dbConnectionString))); - dependencies.Cache(files = new FileStore(new OsuDbContext(dbConnectionString), storage)); - dependencies.Cache(manager = new BeatmapManager(storage, files, new OsuDbContext(dbConnectionString), rulesets, null)); + Func contextFactory = () => new OsuDbContext(dbConnectionString); + + dependencies.Cache(rulesets = new RulesetStore(contextFactory)); + dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)); for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 731e065a48..d3ac2d6189 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -285,6 +285,7 @@ + 20171014052545_Init.cs From e487b6f82a18543592c4f209f5d428881a774d72 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 15:50:42 +0900 Subject: [PATCH 294/344] Standardise context retrieval --- osu.Game/Beatmaps/BeatmapStore.cs | 70 ++++++++++++++---------- osu.Game/Database/DatabaseBackedStore.cs | 8 +-- osu.Game/IO/FileStore.cs | 36 +++++++----- osu.Game/Input/KeyBindingStore.cs | 31 ++++++++--- osu.Game/Rulesets/RulesetStore.cs | 22 ++++---- 5 files changed, 101 insertions(+), 66 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index f3d3caeb0f..69aadb470e 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -29,12 +29,14 @@ namespace osu.Game.Beatmaps { if (reset) { + var context = GetContext(); + // https://stackoverflow.com/a/10450893 - Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata"); - Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty"); - Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo"); - Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo"); - Context.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo"); + context.Database.ExecuteSqlCommand("DELETE FROM BeatmapMetadata"); + context.Database.ExecuteSqlCommand("DELETE FROM BeatmapDifficulty"); + context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetInfo"); + context.Database.ExecuteSqlCommand("DELETE FROM BeatmapSetFileInfo"); + context.Database.ExecuteSqlCommand("DELETE FROM BeatmapInfo"); } } @@ -50,8 +52,10 @@ namespace osu.Game.Beatmaps /// The beatmap to add. public void Add(BeatmapSetInfo beatmapSet) { - Context.BeatmapSetInfo.Attach(beatmapSet); - Context.SaveChanges(); + var context = GetContext(); + + context.BeatmapSetInfo.Attach(beatmapSet); + context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); } @@ -63,11 +67,13 @@ namespace osu.Game.Beatmaps /// Whether the beatmap's was changed. public bool Delete(BeatmapSetInfo beatmapSet) { + var context = GetContext(); + if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - Context.BeatmapSetInfo.Update(beatmapSet); - Context.SaveChanges(); + context.BeatmapSetInfo.Update(beatmapSet); + context.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); return true; @@ -80,11 +86,13 @@ namespace osu.Game.Beatmaps /// Whether the beatmap's was changed. public bool Undelete(BeatmapSetInfo beatmapSet) { + var context = GetContext(); + if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; - Context.BeatmapSetInfo.Update(beatmapSet); - Context.SaveChanges(); + context.BeatmapSetInfo.Update(beatmapSet); + context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); return true; @@ -97,11 +105,13 @@ namespace osu.Game.Beatmaps /// Whether the beatmap's was changed. public bool Hide(BeatmapInfo beatmap) { + var context = GetContext(); + if (beatmap.Hidden) return false; beatmap.Hidden = true; - Context.BeatmapInfo.Update(beatmap); - Context.SaveChanges(); + context.BeatmapInfo.Update(beatmap); + context.SaveChanges(); BeatmapHidden?.Invoke(beatmap); return true; @@ -114,11 +124,13 @@ namespace osu.Game.Beatmaps /// Whether the beatmap's was changed. public bool Restore(BeatmapInfo beatmap) { + var context = GetContext(); + if (!beatmap.Hidden) return false; beatmap.Hidden = false; - Context.BeatmapInfo.Update(beatmap); - Context.SaveChanges(); + context.BeatmapInfo.Update(beatmap); + context.SaveChanges(); BeatmapRestored?.Invoke(beatmap); return true; @@ -126,21 +138,23 @@ namespace osu.Game.Beatmaps private void cleanupPendingDeletions() { - Context.BeatmapSetInfo.RemoveRange(Context.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); - Context.SaveChanges(); + var context = GetContext(); + + context.BeatmapSetInfo.RemoveRange(context.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); + context.SaveChanges(); } - public IEnumerable BeatmapSets => Context.BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); + public IEnumerable BeatmapSets => GetContext().BeatmapSetInfo + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); - public IEnumerable Beatmaps => Context.BeatmapInfo - .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.Difficulty); + public IEnumerable Beatmaps => GetContext().BeatmapInfo + .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.Difficulty); } } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 9d3d020250..79aea7863a 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -11,14 +11,12 @@ namespace osu.Game.Database { protected readonly Storage Storage; - private readonly Func contextSource; + protected readonly Func GetContext; - protected OsuDbContext Context => contextSource(); - - protected DatabaseBackedStore(Func contextSource, Storage storage = null) + protected DatabaseBackedStore(Func getContext, Storage storage = null) { Storage = storage; - this.contextSource = contextSource; + GetContext = getContext; try { diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index d715ccd0a7..b60d82d61c 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -22,7 +22,7 @@ namespace osu.Game.IO public readonly ResourceStore Store; - public FileStore(Func contextSource, Storage storage) : base(contextSource, storage) + public FileStore(Func getContext, Storage storage) : base(getContext, storage) { Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); } @@ -34,7 +34,7 @@ namespace osu.Game.IO if (Storage.ExistsDirectory(prefix)) Storage.DeleteDirectory(prefix); - Context.Database.ExecuteSqlCommand("DELETE FROM FileInfo"); + GetContext().Database.ExecuteSqlCommand("DELETE FROM FileInfo"); } } @@ -46,9 +46,11 @@ namespace osu.Game.IO public FileInfo Add(Stream data, bool reference = true) { + var context = GetContext(); + string hash = data.ComputeSHA2Hash(); - var existing = Context.FileInfo.FirstOrDefault(f => f.Hash == hash); + var existing = context.FileInfo.FirstOrDefault(f => f.Hash == hash); var info = existing ?? new FileInfo { Hash = hash }; @@ -71,38 +73,44 @@ namespace osu.Game.IO return info; } - public void Reference(params FileInfo[] files) + public void Reference(params FileInfo[] files) => reference(GetContext(), files); + + private void reference(OsuDbContext context, FileInfo[] files) { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = Context.Find(f.First().ID) ?? f.First(); + var refetch = context.Find(f.First().ID) ?? f.First(); refetch.ReferenceCount += f.Count(); - Context.FileInfo.Update(refetch); + context.FileInfo.Update(refetch); } - Context.SaveChanges(); + context.SaveChanges(); } - public void Dereference(params FileInfo[] files) + public void Dereference(params FileInfo[] files) => dereference(GetContext(), files); + + private void dereference(OsuDbContext context, FileInfo[] files) { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = Context.Find(f.First().ID); + var refetch = context.Find(f.First().ID); refetch.ReferenceCount -= f.Count(); - Context.Update(refetch); + context.Update(refetch); } - Context.SaveChanges(); + context.SaveChanges(); } private void deletePending() { - foreach (var f in Context.FileInfo.Where(f => f.ReferenceCount < 1)) + var context = GetContext(); + + foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1)) { try { Storage.Delete(Path.Combine(prefix, f.StoragePath)); - Context.FileInfo.Remove(f); + context.FileInfo.Remove(f); } catch (Exception e) { @@ -110,7 +118,7 @@ namespace osu.Game.IO } } - Context.SaveChanges(); + context.SaveChanges(); } } } diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 5c41179418..1e9a2aa22f 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -15,9 +15,16 @@ namespace osu.Game.Input { public class KeyBindingStore : DatabaseBackedStore { - public KeyBindingStore(Func contextSource, RulesetStore rulesets, Storage storage = null) - : base(contextSource, storage) + /// + /// As we do a lot of lookups, let's share a context between them to hopefully improve performance. + /// + private readonly OsuDbContext queryContext; + + public KeyBindingStore(Func getContext, RulesetStore rulesets, Storage storage = null) + : base(getContext, storage) { + queryContext = GetContext(); + foreach (var info in rulesets.AvailableRulesets) { var ruleset = info.CreateInstance(); @@ -31,15 +38,17 @@ namespace osu.Game.Input protected override void Prepare(bool reset = false) { if (reset) - Context.Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); + GetContext().Database.ExecuteSqlCommand("DELETE FROM KeyBinding"); } private void insertDefaults(IEnumerable defaults, int? rulesetId = null, int? variant = null) { + var context = GetContext(); + // compare counts in database vs defaults foreach (var group in defaults.GroupBy(k => k.Action)) { - int count = Query(rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); + int count = query(context, rulesetId, variant).Count(k => (int)k.Action == (int)group.Key); int aimCount = group.Count(); if (aimCount <= count) @@ -47,7 +56,7 @@ namespace osu.Game.Input foreach (var insertable in group.Skip(count).Take(aimCount - count)) // insert any defaults which are missing. - Context.DatabasedKeyBinding.Add(new DatabasedKeyBinding + context.DatabasedKeyBinding.Add(new DatabasedKeyBinding { KeyCombination = insertable.KeyCombination, Action = insertable.Action, @@ -56,7 +65,7 @@ namespace osu.Game.Input }); } - Context.SaveChanges(); + context.SaveChanges(); } /// @@ -65,12 +74,16 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public IEnumerable Query(int? rulesetId = null, int? variant = null) => Context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); + public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(queryContext, rulesetId, variant); + + private IEnumerable query(OsuDbContext context, int? rulesetId = null, int? variant = null) => + context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); public void Update(KeyBinding keyBinding) { - Context.Update(keyBinding); - Context.SaveChanges(); + var context = GetContext(); + context.Update(keyBinding); + context.SaveChanges(); } } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index bd3c22fc42..7d982eb39e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets /// /// All available rulesets. /// - public IEnumerable AvailableRulesets => Context.RulesetInfo.Where(r => r.Available); + public IEnumerable AvailableRulesets => GetContext().RulesetInfo.Where(r => r.Available); private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name); @@ -49,9 +49,11 @@ namespace osu.Game.Rulesets protected override void Prepare(bool reset = false) { + var context = GetContext(); + if (reset) { - Context.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); + context.Database.ExecuteSqlCommand("DELETE FROM RulesetInfo"); } var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, new RulesetInfo())).ToList(); @@ -60,29 +62,29 @@ namespace osu.Game.Rulesets foreach (var r in instances.Where(r => r.LegacyID >= 0).OrderBy(r => r.LegacyID)) { var rulesetInfo = createRulesetInfo(r); - if (Context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) + if (context.RulesetInfo.SingleOrDefault(rsi => rsi.ID == rulesetInfo.ID) == null) { - Context.RulesetInfo.Add(rulesetInfo); + context.RulesetInfo.Add(rulesetInfo); } } - Context.SaveChanges(); + context.SaveChanges(); //add any other modes foreach (var r in instances.Where(r => r.LegacyID < 0)) { var us = createRulesetInfo(r); - var existing = Context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo); + var existing = context.RulesetInfo.FirstOrDefault(ri => ri.InstantiationInfo == us.InstantiationInfo); if (existing == null) - Context.RulesetInfo.Add(us); + context.RulesetInfo.Add(us); } - Context.SaveChanges(); + context.SaveChanges(); //perform a consistency check - foreach (var r in Context.RulesetInfo) + foreach (var r in context.RulesetInfo) { try { @@ -95,7 +97,7 @@ namespace osu.Game.Rulesets } } - Context.SaveChanges(); + context.SaveChanges(); } private static void loadRulesetFromFile(string file) From cf3881b18c4848e16188d5b96dd7b65e87ce996b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 15:50:52 +0900 Subject: [PATCH 295/344] Fix not being able to restore hidden beatmaps via context menu --- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index d1682a392d..6e5af29799 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -83,8 +83,7 @@ namespace osu.Game.Beatmaps.Drawables RelativeSizeAxes = Axes.X, }; - BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList(); - BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b) + BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b) { Alpha = 0, GainedSelection = panelGainedSelection, From ad54ca92688be0e4f7ebf8ccc4f548b6d360d2be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 16:02:13 +0900 Subject: [PATCH 296/344] Fix TestCasePlaySongSelect --- osu.Game/Database/DatabaseContextFactory.cs | 2 +- osu.Game/Tests/Visual/TestCasePlaySongSelect.cs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 3fc5141880..359188b4e2 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,5 +1,5 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Platform; diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index 9ca6aa2fb5..a46542760b 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -36,9 +37,11 @@ namespace osu.Game.Tests.Visual { var storage = new TestStorage(@"TestCasePlaySongSelect"); - var dbConnectionString = storage.GetDatabaseConnectionString(@"client"); + // this is by no means clean. should be replacing inside of OsuGameBase somehow. + var context = new OsuDbContext(storage.GetDatabaseConnectionString(@"client")); + context.Database.Migrate(); - Func contextFactory = () => new OsuDbContext(dbConnectionString); + Func contextFactory = () => context; dependencies.Cache(rulesets = new RulesetStore(contextFactory)); dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)); From e02640637a1c52df860876f1f9ab94998d412c43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:08:01 +0900 Subject: [PATCH 297/344] Fix KeyBindingStore regression --- osu.Game/Input/KeyBindingStore.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 1e9a2aa22f..54cf48bc2a 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -15,16 +15,9 @@ namespace osu.Game.Input { public class KeyBindingStore : DatabaseBackedStore { - /// - /// As we do a lot of lookups, let's share a context between them to hopefully improve performance. - /// - private readonly OsuDbContext queryContext; - public KeyBindingStore(Func getContext, RulesetStore rulesets, Storage storage = null) : base(getContext, storage) { - queryContext = GetContext(); - foreach (var info in rulesets.AvailableRulesets) { var ruleset = info.CreateInstance(); @@ -74,7 +67,7 @@ namespace osu.Game.Input /// The ruleset's internal ID. /// An optional variant. /// - public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(queryContext, rulesetId, variant); + public IEnumerable Query(int? rulesetId = null, int? variant = null) => query(GetContext(), rulesetId, variant); private IEnumerable query(OsuDbContext context, int? rulesetId = null, int? variant = null) => context.DatabasedKeyBinding.Where(b => b.RulesetID == rulesetId && b.Variant == variant); From 64dfce258ff1b561120bc0e6c23f5e2872e93db3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:08:19 +0900 Subject: [PATCH 298/344] Fix file prefix not being read when calling storage.Exists --- osu.Game/IO/FileStore.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index b60d82d61c..db4f5fd2a2 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -18,21 +18,21 @@ namespace osu.Game.IO /// public class FileStore : DatabaseBackedStore { - private const string prefix = "files"; + public readonly IResourceStore Store; - public readonly ResourceStore Store; + public Storage Storage => base.Storage; - public FileStore(Func getContext, Storage storage) : base(getContext, storage) + public FileStore(Func getContext, Storage storage) : base(getContext, storage.GetStorageForDirectory(@"files")) { - Store = new NamespacedResourceStore(new StorageBackedResourceStore(storage), prefix); + Store = new StorageBackedResourceStore(Storage); } protected override void Prepare(bool reset = false) { if (reset) { - if (Storage.ExistsDirectory(prefix)) - Storage.DeleteDirectory(prefix); + if (Storage.ExistsDirectory(string.Empty)) + Storage.DeleteDirectory(string.Empty); GetContext().Database.ExecuteSqlCommand("DELETE FROM FileInfo"); } @@ -54,7 +54,7 @@ namespace osu.Game.IO var info = existing ?? new FileInfo { Hash = hash }; - string path = Path.Combine(prefix, info.StoragePath); + string path = info.StoragePath; // we may be re-adding a file to fix missing store entries. if (!Storage.Exists(path)) @@ -109,7 +109,7 @@ namespace osu.Game.IO { try { - Storage.Delete(Path.Combine(prefix, f.StoragePath)); + Storage.Delete(f.StoragePath); context.FileInfo.Remove(f); } catch (Exception e) From 7a18d373ec9a4b96151280c5d475ce7d1a64e474 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:08:42 +0900 Subject: [PATCH 299/344] Improve performance of beatmap imports (still needs revision) --- osu.Game/Beatmaps/BeatmapManager.cs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1423e9138f..bf71033f36 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -98,7 +98,7 @@ namespace osu.Game.Beatmaps beatmaps = createBeatmapStore(context); files = new FileStore(context, storage); - this.storage = storage; + this.storage = files.Storage; this.rulesets = rulesets; this.api = api; @@ -174,16 +174,23 @@ namespace osu.Game.Beatmaps { var context = createContext(); + context.Database.AutoTransactionsEnabled = false; + using (var transaction = context.Database.BeginTransaction()) { // create local stores so we can isolate and thread safely, and share a context/transaction. - var filesForImport = new FileStore(() => context, storage); - var beatmapsForImport = createBeatmapStore(() => context); + var iFiles = new FileStore(() => context, storage); + var iBeatmaps = createBeatmapStore(() => context); + + BeatmapSetInfo set = importToStorage(iFiles, iBeatmaps, archiveReader); + + if (set.ID == 0) + { + iBeatmaps.Add(set); + context.SaveChanges(); + transaction.Commit(); + } - BeatmapSetInfo set = importToStorage(filesForImport, archiveReader); - beatmapsForImport.Add(set); - context.SaveChanges(); - transaction.Commit(); return set; } } @@ -308,7 +315,7 @@ namespace osu.Game.Beatmaps /// Is a no-op for already usable beatmaps. /// /// The beatmap to restore. - public void Undelete(BeatmapSetInfo beatmapSet) + private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet) { lock (beatmaps) if (!beatmaps.Undelete(beatmapSet)) return; @@ -410,7 +417,7 @@ namespace osu.Game.Beatmaps /// /// The beatmap archive to be read. /// The imported beatmap, or an existing instance if it is already present. - private BeatmapSetInfo importToStorage(FileStore files, ArchiveReader reader) + private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); @@ -432,7 +439,7 @@ namespace osu.Game.Beatmaps if (beatmapSet != null) { - Undelete(beatmapSet); + undelete(beatmaps, files, beatmapSet); // ensure all files are present and accessible foreach (var f in beatmapSet.Files) @@ -442,6 +449,8 @@ namespace osu.Game.Beatmaps files.Add(s, false); } + // todo: delete any files which shouldn't exist any more. + return beatmapSet; } From 0177fcbe5f9ec6c849a084ca3559311437bc4d27 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:08:47 +0900 Subject: [PATCH 300/344] Fix xmldoc --- osu.Game/Database/DatabaseBackedStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 79aea7863a..90a43a47c5 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -32,7 +32,7 @@ namespace osu.Game.Database } /// - /// Perform any common startup tasks. Runs after and . + /// Perform any common startup tasks. Runs after . /// protected virtual void StartupTasks() { From 3e415e326908ba50ecb42c78dc1c70d7fc4433f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:52:02 +0900 Subject: [PATCH 301/344] Fix tooling failures --- osu.Game/Database/OsuDbContext.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 5f4e42ed6b..bd288621e0 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -28,11 +28,19 @@ namespace osu.Game.Database SQLitePCL.Batteries_V2.Init(); } + /// + /// Create a new in-memory OsuDbContext instance. + /// + public OsuDbContext() : this("DataSource=:memory:") + { + // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0). + } + /// /// Create a new OsuDbContext instance. /// - /// A valid SQLite connection string. If not provided, an in-memory instance will be created. - public OsuDbContext(string connectionString = "DataSource=:memory:") + /// A valid SQLite connection string. + public OsuDbContext(string connectionString) { this.connectionString = connectionString; @@ -82,7 +90,7 @@ namespace osu.Game.Database public void AddProvider(ILoggerProvider provider) { - throw new NotImplementedException(); + // no-op. called by tooling. } private class OsuDbLoggerProvider : ILoggerProvider From 12639c68191d65e8e703a0d313caf640aa6325b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 17:52:20 +0900 Subject: [PATCH 302/344] Use a different database name for now to avoid conflicts when switching versions --- osu.Game/Database/DatabaseContextFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 359188b4e2..e22301adfe 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -14,6 +14,6 @@ namespace osu.Game.Database this.host = host; } - public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client")); + public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client-ef")); } } From b9d0fb96ed0fd636a18fd385887f334b7cb10a3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 18:26:28 +0900 Subject: [PATCH 303/344] Fix cascade deletions --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 3 + osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Beatmaps/BeatmapMetadata.cs | 8 + osu.Game/Database/OsuDbContext.cs | 3 + ... 20171017092037_InitialCreate.Designer.cs} | 76 ++--- ...nit.cs => 20171017092037_InitialCreate.cs} | 280 +++++++++--------- .../Migrations/OsuDbContextModelSnapshot.cs | 76 ++--- osu.Game/osu.Game.csproj | 6 +- 8 files changed, 243 insertions(+), 210 deletions(-) rename osu.Game/Migrations/{20171014052545_Init.designer.cs => 20171017092037_InitialCreate.Designer.cs} (80%) rename osu.Game/Migrations/{20171014052545_Init.cs => 20171017092037_InitialCreate.cs} (86%) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 25e212f3c5..e310c4a646 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -14,6 +14,9 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } + + public int BeatmapInfoID { get; set; } + public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index c1516f17c9..d2ed5d692c 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -31,6 +31,7 @@ namespace osu.Game.Beatmaps [Required] public BeatmapSetInfo BeatmapSet { get; set; } + public BeatmapMetadata Metadata { get; set; } public int BaseDifficultyID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 5e47d6c13d..ca8702d222 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -16,6 +16,14 @@ namespace osu.Game.Beatmaps [NotMapped] public int? OnlineBeatmapSetID { get; set; } + public int? BeatmapSetInfoID { get; set; } + + public BeatmapSetInfo BeatmapSetInfo { get; set; } + + public int? BeatmapInfoID { get; set; } + + public BeatmapInfo BeatmapInfo { get; set; } + public string Title { get; set; } public string TitleUnicode { get; set; } public string Artist { get; set; } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index bd288621e0..e360f11ba3 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -74,6 +74,9 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.Name).IsUnique(); modelBuilder.Entity().HasIndex(b => b.InstantiationInfo).IsUnique(); modelBuilder.Entity().HasIndex(b => b.Available); + + modelBuilder.Entity().HasOne(m => m.BeatmapSetInfo).WithOne(s => s.Metadata).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(m => m.BeatmapInfo).WithOne(b => b.Metadata).OnDelete(DeleteBehavior.Cascade); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Migrations/20171014052545_Init.designer.cs b/osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs similarity index 80% rename from osu.Game/Migrations/20171014052545_Init.designer.cs rename to osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs index f151131882..422c1612d2 100644 --- a/osu.Game/Migrations/20171014052545_Init.designer.cs +++ b/osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs @@ -1,7 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -// +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -13,8 +10,8 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171014052545_Init")] - partial class Init + [Migration("20171017092037_InitialCreate")] + partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -29,6 +26,8 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); + b.Property("BeatmapInfoID"); + b.Property("CircleSize"); b.Property("DrainRate"); @@ -41,6 +40,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoID") + .IsUnique(); + b.ToTable("BeatmapDifficulty"); }); @@ -59,8 +61,6 @@ namespace osu.Game.Migrations b.Property("Countdown"); - b.Property("DifficultyID"); - b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -73,8 +73,6 @@ namespace osu.Game.Migrations b.Property("MD5Hash"); - b.Property("MetadataID"); - b.Property("Path"); b.Property("RulesetID"); @@ -97,12 +95,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); - b.HasIndex("DifficultyID"); - b.HasIndex("MD5Hash"); - b.HasIndex("MetadataID"); - b.HasIndex("RulesetID"); b.ToTable("BeatmapInfo"); @@ -119,10 +113,15 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("Author"); + b.Property("AuthorString") + .HasColumnName("Author"); b.Property("BackgroundFile"); + b.Property("BeatmapInfoID"); + + b.Property("BeatmapSetInfoID"); + b.Property("PreviewTime"); b.Property("Source"); @@ -135,6 +134,12 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoID") + .IsUnique(); + + b.HasIndex("BeatmapSetInfoID") + .IsUnique(); + b.ToTable("BeatmapMetadata"); }); @@ -168,16 +173,12 @@ namespace osu.Game.Migrations b.Property("Hash"); - b.Property("MetadataID"); - b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); - b.HasIndex("MetadataID"); - b.ToTable("BeatmapSetInfo"); }); @@ -248,6 +249,14 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo") + .WithOne("Difficulty") + .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -255,21 +264,25 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") - .WithMany() - .HasForeignKey("DifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") .WithMany() .HasForeignKey("RulesetID") .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "BeatmapInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSetInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -282,13 +295,6 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/Migrations/20171014052545_Init.cs b/osu.Game/Migrations/20171017092037_InitialCreate.cs similarity index 86% rename from osu.Game/Migrations/20171014052545_Init.cs rename to osu.Game/Migrations/20171017092037_InitialCreate.cs index 6792f79e3d..2626e2ea74 100644 --- a/osu.Game/Migrations/20171014052545_Init.cs +++ b/osu.Game/Migrations/20171017092037_InitialCreate.cs @@ -1,52 +1,26 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; namespace osu.Game.Migrations { - public partial class Init : Migration + public partial class InitialCreate : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "BeatmapDifficulty", + name: "BeatmapSetInfo", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); }); migrationBuilder.CreateTable( @@ -94,86 +68,6 @@ namespace osu.Game.Migrations table.PrimaryKey("PK_RulesetInfo", x => x.ID); }); - migrationBuilder.CreateTable( - name: "BeatmapSetInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - }); - - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DifficultyID = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - MetadataID = table.Column(type: "INTEGER", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapDifficulty_DifficultyID", - column: x => x.DifficultyID, - principalTable: "BeatmapDifficulty", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", - column: x => x.MetadataID, - principalTable: "BeatmapMetadata", - principalColumn: "ID", - onDelete: ReferentialAction.Restrict); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateTable( name: "BeatmapSetFileInfo", columns: table => new @@ -201,31 +95,144 @@ namespace osu.Game.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapDifficulty", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApproachRate = table.Column(type: "REAL", nullable: false), + BeatmapInfoID = table.Column(type: "INTEGER", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapDifficulty_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + BeatmapInfoID = table.Column(type: "INTEGER", nullable: true), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapMetadata_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapMetadata_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapDifficulty_BeatmapInfoID", + table: "BeatmapDifficulty", + column: "BeatmapInfoID", + unique: true); + migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_BeatmapSetInfoID", table: "BeatmapInfo", column: "BeatmapSetInfoID"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_DifficultyID", - table: "BeatmapInfo", - column: "DifficultyID"); - migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_MD5Hash", table: "BeatmapInfo", column: "MD5Hash"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapInfo_MetadataID", - table: "BeatmapInfo", - column: "MetadataID"); - migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_RulesetID", table: "BeatmapInfo", column: "RulesetID"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapMetadata_BeatmapInfoID", + table: "BeatmapMetadata", + column: "BeatmapInfoID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapMetadata_BeatmapSetInfoID", + table: "BeatmapMetadata", + column: "BeatmapSetInfoID", + unique: true); + migrationBuilder.CreateIndex( name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", table: "BeatmapSetFileInfo", @@ -241,11 +248,6 @@ namespace osu.Game.Migrations table: "BeatmapSetInfo", column: "DeletePending"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapSetInfo_MetadataID", - table: "BeatmapSetInfo", - column: "MetadataID"); - migrationBuilder.CreateIndex( name: "IX_FileInfo_Hash", table: "FileInfo", @@ -288,7 +290,10 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "BeatmapInfo"); + name: "BeatmapDifficulty"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); migrationBuilder.DropTable( name: "BeatmapSetFileInfo"); @@ -297,19 +302,16 @@ namespace osu.Game.Migrations name: "KeyBinding"); migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "RulesetInfo"); - - migrationBuilder.DropTable( - name: "BeatmapSetInfo"); + name: "BeatmapInfo"); migrationBuilder.DropTable( name: "FileInfo"); migrationBuilder.DropTable( - name: "BeatmapMetadata"); + name: "BeatmapSetInfo"); + + migrationBuilder.DropTable( + name: "RulesetInfo"); } } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index f3a6c5a520..69cc206b7e 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -// +// using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; using osu.Game.Database; +using System; namespace osu.Game.Migrations { @@ -24,6 +25,8 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); + b.Property("BeatmapInfoID"); + b.Property("CircleSize"); b.Property("DrainRate"); @@ -36,6 +39,9 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoID") + .IsUnique(); + b.ToTable("BeatmapDifficulty"); }); @@ -54,8 +60,6 @@ namespace osu.Game.Migrations b.Property("Countdown"); - b.Property("DifficultyID"); - b.Property("DistanceSpacing"); b.Property("GridSize"); @@ -68,8 +72,6 @@ namespace osu.Game.Migrations b.Property("MD5Hash"); - b.Property("MetadataID"); - b.Property("Path"); b.Property("RulesetID"); @@ -92,12 +94,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); - b.HasIndex("DifficultyID"); - b.HasIndex("MD5Hash"); - b.HasIndex("MetadataID"); - b.HasIndex("RulesetID"); b.ToTable("BeatmapInfo"); @@ -114,10 +112,15 @@ namespace osu.Game.Migrations b.Property("AudioFile"); - b.Property("Author"); + b.Property("AuthorString") + .HasColumnName("Author"); b.Property("BackgroundFile"); + b.Property("BeatmapInfoID"); + + b.Property("BeatmapSetInfoID"); + b.Property("PreviewTime"); b.Property("Source"); @@ -130,6 +133,12 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BeatmapInfoID") + .IsUnique(); + + b.HasIndex("BeatmapSetInfoID") + .IsUnique(); + b.ToTable("BeatmapMetadata"); }); @@ -163,16 +172,12 @@ namespace osu.Game.Migrations b.Property("Hash"); - b.Property("MetadataID"); - b.Property("Protected"); b.HasKey("ID"); b.HasIndex("DeletePending"); - b.HasIndex("MetadataID"); - b.ToTable("BeatmapSetInfo"); }); @@ -243,6 +248,14 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo") + .WithOne("Difficulty") + .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") @@ -250,21 +263,25 @@ namespace osu.Game.Migrations .HasForeignKey("BeatmapSetInfoID") .OnDelete(DeleteBehavior.Cascade); - b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "Difficulty") - .WithMany() - .HasForeignKey("DifficultyID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") .WithMany() .HasForeignKey("RulesetID") .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "BeatmapInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSetInfo") + .WithOne("Metadata") + .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -277,13 +294,6 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); - - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") - .WithMany() - .HasForeignKey("MetadataID"); - }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d3ac2d6189..9c6e03249e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -286,9 +286,9 @@ - - - 20171014052545_Init.cs + + + 20171017092037_InitialCreate.cs From e4a066dc5fcb76926a34e75c2c333961d187becc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 19:58:33 +0900 Subject: [PATCH 304/344] Run cleanup tasks only on startup via manual calls --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ osu.Game/Beatmaps/BeatmapStore.cs | 8 +------- osu.Game/Database/DatabaseBackedStore.cs | 6 ++---- osu.Game/IO/FileStore.cs | 8 +------- osu.Game/OsuGameBase.cs | 2 ++ 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bf71033f36..a25f454218 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -104,6 +104,8 @@ namespace osu.Game.Beatmaps if (importHost != null) ipc = new BeatmapIPCChannel(importHost, this); + + beatmaps.Cleanup(); } /// diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 69aadb470e..4892d4f3db 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -40,12 +40,6 @@ namespace osu.Game.Beatmaps } } - protected override void StartupTasks() - { - base.StartupTasks(); - cleanupPendingDeletions(); - } - /// /// Add a to the database. /// @@ -136,7 +130,7 @@ namespace osu.Game.Beatmaps return true; } - private void cleanupPendingDeletions() + public override void Cleanup() { var context = GetContext(); diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 90a43a47c5..be86d35335 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -27,14 +27,12 @@ namespace osu.Game.Database Logger.Error(e, $@"Failed to initialise the {GetType()}! Trying again with a clean database..."); Prepare(true); } - - StartupTasks(); } /// - /// Perform any common startup tasks. Runs after . + /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. /// - protected virtual void StartupTasks() + public virtual void Cleanup() { } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index db4f5fd2a2..5f1b21ddb6 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -38,12 +38,6 @@ namespace osu.Game.IO } } - protected override void StartupTasks() - { - base.StartupTasks(); - deletePending(); - } - public FileInfo Add(Stream data, bool reference = true) { var context = GetContext(); @@ -101,7 +95,7 @@ namespace osu.Game.IO context.SaveChanges(); } - private void deletePending() + public override void Cleanup() { var context = GetContext(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5ecc7279da..22eb75fcea 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -160,6 +160,8 @@ namespace osu.Game }; API.Register(this); + + FileStore.Cleanup(); } private WorkingBeatmap lastBeatmap; From 4e8019b31373860c4dab4921f75b19d986f60a5d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 19:59:22 +0900 Subject: [PATCH 305/344] Add some more missing indices --- osu.Game/Database/OsuDbContext.cs | 2 ++ ...ner.cs => 20171017103309_InitialCreate.Designer.cs} | 6 +++++- ...nitialCreate.cs => 20171017103309_InitialCreate.cs} | 10 ++++++++++ osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 4 ++++ osu.Game/osu.Game.csproj | 6 +++--- 5 files changed, 24 insertions(+), 4 deletions(-) rename osu.Game/Migrations/{20171017092037_InitialCreate.Designer.cs => 20171017103309_InitialCreate.Designer.cs} (95%) rename osu.Game/Migrations/{20171017092037_InitialCreate.cs => 20171017103309_InitialCreate.cs} (95%) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e360f11ba3..cfc5f056ad 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -66,7 +66,9 @@ namespace osu.Game.Database { base.OnModelCreating(modelBuilder); modelBuilder.Entity().HasIndex(b => b.MD5Hash); + modelBuilder.Entity().HasIndex(b => b.Hash); modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => b.Hash); modelBuilder.Entity().HasIndex(b => b.Variant); modelBuilder.Entity().HasIndex(b => b.IntAction); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); diff --git a/osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs b/osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs similarity index 95% rename from osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs rename to osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs index 422c1612d2..4d394fcd90 100644 --- a/osu.Game/Migrations/20171017092037_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs @@ -10,7 +10,7 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171017092037_InitialCreate")] + [Migration("20171017103309_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -95,6 +95,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); + b.HasIndex("Hash"); + b.HasIndex("MD5Hash"); b.HasIndex("RulesetID"); @@ -179,6 +181,8 @@ namespace osu.Game.Migrations b.HasIndex("DeletePending"); + b.HasIndex("Hash"); + b.ToTable("BeatmapSetInfo"); }); diff --git a/osu.Game/Migrations/20171017092037_InitialCreate.cs b/osu.Game/Migrations/20171017103309_InitialCreate.cs similarity index 95% rename from osu.Game/Migrations/20171017092037_InitialCreate.cs rename to osu.Game/Migrations/20171017103309_InitialCreate.cs index 2626e2ea74..759e8bb664 100644 --- a/osu.Game/Migrations/20171017092037_InitialCreate.cs +++ b/osu.Game/Migrations/20171017103309_InitialCreate.cs @@ -211,6 +211,11 @@ namespace osu.Game.Migrations table: "BeatmapInfo", column: "BeatmapSetInfoID"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_MD5Hash", table: "BeatmapInfo", @@ -248,6 +253,11 @@ namespace osu.Game.Migrations table: "BeatmapSetInfo", column: "DeletePending"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_Hash", + table: "BeatmapSetInfo", + column: "Hash"); + migrationBuilder.CreateIndex( name: "IX_FileInfo_Hash", table: "FileInfo", diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 69cc206b7e..ef2c801a6c 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -94,6 +94,8 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); + b.HasIndex("Hash"); + b.HasIndex("MD5Hash"); b.HasIndex("RulesetID"); @@ -178,6 +180,8 @@ namespace osu.Game.Migrations b.HasIndex("DeletePending"); + b.HasIndex("Hash"); + b.ToTable("BeatmapSetInfo"); }); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9c6e03249e..9941792ec5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -286,9 +286,9 @@ - - - 20171017092037_InitialCreate.cs + + + 20171017103309_InitialCreate.cs From 4193004fbfe35805b0306b168055d539d05c86cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Oct 2017 20:29:47 +0900 Subject: [PATCH 306/344] Improve performance of imports by keeping a context hot --- osu.Game/Beatmaps/BeatmapManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a25f454218..e7929ff882 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -165,6 +165,8 @@ namespace osu.Game.Beatmaps private readonly object importLock = new object(); + private OsuDbContext importContext; + /// /// Import a beatmap from an . /// @@ -174,7 +176,7 @@ namespace osu.Game.Beatmaps // let's only allow one concurrent import at a time for now. lock (importLock) { - var context = createContext(); + var context = importContext ?? (importContext = createContext()); context.Database.AutoTransactionsEnabled = false; @@ -190,9 +192,9 @@ namespace osu.Game.Beatmaps { iBeatmaps.Add(set); context.SaveChanges(); - transaction.Commit(); } + transaction.Commit(); return set; } } From 518e5a2245b60e0429f86229382015fafb5a58d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 10:07:20 +0900 Subject: [PATCH 307/344] Make ProgressNotification's status and progress thread-safe Quite regularly a task will hold a reference to a progress notification and udpate it as progress is made. Therefore these operations should be thread-safe. --- .../Notifications/ProgressNotification.cs | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index a31291e1b8..58aff16de0 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -25,10 +25,7 @@ namespace osu.Game.Overlays.Notifications public float Progress { get { return progressBar.Progress; } - set - { - progressBar.Progress = value; - } + set { Schedule(() => progressBar.Progress = value); } } protected override void LoadComplete() @@ -44,41 +41,44 @@ namespace osu.Game.Overlays.Notifications get { return state; } set { - bool stateChanged = state != value; - state = value; - - if (IsLoaded) + Schedule(() => { - switch (state) - { - case ProgressNotificationState.Queued: - Light.Colour = colourQueued; - Light.Pulsate = false; - progressBar.Active = false; - break; - case ProgressNotificationState.Active: - Light.Colour = colourActive; - Light.Pulsate = true; - progressBar.Active = true; - break; - case ProgressNotificationState.Cancelled: - Light.Colour = colourCancelled; - Light.Pulsate = false; - progressBar.Active = false; - break; - } - } + bool stateChanged = state != value; + state = value; - if (stateChanged) - { - switch (state) + if (IsLoaded) { - case ProgressNotificationState.Completed: - NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(d => Completed()); - break; + switch (state) + { + case ProgressNotificationState.Queued: + Light.Colour = colourQueued; + Light.Pulsate = false; + progressBar.Active = false; + break; + case ProgressNotificationState.Active: + Light.Colour = colourActive; + Light.Pulsate = true; + progressBar.Active = true; + break; + case ProgressNotificationState.Cancelled: + Light.Colour = colourCancelled; + Light.Pulsate = false; + progressBar.Active = false; + break; + } } - } + + if (stateChanged) + { + switch (state) + { + case ProgressNotificationState.Completed: + NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); + this.FadeOut(200).Finally(d => Completed()); + break; + } + } + }); } } @@ -232,4 +232,4 @@ namespace osu.Game.Overlays.Notifications Completed, Cancelled } -} \ No newline at end of file +} From bae91d7de703b98441f758e64b6b6c58e0d5c45b Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 18 Oct 2017 05:06:17 +0300 Subject: [PATCH 308/344] Simplify gradient usage in Letterbox Overlay --- .../Play/BreaksOverlay/LetterboxOverlay.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs index 9d5bc986e9..4733a5482b 100644 --- a/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/BreaksOverlay/LetterboxOverlay.cs @@ -31,13 +31,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = new ColourInfo - { - TopLeft = Color4.Black, - TopRight = Color4.Black, - BottomLeft = transparent_black, - BottomRight = transparent_black, - } + Colour = ColourInfo.GradientVertical(Color4.Black, transparent_black), } }, new Container @@ -49,13 +43,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = new ColourInfo - { - TopLeft = transparent_black, - TopRight = transparent_black, - BottomLeft = Color4.Black, - BottomRight = Color4.Black, - } + Colour = ColourInfo.GradientVertical(transparent_black, Color4.Black), } } }; From 9e3d54e80bd21b69225ed9ea7f62eef72961a6f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 11:42:55 +0900 Subject: [PATCH 309/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 2a53abda4b..dbcfa5c244 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 2a53abda4b2ac5b559f95d934572fb99eba42a10 +Subproject commit dbcfa5c244555e7901dac7d94eab53b3b04d17e6 From cf5290fead50c908485f3572b69fd0cdda40aaa5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 13:48:15 +0900 Subject: [PATCH 310/344] Remove unnecessary locking; operations are now thread-safe --- osu.Game/Beatmaps/BeatmapManager.cs | 49 ++++++----------------------- 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e7929ff882..5f182c1dca 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -287,8 +287,7 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - lock (beatmaps) - if (!beatmaps.Delete(beatmapSet)) return; + if (!beatmaps.Delete(beatmapSet)) return; if (!beatmapSet.Protected) files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -298,21 +297,13 @@ namespace osu.Game.Beatmaps /// Delete a beatmap difficulty. /// /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) - { - lock (beatmaps) - beatmaps.Hide(beatmap); - } + public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); /// /// Restore a beatmap difficulty. /// /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) - { - lock (beatmaps) - beatmaps.Restore(beatmap); - } + public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); /// /// Returns a to a usable state if it has previously been deleted but not yet purged. @@ -321,8 +312,7 @@ namespace osu.Game.Beatmaps /// The beatmap to restore. private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet) { - lock (beatmaps) - if (!beatmaps.Undelete(beatmapSet)) return; + if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); @@ -357,11 +347,7 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo QueryBeatmapSet(Func query) - { - lock (beatmaps) - return beatmaps.BeatmapSets.FirstOrDefault(query); - } + public BeatmapSetInfo QueryBeatmapSet(Func query) => beatmaps.BeatmapSets.FirstOrDefault(query); /// /// Refresh an existing instance of a from the store. @@ -375,33 +361,21 @@ namespace osu.Game.Beatmaps /// /// The query. /// Results from the provided query. - public List QueryBeatmapSets(Func query) - { - lock (beatmaps) - return beatmaps.BeatmapSets.Where(query).ToList(); - } + public List QueryBeatmapSets(Func query) => beatmaps.BeatmapSets.Where(query).ToList(); /// /// Perform a lookup query on available s. /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapInfo QueryBeatmap(Func query) - { - lock (beatmaps) - return beatmaps.Beatmaps.FirstOrDefault(query); - } + public BeatmapInfo QueryBeatmap(Func query) => beatmaps.Beatmaps.FirstOrDefault(query); /// /// Perform a lookup query on available s. /// /// The query. /// Results from the provided query. - public List QueryBeatmaps(Func query) - { - lock (beatmaps) - return beatmaps.Beatmaps.Where(query).ToList(); - } + public List QueryBeatmaps(Func query) => beatmaps.Beatmaps.Where(query).ToList(); /// /// Creates an from a valid storage path. @@ -437,9 +411,7 @@ namespace osu.Game.Beatmaps var hash = hashable.ComputeSHA2Hash(); // check if this beatmap has already been imported and exit early if so. - BeatmapSetInfo beatmapSet; - lock (beatmaps) - beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash); + var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash); if (beatmapSet != null) { @@ -523,8 +495,7 @@ namespace osu.Game.Beatmaps /// A list of available . public List GetAllUsableBeatmapSets() { - lock (beatmaps) - return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList(); + return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList(); } protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap From 4841d4a937bb42a212fdced63568220750d42b9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 18:27:17 +0900 Subject: [PATCH 311/344] Fix deletion and use single context for imports and deletions for now --- osu.Game/Beatmaps/BeatmapManager.cs | 53 ++++++++++++++++++++++------- osu.Game/IO/FileStore.cs | 4 +-- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 5f182c1dca..6b6b5cf0bc 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using Ionic.Zip; +using Microsoft.EntityFrameworkCore; using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.Graphics.Textures; @@ -95,6 +96,13 @@ namespace osu.Game.Beatmaps public BeatmapManager(Storage storage, Func context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) { createContext = context; + importContext = new Lazy(() => + { + var c = createContext(); + c.Database.AutoTransactionsEnabled = false; + return c; + }); + beatmaps = createBeatmapStore(context); files = new FileStore(context, storage); @@ -163,9 +171,7 @@ namespace osu.Game.Beatmaps notification.State = ProgressNotificationState.Completed; } - private readonly object importLock = new object(); - - private OsuDbContext importContext; + private readonly Lazy importContext; /// /// Import a beatmap from an . @@ -174,9 +180,9 @@ namespace osu.Game.Beatmaps public BeatmapSetInfo Import(ArchiveReader archiveReader) { // let's only allow one concurrent import at a time for now. - lock (importLock) + lock (importContext) { - var context = importContext ?? (importContext = createContext()); + var context = importContext.Value; context.Database.AutoTransactionsEnabled = false; @@ -287,10 +293,33 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - if (!beatmaps.Delete(beatmapSet)) return; + lock (importContext) + { + var context = importContext.Value; - if (!beatmapSet.Protected) - files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + using (var transaction = context.Database.BeginTransaction()) + { + context.ChangeTracker.AutoDetectChangesEnabled = false; + + // re-fetch the beatmap set on the import context. + beatmapSet = context.BeatmapSetInfo.Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == beatmapSet.ID); + + // create local stores so we can isolate and thread safely, and share a context/transaction. + var iFiles = new FileStore(() => context, storage); + var iBeatmaps = createBeatmapStore(() => context); + + if (iBeatmaps.Delete(beatmapSet)) + { + if (!beatmapSet.Protected) + iFiles.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + } + + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.SaveChanges(); + + transaction.Commit(); + } + } } /// @@ -587,9 +616,9 @@ namespace osu.Game.Beatmaps public void DeleteAll() { - var maps = GetAllUsableBeatmapSets().ToArray(); + var maps = GetAllUsableBeatmapSets(); - if (maps.Length == 0) return; + if (maps.Count == 0) return; var notification = new ProgressNotification { @@ -607,8 +636,8 @@ namespace osu.Game.Beatmaps // user requested abort return; - notification.Text = $"Deleting ({i} of {maps.Length})"; - notification.Progress = (float)++i / maps.Length; + notification.Text = $"Deleting ({i} of {maps.Count})"; + notification.Progress = (float)++i / maps.Count; Delete(b); } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 5f1b21ddb6..6654fa7cb1 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -87,9 +87,9 @@ namespace osu.Game.IO { foreach (var f in files.GroupBy(f => f.ID)) { - var refetch = context.Find(f.First().ID); + var refetch = context.FileInfo.Find(f.Key); refetch.ReferenceCount -= f.Count(); - context.Update(refetch); + context.FileInfo.Update(refetch); } context.SaveChanges(); From 668f68dd63ddd270c8ab8e6acfe4b19dc70c4e22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 18:27:57 +0900 Subject: [PATCH 312/344] Remove some unnecessary update calls --- osu.Game/Beatmaps/BeatmapStore.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 4892d4f3db..21a0b3ef6c 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -66,7 +66,6 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; - context.BeatmapSetInfo.Update(beatmapSet); context.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); @@ -85,7 +84,6 @@ namespace osu.Game.Beatmaps if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; - context.BeatmapSetInfo.Update(beatmapSet); context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); @@ -104,7 +102,6 @@ namespace osu.Game.Beatmaps if (beatmap.Hidden) return false; beatmap.Hidden = true; - context.BeatmapInfo.Update(beatmap); context.SaveChanges(); BeatmapHidden?.Invoke(beatmap); @@ -123,7 +120,6 @@ namespace osu.Game.Beatmaps if (!beatmap.Hidden) return false; beatmap.Hidden = false; - context.BeatmapInfo.Update(beatmap); context.SaveChanges(); BeatmapRestored?.Invoke(beatmap); From a85de09c0f8b76ff5a715e36c47051ab684b2e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 18:36:35 +0900 Subject: [PATCH 313/344] Fix beatmap carousel interactions with deletion when not yet displayed --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c72f599955..b8cc9782ca 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -33,10 +33,7 @@ namespace osu.Game.Screens.Select public IEnumerable Beatmaps { - get - { - return groups.Select(g => g.BeatmapSet); - } + get { return groups.Select(g => g.BeatmapSet); } set { @@ -100,12 +97,10 @@ namespace osu.Game.Screens.Select public void AddBeatmap(BeatmapSetInfo beatmapSet) { - var group = createGroup(beatmapSet); - - //for the time being, let's completely load the difficulty panels in the background. - //this likely won't scale so well, but allows us to completely async the loading flow. - Schedule(delegate + Schedule(() => { + var group = createGroup(beatmapSet); + addGroup(group); computeYPositions(); if (selectedGroup == null) @@ -113,7 +108,10 @@ namespace osu.Game.Screens.Select }); } - public void RemoveBeatmap(BeatmapSetInfo beatmapSet) => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID)); + public void RemoveBeatmap(BeatmapSetInfo beatmapSet) + { + Schedule(() => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); + } internal void UpdateBeatmap(BeatmapInfo beatmap) { @@ -519,7 +517,7 @@ namespace osu.Game.Screens.Select float drawHeight = DrawHeight; // Remove all panels that should no longer be on-screen - scrollableContent.RemoveAll(delegate (Panel p) + scrollableContent.RemoveAll(delegate(Panel p) { float panelPosY = p.Position.Y; bool remove = panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !p.IsPresent; From b73b4755ebdbe53100086dab741c2c8bcc6cd0d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 18:41:04 +0900 Subject: [PATCH 314/344] Ignore r# locked context inspection here --- osu.Game/Beatmaps/BeatmapManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6b6b5cf0bc..8a2997987b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -414,6 +414,7 @@ namespace osu.Game.Beatmaps private ArchiveReader getReaderFrom(string path) { if (ZipFile.IsZipFile(path)) + // ReSharper disable once InconsistentlySynchronizedField return new OszArchiveReader(storage.GetStream(path)); return new LegacyFilesystemReader(path); } From 0dac770e38d899303147e6195a2fd9030a17c782 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 19:39:48 +0900 Subject: [PATCH 315/344] Remove TestCase cleanup temporarily until context disposal is sorted --- osu.Game/Tests/Visual/OsuTestCase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 90c6e427c4..ca0aaebb5e 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -11,15 +11,13 @@ namespace osu.Game.Tests.Visual { public override void RunTest() { - Storage storage; using (var host = new HeadlessGameHost($"test-{Guid.NewGuid()}", realtime: false)) { - storage = host.Storage; host.Run(new OsuTestCaseTestRunner(this)); } // clean up after each run - storage.DeleteDirectory(string.Empty); + //storage.DeleteDirectory(string.Empty); } public class OsuTestCaseTestRunner : OsuGameBase From ac4b2797dc245ac34434f8c6e6b7ad0a0faaa618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 20:07:25 +0900 Subject: [PATCH 316/344] valuetuple nightmare --- osu.Game.Tests/app.config | 4 ++++ osu.Game/osu.Game.csproj | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config index faeaf001de..2f5b13a89d 100644 --- a/osu.Game.Tests/app.config +++ b/osu.Game.Tests/app.config @@ -6,6 +6,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9941792ec5..4488ea8333 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -103,7 +103,6 @@ $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True - $(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll @@ -201,10 +200,6 @@ $(SolutionDir)\packages\System.Runtime.CompilerServices.Unsafe.4.4.0\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll - True - $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True From 00be98dba732a642665042a909d38b0904d7352e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 20:37:53 +0900 Subject: [PATCH 317/344] Query test assert conditions less often --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 087fb54b5f..337800cd30 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -155,7 +155,7 @@ namespace osu.Game.Tests.Beatmaps.IO private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) { - Action waitAction = () => { while (!result()) Thread.Sleep(20); }; + Action waitAction = () => { while (!result()) Thread.Sleep(200); }; Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); } } From 71d614b813abee9720a6f5c2276e3684ad463abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 18 Oct 2017 21:57:54 +0900 Subject: [PATCH 318/344] FIx missing columns yet again --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 1 - ...Designer.cs => 20171018125509_InitialCreate.Designer.cs} | 4 +++- ...309_InitialCreate.cs => 20171018125509_InitialCreate.cs} | 1 + osu.Game/Migrations/OsuDbContextModelSnapshot.cs | 2 ++ osu.Game/osu.Game.csproj | 6 +++--- 6 files changed, 10 insertions(+), 6 deletions(-) rename osu.Game/Migrations/{20171017103309_InitialCreate.Designer.cs => 20171018125509_InitialCreate.Designer.cs} (95%) rename osu.Game/Migrations/{20171017103309_InitialCreate.cs => 20171018125509_InitialCreate.cs} (97%) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 337800cd30..e5b8c7fe57 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Beatmaps.IO //ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - Func> queryBeatmaps = () => store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); + Func> queryBeatmaps = () => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); Func> queryBeatmapSets = () => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 404add2fa5..2dfc4d0fe0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -12,7 +12,6 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } - [NotMapped] public int? OnlineBeatmapSetID { get; set; } public BeatmapMetadata Metadata { get; set; } diff --git a/osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs b/osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs similarity index 95% rename from osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs rename to osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs index 4d394fcd90..28e7e20743 100644 --- a/osu.Game/Migrations/20171017103309_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs @@ -10,7 +10,7 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171017103309_InitialCreate")] + [Migration("20171018125509_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -175,6 +175,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("OnlineBeatmapSetID"); + b.Property("Protected"); b.HasKey("ID"); diff --git a/osu.Game/Migrations/20171017103309_InitialCreate.cs b/osu.Game/Migrations/20171018125509_InitialCreate.cs similarity index 97% rename from osu.Game/Migrations/20171017103309_InitialCreate.cs rename to osu.Game/Migrations/20171018125509_InitialCreate.cs index 759e8bb664..512ffa2790 100644 --- a/osu.Game/Migrations/20171017103309_InitialCreate.cs +++ b/osu.Game/Migrations/20171018125509_InitialCreate.cs @@ -16,6 +16,7 @@ namespace osu.Game.Migrations .Annotation("Sqlite:Autoincrement", true), DeletePending = table.Column(type: "INTEGER", nullable: false), Hash = table.Column(type: "TEXT", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), Protected = table.Column(type: "INTEGER", nullable: false) }, constraints: table => diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index ef2c801a6c..8d0d8f20fe 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -174,6 +174,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("OnlineBeatmapSetID"); + b.Property("Protected"); b.HasKey("ID"); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4488ea8333..0e16c70277 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -281,9 +281,9 @@ - - - 20171017103309_InitialCreate.cs + + + 20171018125509_InitialCreate.cs From 5d5ea5fb2ef803260b1cf05bf426384c49c88471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 14:05:11 +0900 Subject: [PATCH 319/344] Fix all remaining db structure issues --- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Patterns/Legacy/PatternGenerator.cs | 2 +- .../ManiaDifficultyCalculator.cs | 2 +- .../Scoring/ManiaScoreProcessor.cs | 2 +- .../UI/ManiaRulesetContainer.cs | 10 +- .../Scoring/OsuScoreProcessor.cs | 2 +- .../UI/Cursor/GameplayCursor.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 12 +- .../Scoring/TaikoScoreProcessor.cs | 4 +- .../Tests/TestCaseTaikoPlayfield.cs | 2 +- .../UI/TaikoRulesetContainer.cs | 2 +- .../Beatmaps/Formats/OsuLegacyDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 - osu.Game/Beatmaps/BeatmapInfo.cs | 5 +- osu.Game/Beatmaps/BeatmapMetadata.cs | 12 +- osu.Game/Beatmaps/BeatmapStore.cs | 4 +- osu.Game/Beatmaps/DifficultyCalculator.cs | 2 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/Formats/BeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs | 4 +- osu.Game/Database/OsuDbContext.cs | 10 +- ... 20171019041408_InitialCreate.Designer.cs} | 72 ++--- ...ate.cs => 20171019041408_InitialCreate.cs} | 277 +++++++++--------- .../Migrations/OsuDbContextModelSnapshot.cs | 70 ++--- osu.Game/Rulesets/UI/RulesetContainer.cs | 4 +- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 10 +- .../Tests/Visual/TestCaseBeatmapDetails.cs | 8 +- .../Tests/Visual/TestCaseBeatmapSetOverlay.cs | 20 +- .../Tests/Visual/TestCasePlaySongSelect.cs | 6 +- .../Visual/TestCaseScrollingPlayfield.cs | 2 +- osu.Game/osu.Game.csproj | 6 +- 34 files changed, 259 insertions(+), 309 deletions(-) rename osu.Game/Migrations/{20171018125509_InitialCreate.Designer.cs => 20171019041408_InitialCreate.Designer.cs} (80%) rename osu.Game/Migrations/{20171018125509_InitialCreate.cs => 20171019041408_InitialCreate.cs} (85%) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index d7c86e1f89..f6d30ad3fa 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { beatmap = original; - BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty; + 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); random = new FastRandom(seed); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 20966a75f7..270c264e0c 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // The true distance, accounting for any repeats double distance = (distanceData?.Distance ?? 0) * repeatCount; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index a3173f9784..c38680c3a5 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (drainTime == 0) drainTime = 10000; - BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.Difficulty; + BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty; conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs index b98802db69..784df1f293 100644 --- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs @@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Mania return 0; } - protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize))); + protected override BeatmapConverter CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize))); } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index a200ba31e2..9b8ebe0070 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - BeatmapDifficulty difficulty = beatmap.BeatmapInfo.Difficulty; + BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty; hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max); hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max); diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 3b49d81674..08acd46c57 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -88,18 +88,18 @@ namespace osu.Game.Rulesets.Mania.UI protected override BeatmapConverter CreateBeatmapConverter() { if (IsForCurrentRuleset) - AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize)); + AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize)); else { float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) AvailableColumns = 7; - else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5) - AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6; + else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.CircleSize) >= 5) + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 5 ? 7 : 6; else if (percentSliderOrSpinner > 0.6) - AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4; + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) > 4 ? 5 : 4; else - AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7)); + AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) + 1, 7)); } return new ManiaBeatmapConverter(IsForCurrentRuleset, AvailableColumns); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 50239bf16c..e4960c5ca2 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate; + hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; foreach (var obj in beatmap.HitObjects) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index adfc946f86..d8dd6c7323 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (autoCursorScale && beatmap.Value != null) { // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.Difficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); } cursorContainer.Scale = new Vector2(scale); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index ceaecbb555..9b4a6c47a9 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { // Rewrite the beatmap info to add the slider velocity multiplier BeatmapInfo info = original.BeatmapInfo.DeepClone(); - info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; + info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier; Beatmap converted = base.ConvertBeatmap(original); @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps double distance = distanceData.Distance * repeats * legacy_velocity_multiplier; // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; // The duration of the taiko hit object double taikoDuration = distance / taikoVelocity; @@ -106,12 +106,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps speedAdjustedBeatLength *= speedAdjustment; // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; + double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats); + double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / repeats); if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { @@ -154,13 +154,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps Samples = obj.Samples, IsStrong = strong, Duration = taikoDuration, - TickRate = beatmap.BeatmapInfo.Difficulty.SliderTickRate == 3 ? 3 : 4, + TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4, }; } } else if (endTimeData != null) { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index abdda9676f..752e3bee26 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -71,12 +71,12 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override void SimulateAutoplay(Beatmap beatmap) { - double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, 0.5, 0.75, 0.98)); + double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); hpIncreaseTick = hp_hit_tick; hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; hpIncreaseGood = hpMultiplierNormal * hp_hit_good; - hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.Difficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); + hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); foreach (var obj in beatmap.HitObjects) { diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs index 2136d0d86a..14c27bc332 100644 --- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitObjects = new List { new CentreHit() }, BeatmapInfo = new BeatmapInfo { - Difficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = @"Unknown", diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index f0853aef0e..48ee0a5b42 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI StartTime = time, }; - barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); + barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0; Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs index 6bccd47b5c..95b691e07f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) { var beatmap = decoder.Decode(new StreamReader(stream)); - var difficulty = beatmap.BeatmapInfo.Difficulty; + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); Assert.AreEqual(8, difficulty.OverallDifficulty); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 56bb48965f..35b6cc2b02 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -72,7 +72,7 @@ namespace osu.Game.Beatmaps AuthorString = @"Unknown Creator", }, Version = @"Normal", - Difficulty = new BeatmapDifficulty() + BaseDifficulty = new BeatmapDifficulty() }; } } diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index e310c4a646..0b0fca8292 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -15,8 +15,6 @@ namespace osu.Game.Beatmaps [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } - public int BeatmapInfoID { get; set; } - public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d2ed5d692c..08cef3f934 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -20,7 +20,6 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; [JsonProperty("id")] - [NotMapped] public int? OnlineBeatmapID { get; set; } [JsonProperty("beatmapset_id")] @@ -36,8 +35,7 @@ namespace osu.Game.Beatmaps public int BaseDifficultyID { get; set; } - [Required] - public BeatmapDifficulty Difficulty { get; set; } + public BeatmapDifficulty BaseDifficulty { get; set; } [NotMapped] public BeatmapMetrics Metrics { get; set; } @@ -55,7 +53,6 @@ namespace osu.Game.Beatmaps /// /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). /// - [NotMapped] [JsonProperty("file_md5")] public string MD5Hash { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index ca8702d222..85bcfecfb8 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; @@ -16,19 +17,14 @@ namespace osu.Game.Beatmaps [NotMapped] public int? OnlineBeatmapSetID { get; set; } - public int? BeatmapSetInfoID { get; set; } - - public BeatmapSetInfo BeatmapSetInfo { get; set; } - - public int? BeatmapInfoID { get; set; } - - public BeatmapInfo BeatmapInfo { get; set; } - public string Title { get; set; } public string TitleUnicode { get; set; } public string Artist { get; set; } public string ArtistUnicode { get; set; } + public List Beatmaps { get; set; } + public List BeatmapSets { get; set; } + /// /// Helper property to deserialize a username to . /// diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 21a0b3ef6c..c2ec8c7b05 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -137,7 +137,7 @@ namespace osu.Game.Beatmaps public IEnumerable BeatmapSets => GetContext().BeatmapSetInfo .Include(s => s.Metadata) .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.Difficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Files).ThenInclude(f => f.FileInfo); @@ -145,6 +145,6 @@ namespace osu.Game.Beatmaps .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) .Include(b => b.Metadata) .Include(b => b.Ruleset) - .Include(b => b.Difficulty); + .Include(b => b.BaseDifficulty); } } diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs index 60cbf0ac61..bb6a292d9d 100644 --- a/osu.Game/Beatmaps/DifficultyCalculator.cs +++ b/osu.Game/Beatmaps/DifficultyCalculator.cs @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects; foreach (var h in Objects) - h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); PreprocessHitObjects(); } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 9a61762fa6..b9376849c1 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps AuthorString = "no one", }, BeatmapSet = new BeatmapSetInfo(), - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { DrainRate = 0, CircleSize = 0, diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 81695c3b5a..962c6ad49a 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata(), - Difficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty(), }, }; diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 3c06180532..d775ab409b 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -196,7 +196,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = splitKeyVal(line, ':'); - var difficulty = beatmap.BeatmapInfo.Difficulty; + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; switch (pair.Key) { case @"HPDrainRate": @@ -674,7 +674,7 @@ namespace osu.Game.Beatmaps.Formats } foreach (var hitObject in beatmap.HitObjects) - hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); } private KeyValuePair splitKeyVal(string line, char separator) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index cfc5f056ad..37d5bb21c8 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -65,20 +65,22 @@ namespace osu.Game.Database protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + modelBuilder.Entity().HasIndex(b => b.MD5Hash); modelBuilder.Entity().HasIndex(b => b.Hash); + modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash); + modelBuilder.Entity().HasIndex(b => b.Variant); modelBuilder.Entity().HasIndex(b => b.IntAction); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity().HasIndex(b => b.ReferenceCount); - modelBuilder.Entity().HasIndex(b => b.Name).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.InstantiationInfo).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Available); - modelBuilder.Entity().HasOne(m => m.BeatmapSetInfo).WithOne(s => s.Metadata).OnDelete(DeleteBehavior.Cascade); - modelBuilder.Entity().HasOne(m => m.BeatmapInfo).WithOne(b => b.Metadata).OnDelete(DeleteBehavior.Cascade); + modelBuilder.Entity().HasOne(b => b.BaseDifficulty); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs similarity index 80% rename from osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs rename to osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs index 28e7e20743..0820041643 100644 --- a/osu.Game/Migrations/20171018125509_InitialCreate.Designer.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.Designer.cs @@ -10,7 +10,7 @@ using System; namespace osu.Game.Migrations { [DbContext(typeof(OsuDbContext))] - [Migration("20171018125509_InitialCreate")] + [Migration("20171019041408_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -26,8 +26,6 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); - b.Property("BeatmapInfoID"); - b.Property("CircleSize"); b.Property("DrainRate"); @@ -40,9 +38,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoID") - .IsUnique(); - b.ToTable("BeatmapDifficulty"); }); @@ -73,6 +68,10 @@ namespace osu.Game.Migrations b.Property("MD5Hash"); + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + b.Property("Path"); b.Property("RulesetID"); @@ -93,12 +92,16 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BaseDifficultyID"); + b.HasIndex("BeatmapSetInfoID"); b.HasIndex("Hash"); b.HasIndex("MD5Hash"); + b.HasIndex("MetadataID"); + b.HasIndex("RulesetID"); b.ToTable("BeatmapInfo"); @@ -120,10 +123,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("BeatmapInfoID"); - - b.Property("BeatmapSetInfoID"); - b.Property("PreviewTime"); b.Property("Source"); @@ -136,12 +135,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoID") - .IsUnique(); - - b.HasIndex("BeatmapSetInfoID") - .IsUnique(); - b.ToTable("BeatmapMetadata"); }); @@ -175,6 +168,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("MetadataID"); + b.Property("OnlineBeatmapSetID"); b.Property("Protected"); @@ -185,6 +180,8 @@ namespace osu.Game.Migrations b.HasIndex("Hash"); + b.HasIndex("MetadataID"); + b.ToTable("BeatmapSetInfo"); }); @@ -246,49 +243,31 @@ namespace osu.Game.Migrations b.HasIndex("Available"); - b.HasIndex("InstantiationInfo") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - b.ToTable("RulesetInfo"); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo") - .WithOne("Difficulty") - .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - 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.BeatmapMetadata", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "BeatmapInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSetInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -301,6 +280,13 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/Migrations/20171018125509_InitialCreate.cs b/osu.Game/Migrations/20171019041408_InitialCreate.cs similarity index 85% rename from osu.Game/Migrations/20171018125509_InitialCreate.cs rename to osu.Game/Migrations/20171019041408_InitialCreate.cs index 512ffa2790..23e5b6f8bb 100644 --- a/osu.Game/Migrations/20171018125509_InitialCreate.cs +++ b/osu.Game/Migrations/20171019041408_InitialCreate.cs @@ -9,19 +9,43 @@ namespace osu.Game.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "BeatmapSetInfo", + name: "BeatmapDifficulty", columns: table => new { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - DeletePending = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), - Protected = table.Column(type: "INTEGER", nullable: false) + ApproachRate = table.Column(type: "REAL", nullable: false), + CircleSize = table.Column(type: "REAL", nullable: false), + DrainRate = table.Column(type: "REAL", nullable: false), + OverallDifficulty = table.Column(type: "REAL", nullable: false), + SliderMultiplier = table.Column(type: "REAL", nullable: false), + SliderTickRate = table.Column(type: "REAL", nullable: false) }, constraints: table => { - table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "BeatmapMetadata", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Artist = table.Column(type: "TEXT", nullable: true), + ArtistUnicode = table.Column(type: "TEXT", nullable: true), + AudioFile = table.Column(type: "TEXT", nullable: true), + Author = table.Column(type: "TEXT", nullable: true), + BackgroundFile = table.Column(type: "TEXT", nullable: true), + PreviewTime = table.Column(type: "INTEGER", nullable: false), + Source = table.Column(type: "TEXT", nullable: true), + Tags = table.Column(type: "TEXT", nullable: true), + Title = table.Column(type: "TEXT", nullable: true), + TitleUnicode = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); }); migrationBuilder.CreateTable( @@ -69,6 +93,87 @@ namespace osu.Game.Migrations table.PrimaryKey("PK_RulesetInfo", x => x.ID); }); + migrationBuilder.CreateTable( + name: "BeatmapSetInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapSetID = table.Column(type: "INTEGER", nullable: true), + Protected = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapSetInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapSetInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "BeatmapInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + AudioLeadIn = table.Column(type: "INTEGER", nullable: false), + BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), + BeatDivisor = table.Column(type: "INTEGER", nullable: false), + BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), + Countdown = table.Column(type: "INTEGER", nullable: false), + DistanceSpacing = table.Column(type: "REAL", nullable: false), + GridSize = table.Column(type: "INTEGER", nullable: false), + Hash = table.Column(type: "TEXT", nullable: true), + Hidden = table.Column(type: "INTEGER", nullable: false), + LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), + MD5Hash = table.Column(type: "TEXT", nullable: true), + MetadataID = table.Column(type: "INTEGER", nullable: true), + OnlineBeatmapID = table.Column(type: "INTEGER", nullable: true), + Path = table.Column(type: "TEXT", nullable: true), + RulesetID = table.Column(type: "INTEGER", nullable: false), + SpecialStyle = table.Column(type: "INTEGER", nullable: false), + StackLeniency = table.Column(type: "REAL", nullable: false), + StarDifficulty = table.Column(type: "REAL", nullable: false), + StoredBookmarks = table.Column(type: "TEXT", nullable: true), + TimelineZoom = table.Column(type: "REAL", nullable: false), + Version = table.Column(type: "TEXT", nullable: true), + WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BeatmapInfo", x => x.ID); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapDifficulty_BaseDifficultyID", + column: x => x.BaseDifficultyID, + principalTable: "BeatmapDifficulty", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", + column: x => x.BeatmapSetInfoID, + principalTable: "BeatmapSetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BeatmapInfo_BeatmapMetadata_MetadataID", + column: x => x.MetadataID, + principalTable: "BeatmapMetadata", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_BeatmapInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateTable( name: "BeatmapSetFileInfo", columns: table => new @@ -96,116 +201,10 @@ namespace osu.Game.Migrations onDelete: ReferentialAction.Cascade); }); - migrationBuilder.CreateTable( - name: "BeatmapInfo", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - AudioLeadIn = table.Column(type: "INTEGER", nullable: false), - BaseDifficultyID = table.Column(type: "INTEGER", nullable: false), - BeatDivisor = table.Column(type: "INTEGER", nullable: false), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: false), - Countdown = table.Column(type: "INTEGER", nullable: false), - DistanceSpacing = table.Column(type: "REAL", nullable: false), - GridSize = table.Column(type: "INTEGER", nullable: false), - Hash = table.Column(type: "TEXT", nullable: true), - Hidden = table.Column(type: "INTEGER", nullable: false), - LetterboxInBreaks = table.Column(type: "INTEGER", nullable: false), - MD5Hash = table.Column(type: "TEXT", nullable: true), - Path = table.Column(type: "TEXT", nullable: true), - RulesetID = table.Column(type: "INTEGER", nullable: false), - SpecialStyle = table.Column(type: "INTEGER", nullable: false), - StackLeniency = table.Column(type: "REAL", nullable: false), - StarDifficulty = table.Column(type: "REAL", nullable: false), - StoredBookmarks = table.Column(type: "TEXT", nullable: true), - TimelineZoom = table.Column(type: "REAL", nullable: false), - Version = table.Column(type: "TEXT", nullable: true), - WidescreenStoryboard = table.Column(type: "INTEGER", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapInfo", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapInfo_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapInfo_RulesetInfo_RulesetID", - column: x => x.RulesetID, - principalTable: "RulesetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapDifficulty", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - ApproachRate = table.Column(type: "REAL", nullable: false), - BeatmapInfoID = table.Column(type: "INTEGER", nullable: false), - CircleSize = table.Column(type: "REAL", nullable: false), - DrainRate = table.Column(type: "REAL", nullable: false), - OverallDifficulty = table.Column(type: "REAL", nullable: false), - SliderMultiplier = table.Column(type: "REAL", nullable: false), - SliderTickRate = table.Column(type: "REAL", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapDifficulty", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapDifficulty_BeatmapInfo_BeatmapInfoID", - column: x => x.BeatmapInfoID, - principalTable: "BeatmapInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "BeatmapMetadata", - columns: table => new - { - ID = table.Column(type: "INTEGER", nullable: false) - .Annotation("Sqlite:Autoincrement", true), - Artist = table.Column(type: "TEXT", nullable: true), - ArtistUnicode = table.Column(type: "TEXT", nullable: true), - AudioFile = table.Column(type: "TEXT", nullable: true), - Author = table.Column(type: "TEXT", nullable: true), - BackgroundFile = table.Column(type: "TEXT", nullable: true), - BeatmapInfoID = table.Column(type: "INTEGER", nullable: true), - BeatmapSetInfoID = table.Column(type: "INTEGER", nullable: true), - PreviewTime = table.Column(type: "INTEGER", nullable: false), - Source = table.Column(type: "TEXT", nullable: true), - Tags = table.Column(type: "TEXT", nullable: true), - Title = table.Column(type: "TEXT", nullable: true), - TitleUnicode = table.Column(type: "TEXT", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_BeatmapMetadata", x => x.ID); - table.ForeignKey( - name: "FK_BeatmapMetadata_BeatmapInfo_BeatmapInfoID", - column: x => x.BeatmapInfoID, - principalTable: "BeatmapInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_BeatmapMetadata_BeatmapSetInfo_BeatmapSetInfoID", - column: x => x.BeatmapSetInfoID, - principalTable: "BeatmapSetInfo", - principalColumn: "ID", - onDelete: ReferentialAction.Cascade); - }); - migrationBuilder.CreateIndex( - name: "IX_BeatmapDifficulty_BeatmapInfoID", - table: "BeatmapDifficulty", - column: "BeatmapInfoID", - unique: true); + name: "IX_BeatmapInfo_BaseDifficultyID", + table: "BeatmapInfo", + column: "BaseDifficultyID"); migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_BeatmapSetInfoID", @@ -222,23 +221,16 @@ namespace osu.Game.Migrations table: "BeatmapInfo", column: "MD5Hash"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MetadataID", + table: "BeatmapInfo", + column: "MetadataID"); + migrationBuilder.CreateIndex( name: "IX_BeatmapInfo_RulesetID", table: "BeatmapInfo", column: "RulesetID"); - migrationBuilder.CreateIndex( - name: "IX_BeatmapMetadata_BeatmapInfoID", - table: "BeatmapMetadata", - column: "BeatmapInfoID", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_BeatmapMetadata_BeatmapSetInfoID", - table: "BeatmapMetadata", - column: "BeatmapSetInfoID", - unique: true); - migrationBuilder.CreateIndex( name: "IX_BeatmapSetFileInfo_BeatmapSetInfoID", table: "BeatmapSetFileInfo", @@ -259,6 +251,11 @@ namespace osu.Game.Migrations table: "BeatmapSetInfo", column: "Hash"); + migrationBuilder.CreateIndex( + name: "IX_BeatmapSetInfo_MetadataID", + table: "BeatmapSetInfo", + column: "MetadataID"); + migrationBuilder.CreateIndex( name: "IX_FileInfo_Hash", table: "FileInfo", @@ -284,27 +281,12 @@ namespace osu.Game.Migrations name: "IX_RulesetInfo_Available", table: "RulesetInfo", column: "Available"); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_InstantiationInfo", - table: "RulesetInfo", - column: "InstantiationInfo", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_RulesetInfo_Name", - table: "RulesetInfo", - column: "Name", - unique: true); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "BeatmapDifficulty"); - - migrationBuilder.DropTable( - name: "BeatmapMetadata"); + name: "BeatmapInfo"); migrationBuilder.DropTable( name: "BeatmapSetFileInfo"); @@ -313,16 +295,19 @@ namespace osu.Game.Migrations name: "KeyBinding"); migrationBuilder.DropTable( - name: "BeatmapInfo"); + name: "BeatmapDifficulty"); migrationBuilder.DropTable( - name: "FileInfo"); + name: "RulesetInfo"); migrationBuilder.DropTable( name: "BeatmapSetInfo"); migrationBuilder.DropTable( - name: "RulesetInfo"); + name: "FileInfo"); + + migrationBuilder.DropTable( + name: "BeatmapMetadata"); } } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 8d0d8f20fe..0576242648 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -25,8 +25,6 @@ namespace osu.Game.Migrations b.Property("ApproachRate"); - b.Property("BeatmapInfoID"); - b.Property("CircleSize"); b.Property("DrainRate"); @@ -39,9 +37,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoID") - .IsUnique(); - b.ToTable("BeatmapDifficulty"); }); @@ -72,6 +67,10 @@ namespace osu.Game.Migrations b.Property("MD5Hash"); + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + b.Property("Path"); b.Property("RulesetID"); @@ -92,12 +91,16 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("BaseDifficultyID"); + b.HasIndex("BeatmapSetInfoID"); b.HasIndex("Hash"); b.HasIndex("MD5Hash"); + b.HasIndex("MetadataID"); + b.HasIndex("RulesetID"); b.ToTable("BeatmapInfo"); @@ -119,10 +122,6 @@ namespace osu.Game.Migrations b.Property("BackgroundFile"); - b.Property("BeatmapInfoID"); - - b.Property("BeatmapSetInfoID"); - b.Property("PreviewTime"); b.Property("Source"); @@ -135,12 +134,6 @@ namespace osu.Game.Migrations b.HasKey("ID"); - b.HasIndex("BeatmapInfoID") - .IsUnique(); - - b.HasIndex("BeatmapSetInfoID") - .IsUnique(); - b.ToTable("BeatmapMetadata"); }); @@ -174,6 +167,8 @@ namespace osu.Game.Migrations b.Property("Hash"); + b.Property("MetadataID"); + b.Property("OnlineBeatmapSetID"); b.Property("Protected"); @@ -184,6 +179,8 @@ namespace osu.Game.Migrations b.HasIndex("Hash"); + b.HasIndex("MetadataID"); + b.ToTable("BeatmapSetInfo"); }); @@ -245,49 +242,31 @@ namespace osu.Game.Migrations b.HasIndex("Available"); - b.HasIndex("InstantiationInfo") - .IsUnique(); - - b.HasIndex("Name") - .IsUnique(); - b.ToTable("RulesetInfo"); }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo") - .WithOne("Difficulty") - .HasForeignKey("osu.Game.Beatmaps.BeatmapDifficulty", "BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - 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.BeatmapMetadata", b => - { - b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "BeatmapInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapInfoID") - .OnDelete(DeleteBehavior.Cascade); - - b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSetInfo") - .WithOne("Metadata") - .HasForeignKey("osu.Game.Beatmaps.BeatmapMetadata", "BeatmapSetInfoID") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") @@ -300,6 +279,13 @@ namespace osu.Game.Migrations .HasForeignKey("FileInfoID") .OnDelete(DeleteBehavior.Cascade); }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 729df02ffd..6f53b76031 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -172,11 +172,11 @@ namespace osu.Game.Rulesets.UI // Apply difficulty adjustments from mods before using Difficulty. foreach (var mod in Mods.OfType()) - mod.ApplyToDifficulty(Beatmap.BeatmapInfo.Difficulty); + mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); // Apply defaults foreach (var h in Beatmap.HitObjects) - h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty); + h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); // Post-process the beatmap processor.PostProcess(Beatmap); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a98362e89c..d7c509d979 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Select advanced.Beatmap = new BeatmapInfo { StarDifficulty = 0, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 0, DrainRate = 0, diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index f1215ab33d..3c9cffadfb 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -32,17 +32,17 @@ namespace osu.Game.Screens.Select.Details if ((Beatmap?.Ruleset?.ID ?? 0) == 3) { firstValue.Title = "Key Amount"; - firstValue.Value = (int)Math.Round(Beatmap?.Difficulty?.CircleSize ?? 0); + firstValue.Value = (int)Math.Round(Beatmap?.BaseDifficulty?.CircleSize ?? 0); } else { firstValue.Title = "Circle Size"; - firstValue.Value = Beatmap?.Difficulty?.CircleSize ?? 0; + firstValue.Value = Beatmap?.BaseDifficulty?.CircleSize ?? 0; } - hpDrain.Value = beatmap.Difficulty?.DrainRate ?? 0; - accuracy.Value = beatmap.Difficulty?.OverallDifficulty ?? 0; - approachRate.Value = beatmap.Difficulty?.ApproachRate ?? 0; + hpDrain.Value = beatmap.BaseDifficulty?.DrainRate ?? 0; + accuracy.Value = beatmap.BaseDifficulty?.OverallDifficulty ?? 0; + approachRate.Value = beatmap.BaseDifficulty?.ApproachRate ?? 0; starDifficulty.Value = (float)beatmap.StarDifficulty; } } diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs index cd4d97425b..5306121a92 100644 --- a/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game/Tests/Visual/TestCaseBeatmapDetails.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual Source = "osu!lazer", Tags = "this beatmap has all the metrics", }, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 7, DrainRate = 1, @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual Source = "osu!lazer", Tags = "this beatmap has ratings metrics but not retries or fails", }, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, DrainRate = 9, @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual Source = "osu!lazer", Tags = "this beatmap has retries and fails but no ratings", }, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.7f, DrainRate = 6, @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual Source = "osu!lazer", Tags = "this beatmap has no metrics", }, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 5, diff --git a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs index 72d97f905c..1ade0be626 100644 --- a/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 1.36, Version = @"BASIC", Ruleset = mania, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 6.5f, @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 2.22, Version = @"NOVICE", Ruleset = mania, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 7, @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 3.49, Version = @"ADVANCED", Ruleset = mania, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 7.5f, @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 4.24, Version = @"EXHAUST", Ruleset = mania, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 8, @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 5.26, Version = @"GRAVITY", Ruleset = mania, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 4, DrainRate = 8.5f, @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 1.40, Version = @"yzrin's Kantan", Ruleset = taiko, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 7, @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 2.23, Version = @"Futsuu", Ruleset = taiko, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 6, @@ -295,7 +295,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 3.19, Version = @"Muzukashii", Ruleset = taiko, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 2, DrainRate = 6, @@ -323,7 +323,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 3.97, Version = @"Charlotte's Oni", Ruleset = taiko, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 6, @@ -351,7 +351,7 @@ namespace osu.Game.Tests.Visual StarDifficulty = 5.08, Version = @"Labyrinth Oni", Ruleset = taiko, - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, DrainRate = 5, diff --git a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs index a46542760b..965308c32c 100644 --- a/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game/Tests/Visual/TestCasePlaySongSelect.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual Ruleset = rulesets.AvailableRulesets.First(), Path = "normal.osu", Version = "Normal", - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, } @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual Ruleset = rulesets.AvailableRulesets.First(), Path = "hard.osu", Version = "Hard", - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 5, } @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual Ruleset = rulesets.AvailableRulesets.First(), Path = "insane.osu", Version = "Insane", - Difficulty = new BeatmapDifficulty + BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 7, } diff --git a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs index d0761e5841..40fb22af1e 100644 --- a/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs +++ b/osu.Game/Tests/Visual/TestCaseScrollingPlayfield.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual HitObjects = objects, BeatmapInfo = new BeatmapInfo { - Difficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata() } }; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0e16c70277..85b08afecd 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -281,9 +281,9 @@ - - - 20171018125509_InitialCreate.cs + + + 20171019041408_InitialCreate.cs From 908c6d827fc466e719101037d94bca27ef00b97f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 14:49:23 +0900 Subject: [PATCH 320/344] ValueTuple please --- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7a3bbab176..51c1b03373 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -40,7 +40,7 @@ - ../packages/System.ValueTuple.4.4.0/lib/net461/System.ValueTuple.dll + $(SolutionDir)\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll True diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 85b08afecd..601c99e19f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -861,10 +861,6 @@ - - true - true - From 36af0dc809418ac61b1d1a72f547736ec68af9ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 14:59:11 +0900 Subject: [PATCH 321/344] Update app config for rulesets --- osu.Game.Rulesets.Catch/app.config | 4 ++++ osu.Game.Rulesets.Mania/app.config | 4 ++++ osu.Game.Rulesets.Osu/app.config | 4 ++++ osu.Game.Rulesets.Taiko/app.config | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/osu.Game.Rulesets.Catch/app.config b/osu.Game.Rulesets.Catch/app.config index 11af32e2cf..c9d4e44b1a 100644 --- a/osu.Game.Rulesets.Catch/app.config +++ b/osu.Game.Rulesets.Catch/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/app.config b/osu.Game.Rulesets.Mania/app.config index 11af32e2cf..c9d4e44b1a 100644 --- a/osu.Game.Rulesets.Mania/app.config +++ b/osu.Game.Rulesets.Mania/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu/app.config b/osu.Game.Rulesets.Osu/app.config index 11af32e2cf..c9d4e44b1a 100644 --- a/osu.Game.Rulesets.Osu/app.config +++ b/osu.Game.Rulesets.Osu/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/app.config b/osu.Game.Rulesets.Taiko/app.config index 11af32e2cf..c9d4e44b1a 100644 --- a/osu.Game.Rulesets.Taiko/app.config +++ b/osu.Game.Rulesets.Taiko/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file From 12900a8b1503869a2a5a10a7ce29ac20b6383790 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 15:53:11 +0900 Subject: [PATCH 322/344] Remove unnecessary second call to AutoTransactionsEnabled --- osu.Game/Beatmaps/BeatmapManager.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8a2997987b..47dbc72837 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -184,8 +184,6 @@ namespace osu.Game.Beatmaps { var context = importContext.Value; - context.Database.AutoTransactionsEnabled = false; - using (var transaction = context.Database.BeginTransaction()) { // create local stores so we can isolate and thread safely, and share a context/transaction. From 36c00577af65638ed56f56dedaca01d0210005ce Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 16:35:06 +0900 Subject: [PATCH 323/344] Reduce database log output Also hard-disables it for uninteresting log levels, providing a further performance boost. --- osu.Game/Database/OsuDbContext.cs | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 37d5bb21c8..e3605181a4 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -31,7 +31,8 @@ namespace osu.Game.Database /// /// Create a new in-memory OsuDbContext instance. /// - public OsuDbContext() : this("DataSource=:memory:") + public OsuDbContext() + : this("DataSource=:memory:") { // required for tooling (see https://wildermuth.com/2017/07/06/Program-cs-in-ASP-NET-Core-2-0). } @@ -116,9 +117,37 @@ namespace osu.Game.Database private class OsuDbLogger : ILogger { public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - => Logger.Log(formatter(state, exception), LoggingTarget.Database, Framework.Logging.LogLevel.Debug); + { + if (logLevel < LogLevel.Information) + return; - public bool IsEnabled(LogLevel logLevel) => true; + Framework.Logging.LogLevel frameworkLogLevel; + + switch (logLevel) + { + default: + frameworkLogLevel = Framework.Logging.LogLevel.Debug; + break; + case LogLevel.Warning: + frameworkLogLevel = Framework.Logging.LogLevel.Important; + break; + case LogLevel.Error: + case LogLevel.Critical: + frameworkLogLevel = Framework.Logging.LogLevel.Error; + break; + } + + Logger.Log(formatter(state, exception), LoggingTarget.Database, frameworkLogLevel); + } + + public bool IsEnabled(LogLevel logLevel) + { +#if DEBUG + return logLevel > LogLevel.Debug; +#else + return logLevel > LogLevel.Information; +#endif + } public IDisposable BeginScope(TState state) => null; } From b02dd196f678d67f95fa80dee534a903bc2d6abb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 17:29:39 +0900 Subject: [PATCH 324/344] Don't make one factory each context A factory is supposed to be re-used. --- osu.Game/Database/OsuDbContext.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e3605181a4..98a5cc8b67 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -22,6 +22,8 @@ namespace osu.Game.Database public DbSet RulesetInfo { get; set; } private readonly string connectionString; + private static readonly Lazy logger = new Lazy(() => new OsuDbLoggerFactory()); + static OsuDbContext() { // required to initialise native SQLite libraries on some platforms. @@ -60,7 +62,7 @@ namespace osu.Game.Database { base.OnConfiguring(optionsBuilder); optionsBuilder.UseSqlite(connectionString); - optionsBuilder.UseLoggerFactory(new OsuDbLoggerFactory()); + optionsBuilder.UseLoggerFactory(logger.Value); } protected override void OnModelCreating(ModelBuilder modelBuilder) From f7d0df174327b0edf4ead40825a793dfad4300f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 17:50:46 +0900 Subject: [PATCH 325/344] Fix beatmap difficulty and metadata deletion --- osu.Game/Beatmaps/BeatmapStore.cs | 15 ++++++++++++++- osu.Game/Database/OsuDbContext.cs | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index c2ec8c7b05..8eac35a667 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -130,7 +130,20 @@ namespace osu.Game.Beatmaps { var context = GetContext(); - context.BeatmapSetInfo.RemoveRange(context.BeatmapSetInfo.Where(b => b.DeletePending && !b.Protected)); + var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Metadata); + + // metadata is M-N so we can't rely on cascades + context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata)); + context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata))); + + // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. + context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); + + // cascades down to beatmaps. + context.BeatmapSetInfo.RemoveRange(purgeable); context.SaveChanges(); } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 98a5cc8b67..2938a8e7be 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -16,6 +16,8 @@ namespace osu.Game.Database public class OsuDbContext : DbContext { public DbSet BeatmapInfo { get; set; } + public DbSet BeatmapDifficulty { get; set; } + public DbSet BeatmapMetadata { get; set; } public DbSet BeatmapSetInfo { get; set; } public DbSet DatabasedKeyBinding { get; set; } public DbSet FileInfo { get; set; } From 1fbbee14e4c000931a8b404bac13b4ce376ab5a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 19:41:16 +0900 Subject: [PATCH 326/344] Allow migration from sqlite-net to EF Bonus stage --- osu.Game/Database/DatabaseContextFactory.cs | 2 +- osu.Game/Database/OsuDbContext.cs | 69 +++++++++++++++++++++ osu.Game/OsuGameBase.cs | 3 +- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index e22301adfe..359188b4e2 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -14,6 +14,6 @@ namespace osu.Game.Database this.host = host; } - public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client-ef")); + public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client")); } } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 2938a8e7be..3f51ac1910 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -156,5 +156,74 @@ namespace osu.Game.Database public IDisposable BeginScope(TState state) => null; } } + + public void Migrate() + { + migrateFromSqliteNet(); + Database.Migrate(); + } + + private void migrateFromSqliteNet() + { + try + { + // will fail if EF hasn't touched the database yet. + Database.ExecuteSqlCommand("SELECT * FROM __EFMigrationsHistory LIMIT 1"); + } + catch + { + try + { + // will fail (intentionally) if we don't have sqlite-net data present. + Database.ExecuteSqlCommand("SELECT OnlineBeatmapSetId FROM BeatmapMetadata LIMIT 1"); + + // we are good to perform messy migration of data!. + Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old"); + Database.ExecuteSqlCommand("ALTER TABLE FileInfo RENAME TO FileInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE KeyBinding RENAME TO KeyBinding_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetInfo RENAME TO BeatmapSetInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapInfo RENAME TO BeatmapInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetFileInfo RENAME TO BeatmapSetFileInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE RulesetInfo RENAME TO RulesetInfo_Old"); + + Database.ExecuteSqlCommand("DROP TABLE StoreVersion"); + + // perform EF migrations to create sane table structure. + Database.Migrate(); + + // copy data table by table to new structure, dropping old tables as we go. + Database.ExecuteSqlCommand("INSERT INTO FileInfo SELECT * FROM FileInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE FileInfo_Old"); + + Database.ExecuteSqlCommand("INSERT INTO KeyBinding SELECT ID, [Action], Keys, RulesetID, Variant FROM KeyBinding_Old"); + Database.ExecuteSqlCommand("DROP TABLE KeyBinding_Old"); + + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapMetadata SELECT ID, Artist, ArtistUnicode, AudioFile, Author, BackgroundFile, PreviewTime, Source, Tags, Title, TitleUnicode FROM BeatmapMetadata_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapMetadata_Old"); + + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapDifficulty SELECT `ID`, `ApproachRate`, `CircleSize`, `DrainRate`, `OverallDifficulty`, `SliderMultiplier`, `SliderTickRate` FROM BeatmapDifficulty_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapDifficulty_Old"); + + Database.ExecuteSqlCommand("INSERT INTO BeatmapSetInfo SELECT ID, DeletePending, Hash, BeatmapMetadataID, OnlineBeatmapSetID, Protected FROM BeatmapSetInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapSetInfo_Old"); + + Database.ExecuteSqlCommand("INSERT INTO BeatmapSetFileInfo SELECT ID, BeatmapSetInfoID, FileInfoID, Filename FROM BeatmapSetFileInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapSetFileInfo_Old"); + + Database.ExecuteSqlCommand("INSERT INTO RulesetInfo SELECT ID, Available, InstantiationInfo, Name FROM RulesetInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old"); + + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); + } + catch + { + } + } + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 22eb75fcea..be12f3ddbc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -4,7 +4,6 @@ using System; using System.Diagnostics; using System.Reflection; -using Microsoft.EntityFrameworkCore; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Development; @@ -92,7 +91,7 @@ namespace osu.Game dependencies.Cache(LocalConfig); using (var context = contextFactory.GetContext()) - context.Database.Migrate(); + context.Migrate(); dependencies.Cache(API = new APIAccess { From a724a20b020732e32bd9df7ffc471716c26d5a44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 20:07:25 +0900 Subject: [PATCH 327/344] Remove duplicate reference to opentk package --- osu.Desktop/packages.config | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Desktop/packages.config b/osu.Desktop/packages.config index 0ec2cc196d..58f9102aa1 100644 --- a/osu.Desktop/packages.config +++ b/osu.Desktop/packages.config @@ -7,7 +7,6 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste - From 365e2343a1af18031795136e20041692bcfa06cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 21:09:29 +0900 Subject: [PATCH 328/344] Remove AllRuleset references --- osu.Desktop/osu.Desktop.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 7b2ec3b24c..fb1ca7eb10 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -57,7 +57,6 @@ false AnyCPU true - AllRules.ruleset false false false @@ -76,7 +75,6 @@ false AnyCPU true - AllRules.ruleset false false @@ -102,7 +100,6 @@ false 6 prompt - AllRules.ruleset --tests From 1672e0d6b660c7379d4da163f3b92c3e7a70933b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 21:11:43 +0900 Subject: [PATCH 329/344] Add fallback logic in case migration fails Nuke it all. --- osu.Game/Database/OsuDbContext.cs | 74 ++++++++++++++++++------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 3f51ac1910..0ebea94683 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -177,48 +177,58 @@ namespace osu.Game.Database // will fail (intentionally) if we don't have sqlite-net data present. Database.ExecuteSqlCommand("SELECT OnlineBeatmapSetId FROM BeatmapMetadata LIMIT 1"); - // we are good to perform messy migration of data!. - Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old"); - Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old"); - Database.ExecuteSqlCommand("ALTER TABLE FileInfo RENAME TO FileInfo_Old"); - Database.ExecuteSqlCommand("ALTER TABLE KeyBinding RENAME TO KeyBinding_Old"); - Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetInfo RENAME TO BeatmapSetInfo_Old"); - Database.ExecuteSqlCommand("ALTER TABLE BeatmapInfo RENAME TO BeatmapInfo_Old"); - Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetFileInfo RENAME TO BeatmapSetFileInfo_Old"); - Database.ExecuteSqlCommand("ALTER TABLE RulesetInfo RENAME TO RulesetInfo_Old"); + try + { + // we are good to perform messy migration of data!. + Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old"); + Database.ExecuteSqlCommand("ALTER TABLE FileInfo RENAME TO FileInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE KeyBinding RENAME TO KeyBinding_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetInfo RENAME TO BeatmapSetInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapInfo RENAME TO BeatmapInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE BeatmapSetFileInfo RENAME TO BeatmapSetFileInfo_Old"); + Database.ExecuteSqlCommand("ALTER TABLE RulesetInfo RENAME TO RulesetInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE StoreVersion"); + Database.ExecuteSqlCommand("DROP TABLE StoreVersion"); - // perform EF migrations to create sane table structure. - Database.Migrate(); + // perform EF migrations to create sane table structure. + Database.Migrate(); - // copy data table by table to new structure, dropping old tables as we go. - Database.ExecuteSqlCommand("INSERT INTO FileInfo SELECT * FROM FileInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE FileInfo_Old"); + // copy data table by table to new structure, dropping old tables as we go. + Database.ExecuteSqlCommand("INSERT INTO FileInfo SELECT * FROM FileInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE FileInfo_Old"); - Database.ExecuteSqlCommand("INSERT INTO KeyBinding SELECT ID, [Action], Keys, RulesetID, Variant FROM KeyBinding_Old"); - Database.ExecuteSqlCommand("DROP TABLE KeyBinding_Old"); + Database.ExecuteSqlCommand("INSERT INTO KeyBinding SELECT ID, [Action], Keys, RulesetID, Variant FROM KeyBinding_Old"); + Database.ExecuteSqlCommand("DROP TABLE KeyBinding_Old"); - Database.ExecuteSqlCommand( - "INSERT INTO BeatmapMetadata SELECT ID, Artist, ArtistUnicode, AudioFile, Author, BackgroundFile, PreviewTime, Source, Tags, Title, TitleUnicode FROM BeatmapMetadata_Old"); - Database.ExecuteSqlCommand("DROP TABLE BeatmapMetadata_Old"); + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapMetadata SELECT ID, Artist, ArtistUnicode, AudioFile, Author, BackgroundFile, PreviewTime, Source, Tags, Title, TitleUnicode FROM BeatmapMetadata_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapMetadata_Old"); - Database.ExecuteSqlCommand( - "INSERT INTO BeatmapDifficulty SELECT `ID`, `ApproachRate`, `CircleSize`, `DrainRate`, `OverallDifficulty`, `SliderMultiplier`, `SliderTickRate` FROM BeatmapDifficulty_Old"); - Database.ExecuteSqlCommand("DROP TABLE BeatmapDifficulty_Old"); + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapDifficulty SELECT `ID`, `ApproachRate`, `CircleSize`, `DrainRate`, `OverallDifficulty`, `SliderMultiplier`, `SliderTickRate` FROM BeatmapDifficulty_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapDifficulty_Old"); - Database.ExecuteSqlCommand("INSERT INTO BeatmapSetInfo SELECT ID, DeletePending, Hash, BeatmapMetadataID, OnlineBeatmapSetID, Protected FROM BeatmapSetInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE BeatmapSetInfo_Old"); + Database.ExecuteSqlCommand("INSERT INTO BeatmapSetInfo SELECT ID, DeletePending, Hash, BeatmapMetadataID, OnlineBeatmapSetID, Protected FROM BeatmapSetInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapSetInfo_Old"); - Database.ExecuteSqlCommand("INSERT INTO BeatmapSetFileInfo SELECT ID, BeatmapSetInfoID, FileInfoID, Filename FROM BeatmapSetFileInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE BeatmapSetFileInfo_Old"); + Database.ExecuteSqlCommand("INSERT INTO BeatmapSetFileInfo SELECT ID, BeatmapSetInfoID, FileInfoID, Filename FROM BeatmapSetFileInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapSetFileInfo_Old"); - Database.ExecuteSqlCommand("INSERT INTO RulesetInfo SELECT ID, Available, InstantiationInfo, Name FROM RulesetInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old"); + Database.ExecuteSqlCommand("INSERT INTO RulesetInfo SELECT ID, Available, InstantiationInfo, Name FROM RulesetInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old"); - Database.ExecuteSqlCommand( - "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); - Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); + Database.ExecuteSqlCommand( + "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); + Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); + + throw new Exception(); + } + catch + { + // if anything went wrong during migration just nuke the database. + Database.EnsureDeleted(); + } } catch { From 8aea6068ba4d2a3f0f923070b983ee7ebf0c9941 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 21:37:09 +0900 Subject: [PATCH 330/344] Add fallback logic for the case where previous database can't be migrated --- osu.Game/Database/OsuDbContext.cs | 12 +++++++++--- osu.Game/OsuGameBase.cs | 19 +++++++++++++------ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 0ebea94683..93d23cc1bc 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -221,19 +221,25 @@ namespace osu.Game.Database Database.ExecuteSqlCommand( "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); - - throw new Exception(); } catch { // if anything went wrong during migration just nuke the database. - Database.EnsureDeleted(); + throw new MigrationFailedException(); } } + catch (MigrationFailedException e) + { + throw; + } catch { } } } } + + public class MigrationFailedException : Exception + { + } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index be12f3ddbc..e7ad77d099 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -90,8 +90,18 @@ namespace osu.Game dependencies.Cache(this); dependencies.Cache(LocalConfig); - using (var context = contextFactory.GetContext()) - context.Migrate(); + try + { + using (var context = contextFactory.GetContext()) + context.Migrate(); + } + catch (MigrationFailedException) + { + using (var context = contextFactory.GetContext()) + context.Database.EnsureDeleted(); + using (var context = contextFactory.GetContext()) + context.Migrate(); + } dependencies.Cache(API = new APIAccess { @@ -203,10 +213,7 @@ namespace osu.Game // TODO: This is temporary until we reimplement the local FPS display. // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += val => - { - FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; - }; + fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); } From d9fd05a5afe96a1b70b833a555bd10c06509407a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 23:19:30 +0900 Subject: [PATCH 331/344] Hidden cannot be null --- osu.Game/Database/OsuDbContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 93d23cc1bc..4e663159ff 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -219,7 +219,7 @@ namespace osu.Game.Database Database.ExecuteSqlCommand("DROP TABLE RulesetInfo_Old"); Database.ExecuteSqlCommand( - "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, Hidden, LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); + "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, IFNULL(Hidden, 0), LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); } catch From 9b1ed5b3aaca927ef41ec637a07405a47e60b115 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Oct 2017 23:33:27 +0900 Subject: [PATCH 332/344] Keep trying until delete succeeds Turns out it can fail if file handles are still open. --- osu.Game/OsuGameBase.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e7ad77d099..4a80a0fa06 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -97,8 +97,18 @@ namespace osu.Game } catch (MigrationFailedException) { - using (var context = contextFactory.GetContext()) - context.Database.EnsureDeleted(); + while (true) + { + try + { + using (var context = contextFactory.GetContext()) + { + context.Database.EnsureDeleted(); + break; + } + } + catch { } + } using (var context = contextFactory.GetContext()) context.Migrate(); } From efaf98c5cf235d4c439e8ab147f1640b48533963 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 00:48:27 +0900 Subject: [PATCH 333/344] Allow recovery from a very broken database --- osu.Game/Database/OsuDbContext.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 4e663159ff..1e3d76789d 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -160,7 +160,14 @@ namespace osu.Game.Database public void Migrate() { migrateFromSqliteNet(); - Database.Migrate(); + try + { + Database.Migrate(); + } + catch + { + throw new MigrationFailedException(); + } } private void migrateFromSqliteNet() From 0e1328a30eb0ef949abec7f90a8422073607d36b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 08:01:38 +0900 Subject: [PATCH 334/344] Add maximum try count before bailing --- osu.Game/OsuGameBase.cs | 55 ++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4a80a0fa06..de9ed70ac9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -90,28 +90,7 @@ namespace osu.Game dependencies.Cache(this); dependencies.Cache(LocalConfig); - try - { - using (var context = contextFactory.GetContext()) - context.Migrate(); - } - catch (MigrationFailedException) - { - while (true) - { - try - { - using (var context = contextFactory.GetContext()) - { - context.Database.EnsureDeleted(); - break; - } - } - catch { } - } - using (var context = contextFactory.GetContext()) - context.Migrate(); - } + runMigrations(); dependencies.Cache(API = new APIAccess { @@ -183,6 +162,38 @@ namespace osu.Game FileStore.Cleanup(); } + private void runMigrations() + { + try + { + using (var context = contextFactory.GetContext()) + context.Migrate(); + } + catch (MigrationFailedException) + { + // if we failed, let's delete the database and start fresh. + // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. + int retries = 20; + while (retries-- > 0) + { + try + { + using (var context = contextFactory.GetContext()) + { + context.Database.EnsureDeleted(); + break; + } + } + catch + { + } + } + + using (var context = contextFactory.GetContext()) + context.Migrate(); + } + } + private WorkingBeatmap lastBeatmap; public void APIStateChanged(APIAccess api, APIState state) From ca780784361ccc695c05a8c53ab505f043cdc51d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 08:01:45 +0900 Subject: [PATCH 335/344] Add more logging output --- osu.Game/Database/OsuDbContext.cs | 18 +++++++++++++----- osu.Game/OsuGameBase.cs | 7 ++++++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 1e3d76789d..bfa3c0a81b 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -160,13 +160,14 @@ namespace osu.Game.Database public void Migrate() { migrateFromSqliteNet(); + try { Database.Migrate(); } - catch + catch (Exception e) { - throw new MigrationFailedException(); + throw new MigrationFailedException(e); } } @@ -186,6 +187,8 @@ namespace osu.Game.Database try { + Logger.Log("Performing migration from sqlite-net to EF...", LoggingTarget.Database, Framework.Logging.LogLevel.Important); + // we are good to perform messy migration of data!. Database.ExecuteSqlCommand("ALTER TABLE BeatmapDifficulty RENAME TO BeatmapDifficulty_Old"); Database.ExecuteSqlCommand("ALTER TABLE BeatmapMetadata RENAME TO BeatmapMetadata_Old"); @@ -228,11 +231,12 @@ namespace osu.Game.Database Database.ExecuteSqlCommand( "INSERT INTO BeatmapInfo SELECT ID, AudioLeadIn, BaseDifficultyID, BeatDivisor, BeatmapSetInfoID, Countdown, DistanceSpacing, GridSize, Hash, IFNULL(Hidden, 0), LetterboxInBreaks, MD5Hash, NULLIF(BeatmapMetadataID, 0), OnlineBeatmapID, Path, RulesetID, SpecialStyle, StackLeniency, StarDifficulty, StoredBookmarks, TimelineZoom, Version, WidescreenStoryboard FROM BeatmapInfo_Old"); Database.ExecuteSqlCommand("DROP TABLE BeatmapInfo_Old"); + + Logger.Log("Migration complete!", LoggingTarget.Database, Framework.Logging.LogLevel.Important); } - catch + catch (Exception e) { - // if anything went wrong during migration just nuke the database. - throw new MigrationFailedException(); + throw new MigrationFailedException(e); } } catch (MigrationFailedException e) @@ -248,5 +252,9 @@ namespace osu.Game.Database public class MigrationFailedException : Exception { + public MigrationFailedException(Exception exception) + : base("sqlite-net migration failed", exception) + { + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index de9ed70ac9..0d575c52d0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; using osu.Framework.Graphics.Performance; +using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Input; using osu.Game.Input.Bindings; @@ -169,8 +170,11 @@ namespace osu.Game using (var context = contextFactory.GetContext()) context.Migrate(); } - catch (MigrationFailedException) + catch (MigrationFailedException e) { + Logger.Log(e.ToString(), LoggingTarget.Database, LogLevel.Error); + Logger.Log("Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database, LogLevel.Error); + // if we failed, let's delete the database and start fresh. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. int retries = 20; @@ -181,6 +185,7 @@ namespace osu.Game using (var context = contextFactory.GetContext()) { context.Database.EnsureDeleted(); + Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); break; } } From d32059a7bac9da500ab120408e5ca1875302e8bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 09:25:54 +0900 Subject: [PATCH 336/344] Ignore include-ignore warnings for now --- osu.Game/Database/OsuDbContext.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index bfa3c0a81b..20d36f3ebe 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,6 +3,7 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Game.Beatmaps; @@ -63,8 +64,12 @@ namespace osu.Game.Database protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); - optionsBuilder.UseSqlite(connectionString); - optionsBuilder.UseLoggerFactory(logger.Value); + optionsBuilder + // this is required for the time being due to the way we are querying in places like BeatmapStore. + // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. + .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) + .UseSqlite(connectionString) + .UseLoggerFactory(logger.Value); } protected override void OnModelCreating(ModelBuilder modelBuilder) From 7f83cf6780b5fb84e0da6ea6690894438d89d4d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 10:05:54 +0900 Subject: [PATCH 337/344] Fix hiding not always working Because we are not sharing a single context, we need to use Update to attach the entity to the local context. --- osu.Game/Beatmaps/BeatmapStore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 8eac35a667..ad4c657619 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -102,6 +102,7 @@ namespace osu.Game.Beatmaps if (beatmap.Hidden) return false; beatmap.Hidden = true; + context.Update(beatmap); context.SaveChanges(); BeatmapHidden?.Invoke(beatmap); @@ -120,6 +121,7 @@ namespace osu.Game.Beatmaps if (!beatmap.Hidden) return false; beatmap.Hidden = false; + context.Update(beatmap); context.SaveChanges(); BeatmapRestored?.Invoke(beatmap); From f69fa0cf1f9ef4a70f8525be5ec05103262df683 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 10:50:00 +0900 Subject: [PATCH 338/344] Fix selection after hiding all difficulties in a set --- osu.Game/Screens/Select/BeatmapCarousel.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b8cc9782ca..582eeebd48 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select Schedule(() => { foreach (var g in newGroups) - addGroup(g); + if (g != null) addGroup(g); computeYPositions(); BeatmapsChanged?.Invoke(); @@ -101,6 +101,9 @@ namespace osu.Game.Screens.Select { var group = createGroup(beatmapSet); + if (group == null) + return; + addGroup(group); computeYPositions(); if (selectedGroup == null) @@ -124,19 +127,23 @@ namespace osu.Game.Screens.Select if (group == null) return; - var newGroup = createGroup(set); - int i = groups.IndexOf(group); groups.RemoveAt(i); - groups.Insert(i, newGroup); - if (selectedGroup == group && newGroup.BeatmapPanels.Count == 0) + var newGroup = createGroup(set); + + if (newGroup != null) + groups.Insert(i, newGroup); + + bool hadSelection = selectedGroup == group; + + if (hadSelection && newGroup == null) selectedGroup = null; Filter(null, false); //check if we can/need to maintain our current selection. - if (selectedGroup == group && newGroup.BeatmapPanels.Count > 0) + if (hadSelection && newGroup != null) { var newSelection = newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID) ?? @@ -335,6 +342,9 @@ namespace osu.Game.Screens.Select private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) { + if (beatmapSet.Beatmaps.All(b => b.Hidden)) + return null; + foreach (var b in beatmapSet.Beatmaps) { if (b.Metadata == null) From 93b2fc6dc51ff9ed665e0a4a2ad09a9421b47e0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 11:14:45 +0900 Subject: [PATCH 339/344] Fix issues with deletion Main fix is avoiding nullrefs being thrown when metadata isn't present on a beatmap (quite a common scenario). --- osu.Game/Beatmaps/BeatmapStore.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 8eac35a667..6451862824 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -66,6 +66,7 @@ namespace osu.Game.Beatmaps if (beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; + context.Update(beatmapSet); context.SaveChanges(); BeatmapSetRemoved?.Invoke(beatmapSet); @@ -84,6 +85,7 @@ namespace osu.Game.Beatmaps if (!beatmapSet.DeletePending) return false; beatmapSet.DeletePending = false; + context.Update(beatmapSet); context.SaveChanges(); BeatmapSetAdded?.Invoke(beatmapSet); @@ -137,7 +139,7 @@ namespace osu.Game.Beatmaps // metadata is M-N so we can't rely on cascades context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata)); - context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata))); + context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); From f9d5eadd05857d5c5587d93a6924fb219d23803c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 12:21:18 +0900 Subject: [PATCH 340/344] Fix TestCase failing in an infinite loop --- osu.Game/Tests/Visual/TestCaseNotificationOverlay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game/Tests/Visual/TestCaseNotificationOverlay.cs index fead5c8b24..ed331076b2 100644 --- a/osu.Game/Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game/Tests/Visual/TestCaseNotificationOverlay.cs @@ -66,13 +66,11 @@ namespace osu.Game.Tests.Visual progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); - while (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) + if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) { var p = progressingNotifications.FirstOrDefault(n => n.IsAlive && n.State == ProgressNotificationState.Queued); - if (p == null) - break; - - p.State = ProgressNotificationState.Active; + if (p != null) + p.State = ProgressNotificationState.Active; } foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) From b805174143f8313d247fd2f4d28d9f75f6c1b536 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Oct 2017 14:33:35 +0900 Subject: [PATCH 341/344] Output the inner exception to the log when possible --- osu.Game/OsuGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 0d575c52d0..520627ad4a 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -172,7 +172,7 @@ namespace osu.Game } catch (MigrationFailedException e) { - Logger.Log(e.ToString(), LoggingTarget.Database, LogLevel.Error); + Logger.Log((e.InnerException ?? e).ToString(), LoggingTarget.Database, LogLevel.Error); Logger.Log("Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database, LogLevel.Error); // if we failed, let's delete the database and start fresh. From 47213d2498e81440f6e2d24bd15d6c37933a4eee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Oct 2017 00:15:02 +0900 Subject: [PATCH 342/344] Rely on storage.DeleteDatabase for guaranteed database deletion Relies on https://github.com/ppy/osu-framework/pull/1100 being merged for most effectiveness. --- osu.Game/Database/DatabaseContextFactory.cs | 10 +++++++++- osu.Game/OsuGameBase.cs | 18 ++---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 359188b4e2..6154016083 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -9,11 +9,19 @@ namespace osu.Game.Database { private readonly GameHost host; + private const string database_name = @"client"; + public DatabaseContextFactory(GameHost host) { this.host = host; } - public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(@"client")); + public OsuDbContext GetContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)); + + public void ResetDatabase() + { + // todo: we probably want to make sure there are no active contexts before performing this operation. + host.Storage.DeleteDatabase(database_name); + } } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 520627ad4a..50639e3427 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -177,22 +177,8 @@ namespace osu.Game // if we failed, let's delete the database and start fresh. // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. - int retries = 20; - while (retries-- > 0) - { - try - { - using (var context = contextFactory.GetContext()) - { - context.Database.EnsureDeleted(); - Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); - break; - } - } - catch - { - } - } + contextFactory.ResetDatabase(); + Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); using (var context = contextFactory.GetContext()) context.Migrate(); From a9657d2142d3537ed0d38f315fa11c6852b91603 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Sun, 22 Oct 2017 00:55:32 +1030 Subject: [PATCH 343/344] Change beatmap import to use OpenTK's FileDrop event --- osu.Desktop/OsuGameDesktop.cs | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index f4fb10a496..1e4bf3119d 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -7,13 +7,13 @@ using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; -using System.Windows.Forms; using Microsoft.Win32; using osu.Desktop.Overlays; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game; using osu.Game.Screens.Menu; +using OpenTK.Input; namespace osu.Desktop { @@ -105,16 +105,13 @@ namespace osu.Desktop desktopWindow.Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); desktopWindow.Title = Name; - desktopWindow.DragEnter += dragEnter; - desktopWindow.DragDrop += dragDrop; + desktopWindow.FileDrop += fileDrop; } } - private void dragDrop(DragEventArgs e) + private void fileDrop(object sender, FileDropEventArgs e) { - // this method will only be executed if e.Effect in dragEnter gets set to something other that None. - var dropData = (object[])e.Data.GetData(DataFormats.FileDrop); - var filePaths = dropData.Select(f => f.ToString()).ToArray(); + var filePaths = new [] { e.FileName }; if (filePaths.All(f => Path.GetExtension(f) == @".osz")) Task.Run(() => BeatmapManager.Import(filePaths)); @@ -127,16 +124,5 @@ namespace osu.Desktop } private static readonly string[] allowed_extensions = { @".osz", @".osr" }; - - private void dragEnter(DragEventArgs e) - { - // dragDrop will only be executed if e.Effect gets set to something other that None in this method. - bool isFile = e.Data.GetDataPresent(DataFormats.FileDrop); - if (isFile) - { - var paths = ((object[])e.Data.GetData(DataFormats.FileDrop)).Select(f => f.ToString()).ToArray(); - e.Effect = allowed_extensions.Any(ext => paths.All(p => p.EndsWith(ext))) ? DragDropEffects.Copy : DragDropEffects.None; - } - } } } From 30307de498c56632b8881914f0f8ac18183f66f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Oct 2017 11:58:40 +0900 Subject: [PATCH 344/344] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index dbcfa5c244..383a8da7bc 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit dbcfa5c244555e7901dac7d94eab53b3b04d17e6 +Subproject commit 383a8da7bc45af498288b4b72c72a048a0996e74

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