diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index d47ed48e99..de3acf7a71 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Configuration; using osu.Framework.Platform; +using osu.Game.Screens.Select; namespace osu.Game.Configuration { @@ -34,6 +35,8 @@ namespace osu.Game.Configuration Set(OsuConfig.MenuParallax, true); + Set(OsuConfig.BeatmapDetailTab, BeatmapDetailTab.Details); + Set(OsuConfig.ShowInterface, true); Set(OsuConfig.KeyOverlay, false); //todo: implement all settings below this line (remove the Disabled set when doing so). @@ -316,6 +319,7 @@ namespace osu.Game.Configuration MenuMusic, MenuVoice, MenuParallax, + BeatmapDetailTab, RawInput, AbsoluteToOsuWindow, ConfineMouse, diff --git a/osu.Game/Database/BeatmapMetrics.cs b/osu.Game/Database/BeatmapMetrics.cs index 91320110d0..25de0f0a8d 100644 --- a/osu.Game/Database/BeatmapMetrics.cs +++ b/osu.Game/Database/BeatmapMetrics.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using Newtonsoft.Json; namespace osu.Game.Database { @@ -18,11 +19,13 @@ namespace osu.Game.Database /// /// Points of failure on a relative time scale (usually 0..100). /// + [JsonProperty(@"fail")] public IEnumerable Fails { get; set; } /// /// Points of retry on a relative time scale (usually 0..100). /// + [JsonProperty(@"exit")] public IEnumerable Retries { get; set; } } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs new file mode 100644 index 0000000000..43e14e59de --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.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 Newtonsoft.Json; +using osu.Game.Database; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapDetailsRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + + private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}"; + + public GetBeatmapDetailsRequest(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + protected override string Target => $@"beatmaps/{lookupString}"; + } + + public class GetBeatmapDeatilsResponse : BeatmapMetrics + { + //the online API returns some metrics as a nested object. + [JsonProperty(@"failtimes")] + private BeatmapMetrics failTimes + { + set + { + Fails = value.Fails; + Retries = value.Retries; + } + } + + //and other metrics in the beatmap set. + [JsonProperty(@"beatmapset")] + private BeatmapMetrics beatmapSet + { + set + { + Ratings = value.Ratings; + } + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index ae117254fa..cc22cca8bf 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select { beatmap = value; Leaderboard.Beatmap = beatmap?.BeatmapInfo; - Details.Beatmap = beatmap?.Beatmap.BeatmapInfo; + Details.Beatmap = beatmap?.BeatmapInfo; } } @@ -66,16 +66,16 @@ namespace osu.Game.Screens.Select { Details = new BeatmapDetails { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(5), + RelativeSizeAxes = Axes.X, + Masking = true, + Height = 352, Alpha = 0, }, Leaderboard = new Leaderboard { RelativeSizeAxes = Axes.Both, - } }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index 48a46f0b90..51ec6f7707 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -4,10 +4,12 @@ using System; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -21,15 +23,22 @@ namespace osu.Game.Screens.Select public Action OnFilter; //passed the selected tab and if mods is checked + private Bindable selectedTab; + private void invokeOnFilter() { OnFilter?.Invoke(tabs.Current, modsCheckbox.Current); } [BackgroundDependencyLoader] - private void load(OsuColour colour) + private void load(OsuColour colour, OsuConfigManager config) { modsCheckbox.AccentColour = tabs.AccentColour = colour.YellowLight; + + selectedTab = config.GetBindable(OsuConfig.BeatmapDetailTab); + + tabs.Current.BindTo(selectedTab); + tabs.Current.TriggerChange(); } public BeatmapDetailAreaTabControl() @@ -62,8 +71,6 @@ namespace osu.Game.Screens.Select tabs.Current.ValueChanged += item => invokeOnFilter(); modsCheckbox.Current.ValueChanged += item => invokeOnFilter(); - - tabs.Current.Value = BeatmapDetailTab.Global; } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index a0d15101e0..63fdfe3717 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -14,6 +14,9 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Globalization; using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Framework.Threading; namespace osu.Game.Screens.Select { @@ -39,60 +42,108 @@ namespace osu.Game.Screens.Select private readonly BarGraph retryGraph; private readonly BarGraph failGraph; + private ScheduledDelegate pendingBeatmapSwitch; private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { - get - { - return beatmap; - } + get { return beatmap; } set { beatmap = value; - if (beatmap == null) return; - description.Text = beatmap.Version; - source.Text = beatmap.Metadata.Source; - tags.Text = beatmap.Metadata.Tags; - - circleSize.Value = beatmap.Difficulty.CircleSize; - drainRate.Value = beatmap.Difficulty.DrainRate; - overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty; - approachRate.Value = beatmap.Difficulty.ApproachRate; - stars.Value = (float)beatmap.StarDifficulty; - - if (beatmap.Metrics?.Ratings.Any() ?? false) - { - var ratings = beatmap.Metrics.Ratings.ToList(); - ratingsContainer.Show(); - - negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString(); - positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString(); - ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum(); - - ratingsGraph.Values = ratings.Select(rating => (float)rating); - } - else - ratingsContainer.Hide(); - - if ((beatmap.Metrics?.Retries.Any() ?? false) && beatmap.Metrics.Fails.Any()) - { - var retries = beatmap.Metrics.Retries; - var fails = beatmap.Metrics.Fails; - retryFailContainer.Show(); - - float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); - failGraph.MaxValue = maxValue; - retryGraph.MaxValue = maxValue; - - failGraph.Values = fails.Select(fail => (float)fail); - retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); - } - else - retryFailContainer.Hide(); + pendingBeatmapSwitch?.Cancel(); + pendingBeatmapSwitch = Schedule(updateStats); } } + private void updateStats() + { + if (beatmap == null) return; + + description.Text = beatmap.Version; + source.Text = beatmap.Metadata.Source; + tags.Text = beatmap.Metadata.Tags; + + circleSize.Value = beatmap.Difficulty.CircleSize; + drainRate.Value = beatmap.Difficulty.DrainRate; + overallDifficulty.Value = beatmap.Difficulty.OverallDifficulty; + approachRate.Value = beatmap.Difficulty.ApproachRate; + stars.Value = (float)beatmap.StarDifficulty; + + var requestedBeatmap = beatmap; + if (requestedBeatmap.Metrics == null) + { + var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); + lookup.Success += res => + { + if (beatmap != requestedBeatmap) + //the beatmap has been changed since we started the lookup. + return; + + requestedBeatmap.Metrics = res; + Schedule(() => updateMetrics(res)); + }; + lookup.Failure += e => updateMetrics(null); + + api.Queue(lookup); + } + + updateMetrics(requestedBeatmap.Metrics, false); + } + + /// + /// Update displayed metrics. + /// + /// New metrics to overwrite the existing display. Can be null. + /// Whether to hide the display on null or empty metrics. If false, we will dim as if waiting for further updates. + private void updateMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + { + var hasRatings = metrics?.Ratings.Any() ?? false; + var hasRetriesFails = (metrics?.Retries.Any() ?? false) && metrics.Fails.Any(); + + if (hasRatings) + { + var ratings = metrics.Ratings.ToList(); + ratingsContainer.Show(); + + negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2).Sum().ToString(); + positiveRatings.Text = ratings.GetRange(ratings.Count / 2, ratings.Count / 2).Sum().ToString(); + ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2).Sum() / ratings.Sum(); + + ratingsGraph.Values = ratings.Select(rating => (float)rating); + + ratingsContainer.FadeColour(Color4.White, 500, EasingTypes.Out); + } + else if (failOnMissing) + ratingsGraph.Values = new float[10]; + else + ratingsContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out); + + if (hasRetriesFails) + { + var retries = metrics.Retries; + var fails = metrics.Fails; + retryFailContainer.Show(); + + float maxValue = fails.Zip(retries, (fail, retry) => fail + retry).Max(); + failGraph.MaxValue = maxValue; + retryGraph.MaxValue = maxValue; + + failGraph.Values = fails.Select(fail => (float)fail); + retryGraph.Values = retries.Zip(fails, (retry, fail) => retry + MathHelper.Clamp(fail, 0, maxValue)); + + retryFailContainer.FadeColour(Color4.White, 500, EasingTypes.Out); + } + else if (failOnMissing) + { + failGraph.Values = new float[100]; + retryGraph.Values = new float[100]; + } + else + retryFailContainer.FadeColour(Color4.Gray, 500, EasingTypes.Out); + } + public BeatmapDetails() { Children = new Drawable[] @@ -113,7 +164,6 @@ namespace osu.Game.Screens.Select Direction = FillDirection.Vertical, LayoutDuration = 200, LayoutEasing = EasingTypes.OutQuint, - Padding = new MarginPadding(10) { Top = 25 }, Children = new [] { description = new MetadataSegment("Description"), @@ -148,8 +198,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0,10), - Padding = new MarginPadding(15) { Top = 25 }, + Spacing = new Vector2(0,5), + Padding = new MarginPadding(10), Children = new [] { circleSize = new DifficultyRow("Circle Size", 7), @@ -252,7 +302,7 @@ namespace osu.Game.Screens.Select new Container { RelativeSizeAxes = Axes.X, - Size = new Vector2(1/0.6f, 50), + Size = new Vector2(1 / 0.6f, 50), Children = new[] { retryGraph = new BarGraph @@ -272,9 +322,13 @@ namespace osu.Game.Screens.Select }; } + private APIAccess api; + [BackgroundDependencyLoader] - private void load(OsuColour colour) + private void load(OsuColour colour, APIAccess api) { + this.api = api; + description.AccentColour = colour.GrayB; source.AccentColour = colour.GrayB; tags.AccentColour = colour.YellowLight; @@ -308,7 +362,7 @@ namespace osu.Game.Screens.Select { difficultyValue = value; bar.Length = value / maxValue; - valueText.Text = value.ToString(CultureInfo.InvariantCulture); + valueText.Text = value.ToString("N1", CultureInfo.CurrentCulture); } } @@ -431,4 +485,4 @@ namespace osu.Game.Screens.Select } } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 2c51429d4c..02a412685c 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using System; using osu.Framework.Allocation; +using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; @@ -93,13 +94,18 @@ namespace osu.Game.Screens.Select.Leaderboards private BeatmapInfo beatmap; + private ScheduledDelegate pendingBeatmapSwitch; + public BeatmapInfo Beatmap { get { return beatmap; } set { beatmap = value; - Schedule(updateScores); + Scores = null; + + pendingBeatmapSwitch?.Cancel(); + pendingBeatmapSwitch = Schedule(updateScores); } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c9a3b08713..c32c5112cf 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -179,6 +179,7 @@ +