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..a3b99331c6 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -0,0 +1,61 @@ +// 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 Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Database; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapDeatilsRequest : APIRequest + { + private readonly BeatmapInfo beatmap; + + private string lookupString; + + public GetBeatmapDeatilsRequest(BeatmapInfo beatmap) + { + this.beatmap = beatmap; + } + + protected override WebRequest CreateWebRequest() + { + if (beatmap.OnlineBeatmapID > 0) + lookupString = beatmap.OnlineBeatmapID.ToString(); + else + lookupString = $@"lookup?checksum={beatmap.Hash}&filename={beatmap.Path}"; + + var req = base.CreateWebRequest(); + + return req; + } + + 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 + { + this.Fails = value.Fails; + this.Retries = value.Retries; + } + } + + //and other metrics in the beatmap set. + [JsonProperty(@"beatmapset")] + private BeatmapMetrics beatmapSet + { + set + { + this.Ratings = value.Ratings; + } + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 26ec74e299..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; } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 462f741dae..c5fb5eafaa 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -14,6 +14,10 @@ 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; +using System; namespace osu.Game.Screens.Select { @@ -39,61 +43,101 @@ 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() + { + 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 GetBeatmapDeatilsRequest(requestedBeatmap); + lookup.Success += res => + { + if (beatmap != requestedBeatmap) + //the beatmap has been changed since we started the lookup. + return; + + requestedBeatmap.Metrics = res; + Schedule(() => updateMetrics(res, true)); + }; + lookup.Failure += e => updateMetrics(null, true); + + api.Queue(lookup); + } + + updateMetrics(requestedBeatmap.Metrics, false); + } + + 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[] @@ -272,9 +316,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; 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 @@ +