From 0df5432f5ebe25ea320f181c3183d3edb483b526 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Wed, 22 Nov 2017 21:45:18 +0100 Subject: [PATCH 001/239] removed line that set metadata per beatmap to null --- osu.Game/Beatmaps/BeatmapManager.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f461317ce1..eb10b23c6f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -515,9 +515,6 @@ namespace osu.Game.Beatmaps if (existing == null) { - // 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. From fe559f4b62245d66e2f6b6b0ae9a4ffa2f60fb7b Mon Sep 17 00:00:00 2001 From: naoey Date: Mon, 20 Nov 2017 10:36:26 +0530 Subject: [PATCH 002/239] Add respective query params to GetScoreRequest based on selected tab. --- .../Online/API/Requests/GetScoresRequest.cs | 25 +++++++++++++++-- osu.Game/Screens/Select/BeatmapDetailArea.cs | 1 + .../Select/Leaderboards/Leaderboard.cs | 28 ++++++++++++++++++- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3777e10a31..a52db4496a 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -10,19 +10,22 @@ using osu.Game.Rulesets; using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Select.Leaderboards; namespace osu.Game.Online.API.Requests { public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; + private readonly LeaderboardScope scope; - public GetScoresRequest(BeatmapInfo beatmap) + public GetScoresRequest(BeatmapInfo beatmap, LeaderboardScope scope = LeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); this.beatmap = beatmap; + this.scope = scope; Success += onSuccess; } @@ -33,7 +36,25 @@ namespace osu.Game.Online.API.Requests score.ApplyBeatmap(beatmap); } - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; + private string mapScopeToQuery() + { + switch(scope) + { + case LeaderboardScope.Global: + return @"?type=global"; + + case LeaderboardScope.Friends: + return @"?type=friend"; + + case LeaderboardScope.Country: + return @"?type=country"; + + default: + return String.Empty; + } + } + + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{mapScopeToQuery()}"; } public class GetScoresResponse diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index a676516300..790a8421a2 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -52,6 +52,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); + Leaderboard.Scope = (LeaderboardScope) tab - 1; Leaderboard.Show(); break; } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index d896da5319..20ab09e83e 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -74,6 +74,19 @@ namespace osu.Game.Screens.Select.Leaderboards } } + private LeaderboardScope scope = LeaderboardScope.Global; + public LeaderboardScope Scope + { + get { return scope; } + set + { + if (value == scope) return; + + scope = value; + updateScores(); + } + } + public Leaderboard() { Children = new Drawable[] @@ -120,6 +133,11 @@ namespace osu.Game.Screens.Select.Leaderboards { if (!IsLoaded) return; + if (Scope == LeaderboardScope.Local) + { + // TODO: get local scores from wherever here. + } + Scores = null; getScoresRequest?.Cancel(); @@ -127,7 +145,7 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap); + getScoresRequest = new GetScoresRequest(Beatmap, Scope); getScoresRequest.Success += r => { Scores = r.Scores; @@ -165,4 +183,12 @@ namespace osu.Game.Screens.Select.Leaderboards } } } + + public enum LeaderboardScope + { + Local, + Country, + Global, + Friends, + } } From a58bd72c6ede87d84a2c2d2c22d05e69d34e37ad Mon Sep 17 00:00:00 2001 From: naoey Date: Mon, 20 Nov 2017 17:07:41 +0530 Subject: [PATCH 003/239] Add placeholder when there are no scores. --- .../Select/Leaderboards/Leaderboard.cs | 56 ++++++++++++++++--- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 20ab09e83e..b5cd729739 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -18,13 +18,18 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Screens.Select.Leaderboards { public class Leaderboard : Container { + private const double fade_duration = 200; + private readonly ScrollContainer scrollContainer; private FillFlowContainer scrollFlow; + private Container placeholderContainer; public Action ScoreSelected; @@ -40,13 +45,19 @@ namespace osu.Game.Screens.Select.Leaderboards scores = value; getScoresRequest?.Cancel(); - scrollFlow?.FadeOut(200); - scrollFlow?.Expire(); + placeholderContainer.FadeOut(fade_duration); + scrollFlow?.FadeOut(fade_duration).Expire(); scrollFlow = null; if (scores == null) return; + if (scores.Count() == 0) + { + placeholderContainer.FadeIn(fade_duration); + return; + } + // schedule because we may not be loaded yet (LoadComponentAsync complains). Schedule(() => { @@ -74,7 +85,7 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private LeaderboardScope scope = LeaderboardScope.Global; + private LeaderboardScope scope; public LeaderboardScope Scope { get { return scope; } @@ -96,7 +107,36 @@ namespace osu.Game.Screens.Select.Leaderboards RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, }, - loading = new LoadingAnimation() + loading = new LoadingAnimation(), + placeholderContainer = new Container + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_exclamation_circle, + Size = new Vector2(26), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Text = @"No records yet!", + TextSize = 22, + }, + } + }, + }, + }, }; } @@ -133,14 +173,16 @@ namespace osu.Game.Screens.Select.Leaderboards { if (!IsLoaded) return; + Scores = null; + getScoresRequest?.Cancel(); + if (Scope == LeaderboardScope.Local) { // TODO: get local scores from wherever here. + Scores = Enumerable.Empty(); + return; } - Scores = null; - getScoresRequest?.Cancel(); - if (api == null || Beatmap?.OnlineBeatmapID == null) return; loading.Show(); From 487483eaddf0833aa39c3f4c305fcb75404a78bb Mon Sep 17 00:00:00 2001 From: naoey Date: Mon, 20 Nov 2017 18:53:50 +0530 Subject: [PATCH 004/239] Move loader hiding to a better place. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index b5cd729739..2567110ef6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -52,6 +52,8 @@ namespace osu.Game.Screens.Select.Leaderboards if (scores == null) return; + loading.Hide(); + if (scores.Count() == 0) { placeholderContainer.FadeIn(fade_duration); @@ -191,7 +193,6 @@ namespace osu.Game.Screens.Select.Leaderboards getScoresRequest.Success += r => { Scores = r.Scores; - loading.Hide(); }; api.Queue(getScoresRequest); } From 096e98b5d3aaeebb67c528b65c1aad44756d0597 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 21 Nov 2017 19:44:38 +0530 Subject: [PATCH 005/239] Add game mode query to request. - Also update scores when game mode is changed --- .../Online/API/Requests/GetScoresRequest.cs | 44 ++++++++++++++++--- .../Select/Leaderboards/Leaderboard.cs | 14 +++--- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index a52db4496a..37b3cc55f1 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -11,6 +11,7 @@ using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; +using System.Collections.Specialized; namespace osu.Game.Online.API.Requests { @@ -18,14 +19,26 @@ namespace osu.Game.Online.API.Requests { private readonly BeatmapInfo beatmap; private readonly LeaderboardScope scope; + private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap, LeaderboardScope scope = LeaderboardScope.Global) + public GetScoresRequest(BeatmapInfo beatmap) + { + if (!beatmap.OnlineBeatmapID.HasValue) + throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + + this.beatmap = beatmap; + + Success += onSuccess; + } + + public GetScoresRequest(BeatmapInfo beatmap, LeaderboardScope scope, RulesetInfo ruleset) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); this.beatmap = beatmap; this.scope = scope; + this.ruleset = ruleset; Success += onSuccess; } @@ -41,20 +54,41 @@ namespace osu.Game.Online.API.Requests switch(scope) { case LeaderboardScope.Global: - return @"?type=global"; + return @"type=global"; case LeaderboardScope.Friends: - return @"?type=friend"; + return @"type=friend"; case LeaderboardScope.Country: - return @"?type=country"; + return @"type=country"; default: return String.Empty; } } - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{mapScopeToQuery()}"; + private string mapRulesetToQuery() + { + switch(ruleset.Name) + { + case @"osu!": + return @"mode=osu"; + + case @"osu!taiko": + return @"mode=taiko"; + + case @"osu!catch": + return @"mode=catch"; + + case @"osu!mania": + return @"mode=mania"; + + default: + return String.Empty; + } + } + + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores?{mapScopeToQuery()}&{mapRulesetToQuery()}"; } public class GetScoresResponse diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 2567110ef6..3bc520e8e6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -146,6 +146,8 @@ namespace osu.Game.Screens.Select.Leaderboards private BeatmapInfo beatmap; + private OsuGame osuGame; + private ScheduledDelegate pendingBeatmapSwitch; public BeatmapInfo Beatmap @@ -164,9 +166,12 @@ namespace osu.Game.Screens.Select.Leaderboards } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api) + private void load(APIAccess api, OsuGame osuGame) { this.api = api; + this.osuGame = osuGame; + + osuGame.Ruleset.ValueChanged += r => updateScores(); } private GetScoresRequest getScoresRequest; @@ -176,7 +181,8 @@ namespace osu.Game.Screens.Select.Leaderboards if (!IsLoaded) return; Scores = null; - getScoresRequest?.Cancel(); + + if (api == null || Beatmap?.OnlineBeatmapID == null) return; if (Scope == LeaderboardScope.Local) { @@ -185,11 +191,9 @@ namespace osu.Game.Screens.Select.Leaderboards return; } - if (api == null || Beatmap?.OnlineBeatmapID == null) return; - loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap, Scope); + getScoresRequest = new GetScoresRequest(Beatmap, Scope, osuGame.Ruleset.Value); getScoresRequest.Success += r => { Scores = r.Scores; From b6de1ce5b649011c6d60d8050215cfcc0a0c7517 Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 22 Nov 2017 09:56:01 +0530 Subject: [PATCH 006/239] Handle query params better. --- .../Online/API/Requests/GetScoresRequest.cs | 58 +++++++++++-------- .../Select/Leaderboards/Leaderboard.cs | 2 +- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 37b3cc55f1..534a209609 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using System.Collections.Specialized; +using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests { @@ -31,7 +32,7 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - public GetScoresRequest(BeatmapInfo beatmap, LeaderboardScope scope, RulesetInfo ruleset) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); @@ -49,46 +50,53 @@ namespace osu.Game.Online.API.Requests score.ApplyBeatmap(beatmap); } - private string mapScopeToQuery() + protected override WebRequest CreateWebRequest() { + var req = base.CreateWebRequest(); + switch(scope) { + default: case LeaderboardScope.Global: - return @"type=global"; + req.AddParameter(@"type", @"global"); + break; case LeaderboardScope.Friends: - return @"type=friend"; + req.AddParameter(@"type", @"friend"); + break; case LeaderboardScope.Country: - return @"type=country"; - - default: - return String.Empty; + req.AddParameter(@"type", @"country"); + break; } - } - private string mapRulesetToQuery() - { - switch(ruleset.Name) + if (ruleset != null) { - case @"osu!": - return @"mode=osu"; + switch (ruleset.Name) + { + default: + case @"osu!": + req.AddParameter(@"mode", @"osu"); + break; - case @"osu!taiko": - return @"mode=taiko"; - - case @"osu!catch": - return @"mode=catch"; - - case @"osu!mania": - return @"mode=mania"; + case @"osu!taiko": + req.AddParameter(@"mode", @"taiko"); + break; - default: - return String.Empty; + case @"osu!catch": + req.AddParameter(@"mode", @"catch"); + break; + + case @"osu!mania": + req.AddParameter(@"mode", @"mania"); + break; + } } + + return req; } - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores?{mapScopeToQuery()}&{mapRulesetToQuery()}"; + protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; } public class GetScoresResponse diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3bc520e8e6..3b7e30dafe 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap, Scope, osuGame.Ruleset.Value); + getScoresRequest = new GetScoresRequest(Beatmap, osuGame.Ruleset.Value, Scope); getScoresRequest.Success += r => { Scores = r.Scores; From e3a230320ae1aa3b1f2279b539d0e97b416d48fc Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 23 Nov 2017 19:46:58 +0100 Subject: [PATCH 007/239] compare metdata and remove duplicate from beatmap to prevent redundant storage --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++++ osu.Game/Beatmaps/BeatmapMetadata.cs | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index eb10b23c6f..ca715b8d1e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -515,6 +515,10 @@ namespace osu.Game.Beatmaps if (existing == null) { + // Exclude beatmap-metadata if it's equal to beatmapset-metadata + if (metadata.Equals(beatmap.Metadata)) + 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. diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 89f9ebf47a..2cd8e2669f 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -66,5 +66,23 @@ namespace osu.Game.Beatmaps Source, Tags }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + public override bool Equals(object other) + { + var otherMetadata = other as BeatmapMetadata; + if (otherMetadata == null) return false; + + return (onlineBeatmapSetID?.Equals(otherMetadata.onlineBeatmapSetID) ?? false) + && (Title?.Equals(otherMetadata.Title) ?? false) + && (TitleUnicode?.Equals(otherMetadata.TitleUnicode) ?? false) + && (Artist?.Equals(otherMetadata.Artist) ?? false) + && (ArtistUnicode?.Equals(otherMetadata.ArtistUnicode) ?? false) + && (AuthorString?.Equals(otherMetadata.AuthorString) ?? false) + && (Source?.Equals(otherMetadata.Source) ?? false) + && (Tags?.Equals(otherMetadata.Tags) ?? false) + && PreviewTime.Equals(otherMetadata.PreviewTime) + && (AudioFile?.Equals(otherMetadata.AudioFile) ?? false) + && (BackgroundFile?.Equals(otherMetadata.BackgroundFile) ?? false); + } } } From c5a78e54e97f29dac7f471c5bb87de89782e0629 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 24 Nov 2017 18:40:52 +0530 Subject: [PATCH 008/239] Add a retry button for when scores request fails. --- .../Select/Leaderboards/Leaderboard.cs | 103 +++++++++++++++++- 1 file changed, 97 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3b7e30dafe..b25479704c 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -20,6 +20,8 @@ using osu.Game.Online.API.Requests; using System.Linq; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; +using osu.Framework.Logging; +using System.Net; namespace osu.Game.Screens.Select.Leaderboards { @@ -29,7 +31,8 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly ScrollContainer scrollContainer; private FillFlowContainer scrollFlow; - private Container placeholderContainer; + private Container noResultsPlaceholder; + private Container retryPlaceholder; public Action ScoreSelected; @@ -44,19 +47,20 @@ namespace osu.Game.Screens.Select.Leaderboards { scores = value; getScoresRequest?.Cancel(); + getScoresRequest = null; - placeholderContainer.FadeOut(fade_duration); + noResultsPlaceholder.FadeOut(fade_duration); scrollFlow?.FadeOut(fade_duration).Expire(); scrollFlow = null; + loading.Hide(); + if (scores == null) return; - loading.Hide(); - if (scores.Count() == 0) { - placeholderContainer.FadeIn(fade_duration); + noResultsPlaceholder.FadeIn(fade_duration); return; } @@ -110,7 +114,7 @@ namespace osu.Game.Screens.Select.Leaderboards ScrollbarVisible = false, }, loading = new LoadingAnimation(), - placeholderContainer = new Container + noResultsPlaceholder = new Container { Alpha = 0, RelativeSizeAxes = Axes.Both, @@ -139,6 +143,35 @@ namespace osu.Game.Screens.Select.Leaderboards }, }, }, + retryPlaceholder = new Container + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new RetryButton + { + Action = updateScores, + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"An error occurred!", + TextSize = 22, + }, + } + }, + }, + }, }; } @@ -180,6 +213,8 @@ namespace osu.Game.Screens.Select.Leaderboards { if (!IsLoaded) return; + retryPlaceholder.FadeOut(fade_duration); + Scores = null; if (api == null || Beatmap?.OnlineBeatmapID == null) return; @@ -198,6 +233,17 @@ namespace osu.Game.Screens.Select.Leaderboards { Scores = r.Scores; }; + getScoresRequest.Failure += e => + { + // TODO: check why failure is repeatedly invoked even on successful requests + if (e is WebException) + { + Scores = null; + retryPlaceholder.FadeIn(fade_duration); + Logger.Error(e, @"Couldn't fetch beatmap scores!"); + } + }; + api.Queue(getScoresRequest); } @@ -229,6 +275,51 @@ namespace osu.Game.Screens.Select.Leaderboards } } } + + private class RetryButton : ClickableContainer + { + private SpriteIcon icon; + + public RetryButton() + { + Height = 26; + Width = 26; + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_refresh, + Size = new Vector2(26), + } + }; + } + + protected override bool OnHover(Framework.Input.InputState state) + { + icon.ScaleTo(1.4f, 400, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(Framework.Input.InputState state) + { + icon.ScaleTo(1f, 400, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnMouseDown(Framework.Input.InputState state, Framework.Input.MouseDownEventArgs args) + { + icon.ScaleTo(0.8f, 200, Easing.InElastic); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(Framework.Input.InputState state, Framework.Input.MouseUpEventArgs args) + { + icon.ScaleTo(1.2f, 400, Easing.OutElastic).Then().ScaleTo(1f, 400, Easing.OutElastic); + return base.OnMouseUp(state, args); + } + } } public enum LeaderboardScope From 5da1466e284b584c636a5cf6f2c00c8280d04e3b Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Nov 2017 15:05:59 +0100 Subject: [PATCH 009/239] requested changes use IEquatable instead of overriding Equals and `==` operator for primitive types. --- osu.Game/Beatmaps/BeatmapMetadata.cs | 31 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 2cd8e2669f..22a64820bc 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; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -9,7 +10,7 @@ using osu.Game.Users; namespace osu.Game.Beatmaps { - public class BeatmapMetadata + public class BeatmapMetadata : IEquatable { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -67,22 +68,22 @@ namespace osu.Game.Beatmaps Tags }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override bool Equals(object other) + public bool Equals(BeatmapMetadata other) { - var otherMetadata = other as BeatmapMetadata; - if (otherMetadata == null) return false; + if (other == null) + return false; - return (onlineBeatmapSetID?.Equals(otherMetadata.onlineBeatmapSetID) ?? false) - && (Title?.Equals(otherMetadata.Title) ?? false) - && (TitleUnicode?.Equals(otherMetadata.TitleUnicode) ?? false) - && (Artist?.Equals(otherMetadata.Artist) ?? false) - && (ArtistUnicode?.Equals(otherMetadata.ArtistUnicode) ?? false) - && (AuthorString?.Equals(otherMetadata.AuthorString) ?? false) - && (Source?.Equals(otherMetadata.Source) ?? false) - && (Tags?.Equals(otherMetadata.Tags) ?? false) - && PreviewTime.Equals(otherMetadata.PreviewTime) - && (AudioFile?.Equals(otherMetadata.AudioFile) ?? false) - && (BackgroundFile?.Equals(otherMetadata.BackgroundFile) ?? false); + return onlineBeatmapSetID == other.onlineBeatmapSetID + && (Title?.Equals(other.Title) ?? false) + && (TitleUnicode?.Equals(other.TitleUnicode) ?? false) + && (Artist?.Equals(other.Artist) ?? false) + && (ArtistUnicode?.Equals(other.ArtistUnicode) ?? false) + && (AuthorString?.Equals(other.AuthorString) ?? false) + && (Source?.Equals(other.Source) ?? false) + && (Tags?.Equals(other.Tags) ?? false) + && PreviewTime == other.PreviewTime + && (AudioFile?.Equals(other.AudioFile) ?? false) + && (BackgroundFile?.Equals(other.BackgroundFile) ?? false); } } } From 0b3f75505ef530a9960fec3f816b7f40cbaaf474 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Nov 2017 20:59:03 +0530 Subject: [PATCH 010/239] Don't break VisualTests and add a real beatmap step. --- osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 65 ++++++++++++++++--- .../Select/Leaderboards/Leaderboard.cs | 6 +- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 9d6fb3a4ec..ad4aa63aa8 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -6,15 +6,43 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; +using osu.Framework.Allocation; using OpenTK; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect leaderboard")] internal class TestCaseLeaderboard : OsuTestCase { + private RulesetStore rulesets; + private readonly Leaderboard leaderboard; + public TestCaseLeaderboard() + { + Add(leaderboard = new Leaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = LeaderboardScope.Global, + }); + + AddStep(@"New Scores", newScores); + AddStep(@"Empty Scores", () => leaderboard.Scores = Enumerable.Empty()); + AddStep(@"Real beatmap", realBeatmap); + newScores(); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + this.rulesets = rulesets; + } + private void newScores() { var scores = new[] @@ -204,17 +232,36 @@ namespace osu.Game.Tests.Visual leaderboard.Scores = scores; } - public TestCaseLeaderboard() + private void realBeatmap() { - Add(leaderboard = new Leaderboard + leaderboard.Beatmap = new BeatmapInfo { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); - - AddStep(@"New Scores", newScores); - newScores(); + StarDifficulty = 1.36, + Version = @"BASIC", + OnlineBeatmapID = 1113057, + Ruleset = rulesets.GetRuleset(0), + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 4, + DrainRate = 6.5f, + OverallDifficulty = 6.5f, + ApproachRate = 5, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 115000, + CircleCount = 265, + SliderCount = 71, + PlayCount = 47906, + PassCount = 19899, + }, + Metrics = new BeatmapMetrics + { + Ratings = Enumerable.Range(0, 11), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + }, + }; } } } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index b25479704c..824a54d372 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Select.Leaderboards noResultsPlaceholder.FadeOut(fade_duration); scrollFlow?.FadeOut(fade_duration).Expire(); + scrollContainer.Clear(true); // scores stick around in scrollFlow in VisualTests without this for some reason scrollFlow = null; loading.Hide(); @@ -204,7 +205,8 @@ namespace osu.Game.Screens.Select.Leaderboards this.api = api; this.osuGame = osuGame; - osuGame.Ruleset.ValueChanged += r => updateScores(); + if (osuGame != null) + osuGame.Ruleset.ValueChanged += r => updateScores(); } private GetScoresRequest getScoresRequest; @@ -228,7 +230,7 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap, osuGame.Ruleset.Value, Scope); + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value, Scope); getScoresRequest.Success += r => { Scores = r.Scores; From ae9ce2f122fa29a65dee21db065a532201141f7e Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Nov 2017 21:23:36 +0530 Subject: [PATCH 011/239] Unbind ruleset event from leaderboard. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 824a54d372..3bea1d4bfd 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Logging; using System.Net; +using osu.Game.Rulesets; namespace osu.Game.Screens.Select.Leaderboards { @@ -206,11 +207,21 @@ namespace osu.Game.Screens.Select.Leaderboards this.osuGame = osuGame; if (osuGame != null) - osuGame.Ruleset.ValueChanged += r => updateScores(); + osuGame.Ruleset.ValueChanged += handleRulesetChange; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (osuGame != null) + osuGame.Ruleset.ValueChanged -= handleRulesetChange; } private GetScoresRequest getScoresRequest; + private void handleRulesetChange(RulesetInfo ruleset) => updateScores(); + private void updateScores() { if (!IsLoaded) return; From f4f732ca4381768d5f54508b90fe06683f2bad0c Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Nov 2017 11:19:42 +0530 Subject: [PATCH 012/239] Remove unnecessary null check and tweak transform a bit. --- .../Online/API/Requests/GetScoresRequest.cs | 31 +++++++++---------- .../Select/Leaderboards/Leaderboard.cs | 8 ++--- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 534a209609..b9db836898 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -70,27 +70,24 @@ namespace osu.Game.Online.API.Requests break; } - if (ruleset != null) + switch (ruleset?.Name) { - switch (ruleset.Name) - { - default: - case @"osu!": - req.AddParameter(@"mode", @"osu"); - break; + default: + case @"osu!": + req.AddParameter(@"mode", @"osu"); + break; - case @"osu!taiko": - req.AddParameter(@"mode", @"taiko"); - break; + case @"osu!taiko": + req.AddParameter(@"mode", @"taiko"); + break; - case @"osu!catch": - req.AddParameter(@"mode", @"catch"); - break; + case @"osu!catch": + req.AddParameter(@"mode", @"catch"); + break; - case @"osu!mania": - req.AddParameter(@"mode", @"mania"); - break; - } + case @"osu!mania": + req.AddParameter(@"mode", @"mania"); + break; } return req; diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3bea1d4bfd..ef6ba01393 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -232,6 +232,8 @@ namespace osu.Game.Screens.Select.Leaderboards if (api == null || Beatmap?.OnlineBeatmapID == null) return; + loading.Show(); + if (Scope == LeaderboardScope.Local) { // TODO: get local scores from wherever here. @@ -239,8 +241,6 @@ namespace osu.Game.Screens.Select.Leaderboards return; } - loading.Show(); - getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value, Scope); getScoresRequest.Success += r => { @@ -323,13 +323,13 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool OnMouseDown(Framework.Input.InputState state, Framework.Input.MouseDownEventArgs args) { - icon.ScaleTo(0.8f, 200, Easing.InElastic); + icon.ScaleTo(0.8f, 800, Easing.InElastic); return base.OnMouseDown(state, args); } protected override bool OnMouseUp(Framework.Input.InputState state, Framework.Input.MouseUpEventArgs args) { - icon.ScaleTo(1.2f, 400, Easing.OutElastic).Then().ScaleTo(1f, 400, Easing.OutElastic); + icon.ScaleTo(1.2f, 800, Easing.OutElastic).Then().ScaleTo(1f, 800, Easing.OutElastic); return base.OnMouseUp(state, args); } } From b261d32588887474cd439200a1d371523b680e70 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Nov 2017 12:25:48 +0530 Subject: [PATCH 013/239] Put retry button in a BeatSyncedContainer and change error message. --- .../Select/Leaderboards/Leaderboard.cs | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index ef6ba01393..8ea577d8c0 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -23,6 +23,9 @@ using osu.Game.Graphics; using osu.Framework.Logging; using System.Net; using osu.Game.Rulesets; +using osu.Framework.Input; +using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Audio.Track; namespace osu.Game.Screens.Select.Leaderboards { @@ -167,7 +170,7 @@ namespace osu.Game.Screens.Select.Leaderboards new OsuSpriteText { Anchor = Anchor.TopLeft, - Text = @"An error occurred!", + Text = @"Couldn't retrieve scores!", TextSize = 22, }, } @@ -289,47 +292,71 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private class RetryButton : ClickableContainer + private class RetryButton : BeatSyncedContainer { private SpriteIcon icon; + public Action Action; + public RetryButton() { Height = 26; Width = 26; - Children = new Drawable[] + Child = new ClickableContainer { - icon = new SpriteIcon + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => Action?.Invoke(), + Child = icon = new SpriteIcon { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Icon = FontAwesome.fa_refresh, Size = new Vector2(26), - } + }, }; } - protected override bool OnHover(Framework.Input.InputState state) + private bool rightWard; + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + { + var duration = timingPoint.BeatLength / 2; + + icon.RotateTo(rightWard ? 3 : -3, duration * 2, Easing.OutCubic); + icon.Animate( + i => i.MoveToY(-3, duration, Easing.Out), + i => i.ScaleTo(IsHovered ? 1.3f : 1.1f, duration, Easing.Out) + ).Then( + i => i.MoveToY(0, duration, Easing.In), + i => i.ScaleTo(IsHovered ? 1.4f : 1f, duration, Easing.In) + ); + + rightWard = !rightWard; + } + + protected override bool OnHover(InputState state) { icon.ScaleTo(1.4f, 400, Easing.OutQuint); return base.OnHover(state); } - protected override void OnHoverLost(Framework.Input.InputState state) + protected override void OnHoverLost(InputState state) { + icon.ClearTransforms(); icon.ScaleTo(1f, 400, Easing.OutQuint); base.OnHoverLost(state); } - protected override bool OnMouseDown(Framework.Input.InputState state, Framework.Input.MouseDownEventArgs args) + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - icon.ScaleTo(0.8f, 800, Easing.InElastic); + icon.ClearTransforms(); + icon.ScaleTo(0.8f, 400, Easing.InElastic); return base.OnMouseDown(state, args); } - protected override bool OnMouseUp(Framework.Input.InputState state, Framework.Input.MouseUpEventArgs args) + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - icon.ScaleTo(1.2f, 800, Easing.OutElastic).Then().ScaleTo(1f, 800, Easing.OutElastic); + icon.ScaleTo(1.2f, 400, Easing.OutElastic).Then().ScaleTo(1f, 400, Easing.OutElastic); return base.OnMouseUp(state, args); } } From 421231550496456baebdbc345f0c8fe97a0bf61d Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Nov 2017 12:50:20 +0530 Subject: [PATCH 014/239] Use a single placeholder container for empty and retry. --- .../Select/Leaderboards/Leaderboard.cs | 97 +++++++++---------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 8ea577d8c0..31e79022c3 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly ScrollContainer scrollContainer; private FillFlowContainer scrollFlow; - private Container noResultsPlaceholder; - private Container retryPlaceholder; + private Container placeholderContainer; + private FillFlowContainer placeholderFlow; public Action ScoreSelected; @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Leaderboards getScoresRequest?.Cancel(); getScoresRequest = null; - noResultsPlaceholder.FadeOut(fade_duration); + placeholderContainer.FadeOut(fade_duration); scrollFlow?.FadeOut(fade_duration).Expire(); scrollContainer.Clear(true); // scores stick around in scrollFlow in VisualTests without this for some reason scrollFlow = null; @@ -65,7 +65,22 @@ namespace osu.Game.Screens.Select.Leaderboards if (scores.Count() == 0) { - noResultsPlaceholder.FadeIn(fade_duration); + placeholderFlow.Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_exclamation_circle, + Size = new Vector2(26), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Text = @"No records yet!", + TextSize = 22, + }, + }; + + placeholderContainer.FadeIn(fade_duration); return; } @@ -119,61 +134,18 @@ namespace osu.Game.Screens.Select.Leaderboards ScrollbarVisible = false, }, loading = new LoadingAnimation(), - noResultsPlaceholder = new Container + placeholderContainer = new Container { Alpha = 0, RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new FillFlowContainer + placeholderFlow = new FillFlowContainer { Direction = FillDirection.Horizontal, Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_exclamation_circle, - Size = new Vector2(26), - Margin = new MarginPadding { Right = 10 }, - }, - new OsuSpriteText - { - Text = @"No records yet!", - TextSize = 22, - }, - } - }, - }, - }, - retryPlaceholder = new Container - { - Alpha = 0, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new RetryButton - { - Action = updateScores, - Margin = new MarginPadding { Right = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopLeft, - Text = @"Couldn't retrieve scores!", - TextSize = 22, - }, - } }, }, }, @@ -229,8 +201,6 @@ namespace osu.Game.Screens.Select.Leaderboards { if (!IsLoaded) return; - retryPlaceholder.FadeOut(fade_duration); - Scores = null; if (api == null || Beatmap?.OnlineBeatmapID == null) return; @@ -255,7 +225,21 @@ namespace osu.Game.Screens.Select.Leaderboards if (e is WebException) { Scores = null; - retryPlaceholder.FadeIn(fade_duration); + placeholderFlow.Children = new Drawable[] + { + new RetryButton + { + Action = updateScores, + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"Couldn't retrieve scores!", + TextSize = 22, + }, + }; + placeholderContainer.FadeIn(fade_duration); Logger.Error(e, @"Couldn't fetch beatmap scores!"); } }; @@ -298,6 +282,8 @@ namespace osu.Game.Screens.Select.Leaderboards public Action Action; + private OsuColour colours; + public RetryButton() { Height = 26; @@ -316,6 +302,12 @@ namespace osu.Game.Screens.Select.Leaderboards }; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + } + private bool rightWard; protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) @@ -350,6 +342,7 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { icon.ClearTransforms(); + icon.FlashColour(colours.Yellow, 400); icon.ScaleTo(0.8f, 400, Easing.InElastic); return base.OnMouseDown(state, args); } From ae201f0ef5dcdf1f4bbae80082ee214739ad7862 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 26 Nov 2017 15:03:49 +0530 Subject: [PATCH 015/239] R# --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 1 - osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index b9db836898..54d656eeca 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -11,7 +11,6 @@ using osu.Game.Users; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; -using System.Collections.Specialized; using osu.Framework.IO.Network; namespace osu.Game.Online.API.Requests diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 31e79022c3..62bd292710 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -34,9 +34,10 @@ namespace osu.Game.Screens.Select.Leaderboards private const double fade_duration = 200; private readonly ScrollContainer scrollContainer; + private readonly Container placeholderContainer; + private readonly FillFlowContainer placeholderFlow; + private FillFlowContainer scrollFlow; - private Container placeholderContainer; - private FillFlowContainer placeholderFlow; public Action ScoreSelected; @@ -63,7 +64,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (scores == null) return; - if (scores.Count() == 0) + if (!scores.Any()) { placeholderFlow.Children = new Drawable[] { @@ -278,7 +279,7 @@ namespace osu.Game.Screens.Select.Leaderboards private class RetryButton : BeatSyncedContainer { - private SpriteIcon icon; + private readonly SpriteIcon icon; public Action Action; From ae55d392de9a05f9d878d79a319e4a39070a1bd3 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 25 Nov 2017 15:28:39 +0100 Subject: [PATCH 016/239] only use `==` for comparion on primitive types --- osu.Game/Beatmaps/BeatmapMetadata.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 22a64820bc..a78ef25166 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -74,16 +74,16 @@ namespace osu.Game.Beatmaps return false; return onlineBeatmapSetID == other.onlineBeatmapSetID - && (Title?.Equals(other.Title) ?? false) - && (TitleUnicode?.Equals(other.TitleUnicode) ?? false) - && (Artist?.Equals(other.Artist) ?? false) - && (ArtistUnicode?.Equals(other.ArtistUnicode) ?? false) - && (AuthorString?.Equals(other.AuthorString) ?? false) - && (Source?.Equals(other.Source) ?? false) - && (Tags?.Equals(other.Tags) ?? false) + && Title == other.Title + && TitleUnicode == other.TitleUnicode + && Artist == other.Artist + && ArtistUnicode == other.ArtistUnicode + && AuthorString == other.AuthorString + && Source == other.Source + && Tags == other.Tags && PreviewTime == other.PreviewTime - && (AudioFile?.Equals(other.AudioFile) ?? false) - && (BackgroundFile?.Equals(other.BackgroundFile) ?? false); + && AudioFile == other.AudioFile + && BackgroundFile == other.BackgroundFile; } } } From d87235a2891e64708a7e617af753e420eb4efa3e Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Mon, 27 Nov 2017 20:08:16 +0100 Subject: [PATCH 017/239] prevent inserting duplicate metadata --- osu.Game/Beatmaps/BeatmapStore.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 3875202e32..ef658d2ff6 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.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.Game.Database; @@ -32,6 +33,15 @@ namespace osu.Game.Beatmaps { var context = GetContext(); + foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null)) + { + var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); + if (contextMetadata != null) + beatmap.Metadata = contextMetadata; + else + context.BeatmapMetadata.Attach(beatmap.Metadata); + } + context.BeatmapSetInfo.Attach(beatmapSet); context.SaveChanges(); From c058065a3ab78258ec24ef90a33738a4a8b1af69 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Mon, 27 Nov 2017 20:24:01 +0100 Subject: [PATCH 018/239] remove unnecessary using --- osu.Game/Beatmaps/BeatmapStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index ef658d2ff6..352f793aac 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.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.Game.Database; From ac1fb5118c6b075de0d333a6f49665b6396b8d53 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 28 Nov 2017 11:35:39 +0530 Subject: [PATCH 019/239] Fix line endings and derp that was causing request failures. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 62bd292710..5574dd69a1 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -24,7 +24,7 @@ using osu.Framework.Logging; using System.Net; using osu.Game.Rulesets; using osu.Framework.Input; -using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; namespace osu.Game.Screens.Select.Leaderboards @@ -51,7 +51,6 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; - getScoresRequest?.Cancel(); getScoresRequest = null; placeholderContainer.FadeOut(fade_duration); @@ -202,6 +201,8 @@ namespace osu.Game.Screens.Select.Leaderboards { if (!IsLoaded) return; + getScoresRequest?.Cancel(); + Scores = null; if (api == null || Beatmap?.OnlineBeatmapID == null) return; @@ -318,7 +319,7 @@ namespace osu.Game.Screens.Select.Leaderboards icon.RotateTo(rightWard ? 3 : -3, duration * 2, Easing.OutCubic); icon.Animate( i => i.MoveToY(-3, duration, Easing.Out), - i => i.ScaleTo(IsHovered ? 1.3f : 1.1f, duration, Easing.Out) + i => i.ScaleTo(IsHovered ? 1.3f : 1.1f, duration, Easing.Out) ).Then( i => i.MoveToY(0, duration, Easing.In), i => i.ScaleTo(IsHovered ? 1.4f : 1f, duration, Easing.In) @@ -350,7 +351,7 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - icon.ScaleTo(1.2f, 400, Easing.OutElastic).Then().ScaleTo(1f, 400, Easing.OutElastic); + icon.ScaleTo(1.2f, 400, Easing.OutElastic); return base.OnMouseUp(state, args); } } From e832f163e7174873ae2d68058e6e51ecc1218cf8 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 28 Nov 2017 11:57:29 +0530 Subject: [PATCH 020/239] Add failure test case. - Only show failure if request wasn't cancelled --- osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 23 +++++++- .../Select/Leaderboards/Leaderboard.cs | 59 +++++++++---------- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index ad4aa63aa8..52daf95810 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -9,6 +9,7 @@ using osu.Game.Users; using osu.Framework.Allocation; using OpenTK; using System.Linq; +using System.Net; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -19,11 +20,11 @@ namespace osu.Game.Tests.Visual { private RulesetStore rulesets; - private readonly Leaderboard leaderboard; + private readonly FailableLeaderboard leaderboard; public TestCaseLeaderboard() { - Add(leaderboard = new Leaderboard + Add(leaderboard = new FailableLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -33,8 +34,8 @@ namespace osu.Game.Tests.Visual AddStep(@"New Scores", newScores); AddStep(@"Empty Scores", () => leaderboard.Scores = Enumerable.Empty()); + AddStep(@"Network failure", networkFailure); AddStep(@"Real beatmap", realBeatmap); - newScores(); } [BackgroundDependencyLoader] @@ -263,5 +264,21 @@ namespace osu.Game.Tests.Visual }, }; } + + private void networkFailure() + { + leaderboard.Beatmap = new BeatmapInfo(); + } + + private class FailableLeaderboard : Leaderboard + { + protected override void UpdateScores() + { + if (Beatmap?.OnlineBeatmapID == null) + OnUpdateFailed(new WebException()); + else + base.UpdateScores(); + } + } } } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 5574dd69a1..8fd04e2c25 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -21,7 +21,6 @@ using System.Linq; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Logging; -using System.Net; using osu.Game.Rulesets; using osu.Framework.Input; using osu.Game.Beatmaps.ControlPoints; @@ -51,7 +50,6 @@ namespace osu.Game.Screens.Select.Leaderboards set { scores = value; - getScoresRequest = null; placeholderContainer.FadeOut(fade_duration); scrollFlow?.FadeOut(fade_duration).Expire(); @@ -120,7 +118,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (value == scope) return; scope = value; - updateScores(); + UpdateScores(); } } @@ -171,7 +169,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; pendingBeatmapSwitch?.Cancel(); - pendingBeatmapSwitch = Schedule(updateScores); + pendingBeatmapSwitch = Schedule(UpdateScores); } } @@ -195,13 +193,14 @@ namespace osu.Game.Screens.Select.Leaderboards private GetScoresRequest getScoresRequest; - private void handleRulesetChange(RulesetInfo ruleset) => updateScores(); + private void handleRulesetChange(RulesetInfo ruleset) => UpdateScores(); - private void updateScores() + protected virtual void UpdateScores() { if (!IsLoaded) return; getScoresRequest?.Cancel(); + getScoresRequest = null; Scores = null; @@ -221,34 +220,34 @@ namespace osu.Game.Screens.Select.Leaderboards { Scores = r.Scores; }; - getScoresRequest.Failure += e => - { - // TODO: check why failure is repeatedly invoked even on successful requests - if (e is WebException) - { - Scores = null; - placeholderFlow.Children = new Drawable[] - { - new RetryButton - { - Action = updateScores, - Margin = new MarginPadding { Right = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopLeft, - Text = @"Couldn't retrieve scores!", - TextSize = 22, - }, - }; - placeholderContainer.FadeIn(fade_duration); - Logger.Error(e, @"Couldn't fetch beatmap scores!"); - } - }; + getScoresRequest.Failure += OnUpdateFailed; api.Queue(getScoresRequest); } + protected void OnUpdateFailed(Exception e) + { + if (e is OperationCanceledException) return; + + Scores = null; + placeholderFlow.Children = new Drawable[] + { + new RetryButton + { + Action = UpdateScores, + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"Couldn't retrieve scores!", + TextSize = 22, + }, + }; + placeholderContainer.FadeIn(fade_duration); + Logger.Error(e, @"Couldn't fetch beatmap scores!"); + } + protected override void Update() { base.Update(); From a30cd42ba2cb43b7535a60c4345bd6c77d26b464 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 28 Nov 2017 14:38:35 +0530 Subject: [PATCH 021/239] Make retry button not look drunk. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 8fd04e2c25..e80f502e73 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -299,6 +299,7 @@ namespace osu.Game.Screens.Select.Leaderboards { Icon = FontAwesome.fa_refresh, Size = new Vector2(26), + Shadow = true, }, }; } @@ -309,13 +310,10 @@ namespace osu.Game.Screens.Select.Leaderboards this.colours = colours; } - private bool rightWard; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { var duration = timingPoint.BeatLength / 2; - icon.RotateTo(rightWard ? 3 : -3, duration * 2, Easing.OutCubic); icon.Animate( i => i.MoveToY(-3, duration, Easing.Out), i => i.ScaleTo(IsHovered ? 1.3f : 1.1f, duration, Easing.Out) @@ -323,8 +321,6 @@ namespace osu.Game.Screens.Select.Leaderboards i => i.MoveToY(0, duration, Easing.In), i => i.ScaleTo(IsHovered ? 1.4f : 1f, duration, Easing.In) ); - - rightWard = !rightWard; } protected override bool OnHover(InputState state) @@ -342,15 +338,13 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - icon.ClearTransforms(); - icon.FlashColour(colours.Yellow, 400); - icon.ScaleTo(0.8f, 400, Easing.InElastic); + icon.FadeColour(colours.Yellow, 400); return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - icon.ScaleTo(1.2f, 400, Easing.OutElastic); + icon.FadeColour(Color4.White, 400); return base.OnMouseUp(state, args); } } From bf386598b6abe4be1758bf2b7045a26cb3208679 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 30 Nov 2017 10:58:32 +0100 Subject: [PATCH 022/239] Added a new "undelete" button that restores every beatmap with "DeletePending" set to true. --- osu.Game/Beatmaps/BeatmapManager.cs | 22 +++++++++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 14 ++++++++++++ 2 files changed, 36 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0641cabcd8..376cbe183a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -338,6 +338,28 @@ namespace osu.Game.Beatmaps } } + public void Undelete(BeatmapSetInfo beatmapSet) + { + lock (importContext) + { + var context = importContext.Value; + + using (var transaction = context.BeginTransaction()) + { + context.ChangeTracker.AutoDetectChangesEnabled = false; + + var iFiles = new FileStore(() => context, storage); + var iBeatmaps = createBeatmapStore(() => context); + + if (iBeatmaps.Undelete(beatmapSet)) + iFiles.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + context.SaveChanges(transaction); + } + } + } + /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 4f4f381ae1..dcad5ab52c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -15,6 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importButton; private TriangleButton deleteButton; private TriangleButton restoreButton; + private TriangleButton undeleteButton; protected override string Header => "General"; @@ -55,6 +56,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } }, + undeleteButton = new SettingsButton + { + Text = "Restore all recently deleted beatmaps", + Action = () => + { + undeleteButton.Enabled.Value = false; + Task.Run(() => + { + foreach (var bs in beatmaps.QueryBeatmapSets(bs => bs.DeletePending).ToList()) + beatmaps.Undelete(bs); + }).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + } + }, }; } } From b09ba19d3fa45a3b650014f6f7ec057d788ca5d3 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 30 Nov 2017 11:02:53 +0100 Subject: [PATCH 023/239] Used the already-existing private method to undelete a mapset --- osu.Game/Beatmaps/BeatmapManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 376cbe183a..cfebaf083e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -351,8 +351,7 @@ namespace osu.Game.Beatmaps var iFiles = new FileStore(() => context, storage); var iBeatmaps = createBeatmapStore(() => context); - if (iBeatmaps.Undelete(beatmapSet)) - iFiles.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + undelete(iBeatmaps, iFiles, beatmapSet); context.ChangeTracker.AutoDetectChangesEnabled = true; context.SaveChanges(transaction); From f6591851c3a6f7ac3d198e940a813019046fbe55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Nov 2017 22:01:49 +0900 Subject: [PATCH 024/239] Implement a selection dragger box --- osu.Game/Rulesets/Edit/PlayfieldOverlay.cs | 50 ++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs b/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs index 58b15e3de2..77aeed42e3 100644 --- a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs +++ b/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs @@ -1,19 +1,69 @@ // 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.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using OpenTK; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Edit { public class PlayfieldOverlay : CompositeDrawable { + private readonly Drawable dragBox; + public PlayfieldOverlay() { RelativeSizeAxes = Axes.Both; + InternalChildren = new[] + { + dragBox = new Container + { + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + MaskingSmoothness = 1, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }; } + private Vector2 dragStartPos; + protected override bool OnDragStart(InputState state) + { + dragStartPos = ToLocalSpace(state.Mouse.NativeState.Position); + return true; + } + + protected override bool OnDrag(InputState state) + { + var dragPos = ToLocalSpace(state.Mouse.NativeState.Position); + var dragRectangle = RectangleF.FromLTRB( + Math.Min(dragStartPos.X, dragPos.X), + Math.Min(dragStartPos.Y, dragPos.Y), + Math.Max(dragStartPos.X, dragPos.X), + Math.Max(dragStartPos.Y, dragPos.Y)); + + dragBox.Position = dragRectangle.Location; + dragBox.Size = dragRectangle.Size; + + return true; + } + + protected override bool OnDragEnd(InputState state) + { + return true; + } } } From cf859a6cf2449896e38e5acd431176e2d20d8192 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 2 Dec 2017 00:26:02 +0900 Subject: [PATCH 025/239] Make the dragger attach to objects it surrounds Plus a lot more implementation. --- osu-framework | 2 +- .../Objects/Drawables/DrawableSlider.cs | 4 ++ .../Objects/Drawables/Pieces/SliderBody.cs | 5 +- .../Visual/TestCaseEditorCompose.cs | 2 + .../Visual/TestCaseEditorPlayfieldOverlay.cs | 54 +++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +- osu.Game/Rulesets/Edit/PlayfieldOverlay.cs | 68 +++++++++++++++++-- osu.Game/Rulesets/Edit/SelectionDragger.cs | 12 ++++ .../Objects/Drawables/DrawableHitObject.cs | 12 ++++ osu.Game/Rulesets/UI/RulesetContainer.cs | 17 ++--- osu.Game/osu.Game.csproj | 2 + 12 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs create mode 100644 osu.Game/Rulesets/Edit/SelectionDragger.cs diff --git a/osu-framework b/osu-framework index 4fc866eee3..d231ca9f79 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 4fc866eee3803f88b155150e32e021b9c21e647f +Subproject commit d231ca9f79936f3a7f3cff0c7721587755ae168c diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 74454ca555..7e6892e70b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -165,6 +166,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } public Drawable ProxiedLayer => initialCircle.ApproachCircle; + + public override Vector2 SelectionPoint => body.Position; + public override Quad SelectionQuad => body.PathDrawQuad; } internal interface ISliderProgress diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 2082e9a27b..75c2c15084 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -14,6 +14,7 @@ using osu.Game.Configuration; using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; + private int textureWidth => (int)PathWidth * 2; private readonly Slider slider; @@ -182,4 +185,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces SetRange(start, end); } } -} \ No newline at end of file +} diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index d52f27f4ab..226329a852 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -2,8 +2,10 @@ // 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.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs b/osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs new file mode 100644 index 0000000000..f0da23955d --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs @@ -0,0 +1,54 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseEditorPlayfieldOverlay : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(PlayfieldOverlay) }; + + public TestCaseEditorPlayfieldOverlay() + { + var playfield = new OsuEditPlayfield(); + playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); + playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f })); + playfield.Add(new DrawableSlider(new Slider + { + ControlPoints = new List + { + new Vector2(128, 256), + new Vector2(344, 256), + }, + Distance = 400, + Position = new Vector2(128, 256), + Velocity = 1, + TickDistance = 100, + Scale = 0.5f + })); + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(new StopwatchClock()), + Child = playfield + }, + new PlayfieldOverlay(playfield) + }; + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index ae88fb004f..b8dce4e4f6 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -110,6 +110,7 @@ + diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index f55b7b0531..41958df29b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Edit Alpha = 0, AlwaysPresent = true, }, - CreateUnderlay(rulesetContainer.Playfield), + CreateUnderlay(), rulesetContainer, CreateOverlay(rulesetContainer.Playfield) } @@ -106,9 +106,9 @@ namespace osu.Game.Rulesets.Edit protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); - protected virtual PlayfieldUnderlay CreateUnderlay(Playfield playfield) => new PlayfieldUnderlay(); + protected virtual PlayfieldUnderlay CreateUnderlay() => new PlayfieldUnderlay(); - protected virtual PlayfieldOverlay CreateOverlay(Playfield playfield) => new PlayfieldOverlay(); + protected virtual PlayfieldOverlay CreateOverlay(Playfield playfield) => new PlayfieldOverlay(playfield); protected abstract IReadOnlyList CompositionTools { get; } } diff --git a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs b/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs index 77aeed42e3..98b3bce265 100644 --- a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs +++ b/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs @@ -9,15 +9,27 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using OpenTK; using OpenTK.Graphics; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; +using System.Linq; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit { public class PlayfieldOverlay : CompositeDrawable { - private readonly Drawable dragBox; + private readonly static Color4 selection_normal_colour = Color4.White; + private readonly static Color4 selection_attached_colour = OsuColour.FromHex("eeaa00"); - public PlayfieldOverlay() + private readonly Container dragBox; + + private readonly Playfield playfield; + + public PlayfieldOverlay(Playfield playfield) { + this.playfield = playfield; + RelativeSizeAxes = Axes.Both; InternalChildren = new[] @@ -31,25 +43,31 @@ namespace osu.Game.Rulesets.Edit Child = new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true + Alpha = 0.1f } } }; } private Vector2 dragStartPos; + private RectangleF dragRectangle; + private List capturedHitObjects = new List(); protected override bool OnDragStart(InputState state) { dragStartPos = ToLocalSpace(state.Mouse.NativeState.Position); + dragBox.Position = dragStartPos; + dragBox.Size = Vector2.Zero; + dragBox.FadeTo(1); + dragBox.FadeColour(selection_normal_colour); + dragBox.BorderThickness = 2; return true; } protected override bool OnDrag(InputState state) { var dragPos = ToLocalSpace(state.Mouse.NativeState.Position); - var dragRectangle = RectangleF.FromLTRB( + dragRectangle = RectangleF.FromLTRB( Math.Min(dragStartPos.X, dragPos.X), Math.Min(dragStartPos.Y, dragPos.Y), Math.Max(dragStartPos.X, dragPos.X), @@ -58,11 +76,51 @@ namespace osu.Game.Rulesets.Edit dragBox.Position = dragRectangle.Location; dragBox.Size = dragRectangle.Size; + updateCapturedHitObjects(); + return true; } + private void updateCapturedHitObjects() + { + capturedHitObjects.Clear(); + + foreach (var obj in playfield.HitObjects.Objects) + { + if (!obj.IsAlive || !obj.IsPresent) + continue; + + var objectPosition = obj.Parent.ToScreenSpace(obj.SelectionPoint); + if (dragRectangle.Contains(ToLocalSpace(objectPosition))) + capturedHitObjects.Add(obj); + } + } + protected override bool OnDragEnd(InputState state) { + if (capturedHitObjects.Count == 0) + dragBox.FadeOut(400, Easing.OutQuint); + else + { + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + foreach (var obj in capturedHitObjects) + { + topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(obj.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(obj.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + dragBox.MoveTo(topLeft, 200, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint) + .FadeColour(selection_attached_colour, 200, Easing.OutQuint); + dragBox.BorderThickness = 3; + } + return true; } } diff --git a/osu.Game/Rulesets/Edit/SelectionDragger.cs b/osu.Game/Rulesets/Edit/SelectionDragger.cs new file mode 100644 index 0000000000..35ea3a375e --- /dev/null +++ b/osu.Game/Rulesets/Edit/SelectionDragger.cs @@ -0,0 +1,12 @@ +// 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; + +namespace osu.Game.Rulesets.Edit +{ + public class SelectionDragger : CompositeDrawable + { + + } +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 941cedca3f..9eecb9b4f5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -14,6 +14,8 @@ using osu.Game.Audio; using System.Linq; using osu.Game.Graphics; using osu.Framework.Configuration; +using OpenTK; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Objects.Drawables { @@ -38,6 +40,16 @@ namespace osu.Game.Rulesets.Objects.Drawables { HitObject = hitObject; } + + /// + /// The local point that causes this to be selected in the Editor. + /// + public virtual Vector2 SelectionPoint => DrawPosition; + + /// + /// The local rectangle that outlines this for selections in the Editor. + /// + public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } public abstract class DrawableHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 69bf6bba29..5b4565e8a8 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -55,10 +55,11 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } + private Playfield playfield; /// /// The playfield. /// - public Playfield Playfield { get; protected set; } + public Playfield Playfield => playfield ?? (playfield = CreatePlayfield()); protected readonly Ruleset Ruleset; @@ -95,6 +96,12 @@ namespace osu.Game.Rulesets.UI Replay = replay; ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; } + + /// + /// Creates a Playfield. + /// + /// The Playfield. + protected abstract Playfield CreatePlayfield(); } /// @@ -198,7 +205,7 @@ namespace osu.Game.Rulesets.UI }); AddInternal(KeyBindingInputManager); - KeyBindingInputManager.Add(Playfield = CreatePlayfield()); + KeyBindingInputManager.Add(Playfield); loadObjects(); } @@ -286,12 +293,6 @@ namespace osu.Game.Rulesets.UI /// The HitObject to make drawable. /// The DrawableHitObject. protected abstract DrawableHitObject GetVisualRepresentation(TObject h); - - /// - /// Creates a Playfield. - /// - /// The Playfield. - protected abstract Playfield CreatePlayfield(); } /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ad1370890f..c12ecc8b14 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -304,6 +304,7 @@ + @@ -567,6 +568,7 @@ + From b584178e851fa26da60a3027ec3b52708b8bd39a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 00:37:37 +0900 Subject: [PATCH 026/239] Make Beatmap ISerializable and add more JsonIgnores --- osu.Game.Rulesets.Mania/Objects/Note.cs | 2 + .../osu.Game.Rulesets.Mania.csproj | 4 + .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 153 ++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Beatmaps/Beatmap.cs | 8 +- osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 + osu.Game/Beatmaps/BeatmapInfo.cs | 7 + osu.Game/Beatmaps/BeatmapMetadata.cs | 5 + .../ControlPoints/ControlPointInfo.cs | 6 +- .../Converters/TypedListConverter.cs | 83 ++++++++++ .../Converters/Vector2Converter.cs | 35 ++++ .../IO/Serialization/IJsonSerializable.cs | 26 +-- osu.Game/Rulesets/RulesetInfo.cs | 3 + osu.Game/osu.Game.csproj | 2 + 14 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs create mode 100644 osu.Game/IO/Serialization/Converters/TypedListConverter.cs create mode 100644 osu.Game/IO/Serialization/Converters/Vector2Converter.cs diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 3c4ff4216f..c4d5a13352 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.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 Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Mania.Judgements; @@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// The key-press hit window for this note. /// + [JsonIgnore] public HitWindows HitWindows { get; protected set; } = new HitWindows(); public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index ec6f59b5be..1e11e2e694 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -40,6 +40,10 @@ $(SolutionDir)\packages\OpenTK.3.0.0-git00009\lib\net20\OpenTK.dll True + + $(SolutionDir)\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + True + diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs new file mode 100644 index 0000000000..1531deb265 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -0,0 +1,153 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using System.Linq; +using NUnit.Framework; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Serialization; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Resources; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class OsuJsonDecoderTest + { + [Test] + public void TestDecodeMetadata() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var meta = beatmap.BeatmapInfo.Metadata; + Assert.AreEqual(241526, meta.OnlineBeatmapSetID); + Assert.AreEqual("Soleily", meta.Artist); + Assert.AreEqual("Soleily", meta.ArtistUnicode); + Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); + Assert.AreEqual("Gamu", meta.AuthorString); + Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); + Assert.AreEqual(164471, meta.PreviewTime); + Assert.AreEqual(string.Empty, meta.Source); + Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); + Assert.AreEqual("Renatus", meta.Title); + Assert.AreEqual("Renatus", meta.TitleUnicode); + } + + [Test] + public void TestDecodeGeneral() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmapInfo = beatmap.BeatmapInfo; + Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(false, beatmapInfo.Countdown); + Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(false, beatmapInfo.SpecialStyle); + Assert.IsTrue(beatmapInfo.RulesetID == 0); + Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); + Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); + } + + [Test] + public void TestDecodeEditor() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmapInfo = beatmap.BeatmapInfo; + + int[] expectedBookmarks = + { + 11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351, + 95901, 106450, 116999, 119637, 130186, 140735, 151285, + 161834, 164471, 175020, 185570, 196119, 206669, 209306 + }; + Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + for (int i = 0; i < expectedBookmarks.Length; i++) + Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(4, beatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(2, beatmapInfo.TimelineZoom); + } + + [Test] + public void TestDecodeDifficulty() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + Assert.AreEqual(6.5f, difficulty.DrainRate); + Assert.AreEqual(4, difficulty.CircleSize); + Assert.AreEqual(8, difficulty.OverallDifficulty); + Assert.AreEqual(9, difficulty.ApproachRate); + Assert.AreEqual(1.8f, difficulty.SliderMultiplier); + Assert.AreEqual(2, difficulty.SliderTickRate); + } + + [Test] + public void TestDecodeColors() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + Color4[] expected = + { + new Color4(142, 199, 255, 255), + new Color4(255, 128, 128, 255), + new Color4(128, 255, 255, 255), + new Color4(128, 255, 128, 255), + new Color4(255, 187, 255, 255), + new Color4(255, 177, 140, 255), + }; + Assert.AreEqual(expected.Length, beatmap.ComboColors.Count); + for (int i = 0; i < expected.Length; i++) + Assert.AreEqual(expected[i], beatmap.ComboColors[i]); + } + + [Test] + public void TestDecodeHitObjects() + { + var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + + var curveData = beatmap.HitObjects[0] as IHasCurve; + var positionData = beatmap.HitObjects[0] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.IsNotNull(curveData); + Assert.AreEqual(new Vector2(192, 168), positionData.Position); + Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + + positionData = beatmap.HitObjects[1] as IHasPosition; + + Assert.IsNotNull(positionData); + Assert.AreEqual(new Vector2(304, 56), positionData.Position); + Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); + Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + } + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private Beatmap decodeAsJson(string filename) + { + using (var stream = Resource.OpenResource(filename)) + using (var sr = new StreamReader(stream)) + { + var legacyDecoded = new OsuLegacyDecoder().Decode(sr); + + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + using (var sr2 = new StreamReader(ms)) + { + sw.Write(legacyDecoded.Serialize()); + sw.Flush(); + + ms.Position = 0; + return new OsuJsonDecoder().Decode(sr2); + } + } + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 092ccfb9b5..60ce67c7f6 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -83,6 +83,7 @@ + diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 35b6cc2b02..c331872dda 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -9,19 +9,21 @@ using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.IO.Serialization; using osu.Game.Storyboards; +using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps { /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap + public class Beatmap : IJsonSerializable where T : HitObject { public BeatmapInfo BeatmapInfo = new BeatmapInfo(); public ControlPointInfo ControlPointInfo = new ControlPointInfo(); public List Breaks = new List(); - public readonly List ComboColors = new List + public List ComboColors = new List { new Color4(17, 136, 170, 255), new Color4(102, 136, 0, 255), @@ -29,8 +31,10 @@ namespace osu.Game.Beatmaps new Color4(121, 9, 13, 255) }; + [JsonIgnore] public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; + [JsonConverter(typeof(TypedListConverter))] /// /// The HitObjects this Beatmap contains. /// diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 0b0fca8292..03fbf9a0a7 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,6 +2,7 @@ // 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 { @@ -13,6 +14,7 @@ namespace osu.Game.Beatmaps public const float DEFAULT_DIFFICULTY = 5; [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 022d64db03..6b5f7cb150 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -15,6 +15,7 @@ namespace osu.Game.Beatmaps public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } //TODO: should be in database @@ -38,13 +39,16 @@ namespace osu.Game.Beatmaps set { onlineBeatmapSetID = value > 0 ? value : null; } } + [JsonIgnore] public int BeatmapSetInfoID { get; set; } [Required] + [JsonIgnore] public BeatmapSetInfo BeatmapSet { get; set; } public BeatmapMetadata Metadata { get; set; } + [JsonIgnore] public int BaseDifficultyID { get; set; } public BeatmapDifficulty BaseDifficulty { get; set; } @@ -60,6 +64,7 @@ namespace osu.Game.Beatmaps [JsonProperty("file_sha2")] public string Hash { get; set; } + [JsonIgnore] public bool Hidden { get; set; } /// @@ -74,6 +79,7 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } public bool SpecialStyle { get; set; } + [JsonIgnore] public int RulesetID { get; set; } public RulesetInfo Ruleset { get; set; } @@ -116,6 +122,7 @@ namespace osu.Game.Beatmaps public string Version { get; set; } public double StarDifficulty { get; set; } + public bool ShouldSerializeStarDifficulty() => false; public bool Equals(BeatmapInfo other) { diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 89f9ebf47a..2efbcd4f15 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -12,6 +12,7 @@ namespace osu.Game.Beatmaps public class BeatmapMetadata { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int ID { get; set; } private int? onlineBeatmapSetID; @@ -29,7 +30,10 @@ namespace osu.Game.Beatmaps public string Artist { get; set; } public string ArtistUnicode { get; set; } + [JsonIgnore] public List Beatmaps { get; set; } + + [JsonIgnore] public List BeatmapSets { get; set; } /// @@ -56,6 +60,7 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } public string BackgroundFile { get; set; } + [JsonIgnore] public string[] SearchableTerms => new[] { Author?.Username, diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e46eb8e20a..f24e25f112 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints @@ -58,18 +59,21 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.FirstOrDefault()); + [JsonIgnore] /// /// Finds the maximum BPM represented by any timing control point. /// public double BPMMaximum => 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + [JsonIgnore] /// /// Finds the minimum BPM represented by any timing control point. /// public double BPMMinimum => 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + [JsonIgnore] /// /// Finds the mode BPM (most common BPM) represented by the control points. /// @@ -108,4 +112,4 @@ namespace osu.Game.Beatmaps.ControlPoints return list[index - 1]; } } -} \ No newline at end of file +} diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs new file mode 100644 index 0000000000..1d617422e7 --- /dev/null +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.IO.Serialization.Converters +{ + public class TypedListConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(List); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var list = new List(); + + var localSerializer = createLocalSerializer(); + + var obj = JObject.Load(reader); + + var lookupTable = new List(); + localSerializer.Populate(obj["LookupTable"].CreateReader(), lookupTable); + + foreach (var tok in obj["Items"]) + { + var itemReader = tok.CreateReader(); + + var typeName = lookupTable[(int)tok["Type"]]; + var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); + localSerializer.Populate(itemReader, instance); + + list.Add(instance); + } + + return list; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var list = (List)value; + + var localSerializer = createLocalSerializer(); + + var lookupTable = new List(); + var objects = new List(); + foreach (var item in list) + { + var type = item.GetType().AssemblyQualifiedName; + + int typeId = lookupTable.IndexOf(type); + if (typeId == -1) + { + lookupTable.Add(type); + typeId = lookupTable.Count - 1; + } + + var itemObject = JObject.FromObject(item, localSerializer); + itemObject.AddFirst(new JProperty("Type", typeId)); + objects.Add(itemObject); + } + + writer.WriteStartObject(); + + writer.WritePropertyName("LookupTable"); + localSerializer.Serialize(writer, lookupTable); + + writer.WritePropertyName("Items"); + writer.WriteStartArray(); + foreach (var item in objects) + item.WriteTo(writer); + writer.WriteEndArray(); + + writer.WriteEndObject(); + } + + private JsonSerializer createLocalSerializer() + { + var localSettings = JsonSerializableExtensions.CreateGlobalSettings(); + localSettings.Converters = localSettings.Converters.Where(c => !(c is TypedListConverter)).ToArray(); + return JsonSerializer.Create(localSettings); + } + } +} diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs new file mode 100644 index 0000000000..4ec27311bc --- /dev/null +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using OpenTK; + +namespace osu.Game.IO.Serialization.Converters +{ + public class Vector2Converter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var obj = JObject.Load(reader); + return new Vector2((float)obj["X"], (float)obj["Y"]); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var vector = (Vector2)value; + + writer.WriteStartObject(); + + writer.WritePropertyName("X"); + writer.WriteValue(vector.X); + writer.WritePropertyName("Y"); + writer.WriteValue(vector.Y); + + writer.WriteEndObject(); + } + } +} diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index e725742726..8d10f0b291 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using Newtonsoft.Json; +using osu.Game.IO.Serialization.Converters; namespace osu.Game.IO.Serialization { @@ -11,20 +12,21 @@ namespace osu.Game.IO.Serialization public static class JsonSerializableExtensions { - public static string Serialize(this IJsonSerializable obj) - { - return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); - } + public static string Serialize(this IJsonSerializable obj) => JsonConvert.SerializeObject(obj, CreateGlobalSettings()); - public static T Deserialize(this string objString) - { - return JsonConvert.DeserializeObject(objString); - } + public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); - public static T DeepClone(this T obj) - where T : IJsonSerializable + public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); + + public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); + + public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { - return Deserialize(Serialize(obj)); - } + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Formatting = Formatting.Indented, + ObjectCreationHandling = ObjectCreationHandling.Replace, + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + Converters = new JsonConverter[] { new Vector2Converter() } + }; } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 17f07158df..e6e0b98293 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -3,18 +3,21 @@ using System; using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [JsonIgnore] public int? ID { get; set; } public string Name { get; set; } public string InstantiationInfo { get; set; } + [JsonIgnore] public bool Available { get; set; } public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4ca6123f04..f50e87b074 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -403,6 +403,8 @@ + + From d2dc7c8937dfe1901494c01c81c4459ef40e86ab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 00:38:12 +0900 Subject: [PATCH 027/239] Add OsuJsonDecoder --- osu.Game/Beatmaps/Formats/BeatmapDecoder.cs | 12 +++++++++- osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs | 25 +++++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index 7e1a87085c..31869e207e 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -14,6 +14,7 @@ namespace osu.Game.Beatmaps.Formats static BeatmapDecoder() { OsuLegacyDecoder.Register(); + OsuJsonDecoder.Register(); } public static BeatmapDecoder GetDecoder(StreamReader stream) @@ -27,7 +28,16 @@ namespace osu.Game.Beatmaps.Formats if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); - return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line); + + try + { + return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line); + } + catch + { + // As a default case, try a parameterless constructor + return (BeatmapDecoder)Activator.CreateInstance(decoders[line]); + } } protected static void AddDecoder(string magic) where T : BeatmapDecoder diff --git a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs new file mode 100644 index 0000000000..392f1b4890 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using osu.Game.IO.Serialization; + +namespace osu.Game.Beatmaps.Formats +{ + public class OsuJsonDecoder : BeatmapDecoder + { + public static void Register() + { + AddDecoder("{"); + } + + protected override void ParseFile(StreamReader stream, Beatmap beatmap) + { + stream.BaseStream.Position = 0; + stream.DiscardBufferedData(); + + string fullText = stream.ReadToEnd(); + fullText.DeserializeInto(beatmap); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f50e87b074..9e5d4291ce 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -266,6 +266,7 @@ + From e199ee52252b85ff0b091cc86c6c1c91f1ef2972 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 00:42:17 +0900 Subject: [PATCH 028/239] Add a few xmldocs --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 6 ++++++ osu.Game/IO/Serialization/Converters/Vector2Converter.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 1d617422e7..9c35fae7d4 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -6,6 +6,12 @@ using Newtonsoft.Json.Linq; namespace osu.Game.IO.Serialization.Converters { + /// + /// A type of that serializes a alongside + /// a lookup table for the types contained. The lookup table is used in deserialization to + /// reconstruct the objects with their original types. + /// + /// The type of objects contained in the this attribute is attached to. public class TypedListConverter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(List); diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs index 4ec27311bc..5f21018fd5 100644 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -8,6 +8,9 @@ using OpenTK; namespace osu.Game.IO.Serialization.Converters { + /// + /// A type of that serializes only the X and Y coordinates of a . + /// public class Vector2Converter : JsonConverter { public override bool CanConvert(Type objectType) => objectType == typeof(Vector2); From 44edb8724fcabc81456616346a9a25dc55dcb54a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 13:46:49 +0900 Subject: [PATCH 029/239] Add JsonIgnores to CommandTimelineGroup --- osu.Game/Storyboards/CommandTimelineGroup.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index 332a6f79cb..c6d9202121 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -6,6 +6,7 @@ using OpenTK.Graphics; using osu.Framework.Graphics; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; namespace osu.Game.Storyboards { @@ -23,6 +24,7 @@ namespace osu.Game.Storyboards public CommandTimeline FlipH = new CommandTimeline(); public CommandTimeline FlipV = new CommandTimeline(); + [JsonIgnore] public IEnumerable Timelines { get @@ -39,14 +41,25 @@ namespace osu.Game.Storyboards } } + [JsonIgnore] public double CommandsStartTime => Timelines.Where(t => t.HasCommands).Min(t => t.StartTime); + + [JsonIgnore] public double CommandsEndTime => Timelines.Where(t => t.HasCommands).Max(t => t.EndTime); + + [JsonIgnore] public double CommandsDuration => CommandsEndTime - CommandsStartTime; + [JsonIgnore] public virtual double StartTime => CommandsStartTime; + + [JsonIgnore] public virtual double EndTime => CommandsEndTime; + + [JsonIgnore] public double Duration => EndTime - StartTime; + [JsonIgnore] public bool HasCommands => Timelines.Any(t => t.HasCommands); public virtual IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) From 887b81148db1741aaee3f85642beeb536ddc0528 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 14:14:07 +0900 Subject: [PATCH 030/239] Don't ignore RulesetId for now --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6b5f7cb150..9450022a01 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -79,7 +79,6 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } public bool SpecialStyle { get; set; } - [JsonIgnore] public int RulesetID { get; set; } public RulesetInfo Ruleset { get; set; } From f9e34dfa3d51415062e8bd7ce677274c7823d274 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 16:23:51 +0900 Subject: [PATCH 031/239] Assume that control points are already sequentially-ordered Fixes up deserializing. --- .../UI/ManiaRulesetContainer.cs | 3 +-- .../Visual/TestCaseBeatSyncedContainer.cs | 4 ++-- .../Beatmaps/ControlPoints/ControlPointInfo.cs | 16 ++++++++++------ 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index cbbcb84b31..83306187bd 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.Lists; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - SortedList timingPoints = Beatmap.ControlPointInfo.TimingPoints; + List timingPoints = Beatmap.ControlPointInfo.TimingPoints; var barLines = new List(); for (int i = 0; i < timingPoints.Count; i++) diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index 18555574ba..d99485f3a2 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs @@ -2,12 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual }; } - private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private List timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { if (timingPoints[timingPoints.Count - 1] == current) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index f24e25f112..652ae42979 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,31 +5,35 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; -using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints { + [Serializable] public class ControlPointInfo { + [JsonProperty] /// /// All timing points. /// - public readonly SortedList TimingPoints = new SortedList(Comparer.Default); + public List TimingPoints { get; private set; } = new List(); + [JsonProperty] /// /// All difficulty points. /// - public readonly SortedList DifficultyPoints = new SortedList(Comparer.Default); + public List DifficultyPoints { get; private set; } = new List(); + [JsonProperty] /// /// All sound points. /// - public readonly SortedList SoundPoints = new SortedList(Comparer.Default); + public List SoundPoints { get; private set; } = new List(); + [JsonProperty] /// /// All effect points. /// - public readonly SortedList EffectPoints = new SortedList(Comparer.Default); + public List EffectPoints { get; private set; } = new List(); /// /// Finds the difficulty control point that is active at . @@ -87,7 +91,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. If null, a new control point will be constructed. /// The active control point at . - private T binarySearch(SortedList list, double time, T prePoint = null) + private T binarySearch(List list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) From ed5f7e5353a77d05203a596cb0c3bee4380fd237 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Dec 2017 16:28:34 +0900 Subject: [PATCH 032/239] Make OsuJsonDecoder apply defaults similar to OsuLegacyDecoder --- osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs index 392f1b4890..ed4b8f3857 100644 --- a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs @@ -20,6 +20,9 @@ namespace osu.Game.Beatmaps.Formats string fullText = stream.ReadToEnd(); fullText.DeserializeInto(beatmap); + + foreach (var hitObject in beatmap.HitObjects) + hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); } } } From b6b26cfe2565140b2b515ae1c076a0fe72083ab0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 03:04:36 +0900 Subject: [PATCH 033/239] Add basic method to migrate beatmaps to the new JSON format --- osu.Game/Beatmaps/BeatmapManager.cs | 25 ++++++++++++ .../IO/Serialization/IJsonSerializable.cs | 2 +- .../Sections/Maintenance/GeneralSettings.cs | 40 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0641cabcd8..c96b889213 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -545,6 +545,31 @@ namespace osu.Game.Beatmaps return beatmapSet; } + public void UpdateContent(BeatmapInfo beatmapInfo, Stream newData) + { + // let's only allow one concurrent update at a time for now. + var context = createContext(); + + using (var transaction = context.BeginTransaction()) + { + // create local stores so we can isolate and thread safely, and share a context/transaction. + var setInfo = beatmapInfo.BeatmapSet; + var existingSetFileInfo = setInfo.Files.First(f => f.FileInfo.Hash == beatmapInfo.Hash); + var existingFileInfo = existingSetFileInfo.FileInfo; + + existingSetFileInfo.FileInfo = files.Add(newData); + files.Dereference(existingFileInfo); + + beatmapInfo.Hash = newData.ComputeSHA2Hash(); + beatmapInfo.MD5Hash = newData.ComputeMD5Hash(); + + context.Update(existingSetFileInfo); + context.Update(beatmapInfo); + + context.SaveChanges(transaction); + } + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 8d10f0b291..e192d702ce 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -16,7 +16,7 @@ namespace osu.Game.IO.Serialization public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); - public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); + public static void DeserializeInto(this string objString, T target) => JsonConvert.DeserializeAnonymousType(objString, target, CreateGlobalSettings()); public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 4f4f381ae1..a41da6109c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,12 +1,15 @@ // 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.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.IO.Serialization; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -15,6 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton importButton; private TriangleButton deleteButton; private TriangleButton restoreButton; + private TriangleButton migrateButton; protected override string Header => "General"; @@ -55,6 +59,42 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } }, + migrateButton = new SettingsButton + { + Text = "Migrate all beatmaps to the new format", + Action = () => + { + migrateButton.Enabled.Value = false; + Task.Run(() => + { + var usableSets = beatmaps.GetAllUsableBeatmapSets(); + foreach (var set in usableSets) + { + foreach (var beatmap in set.Beatmaps) + { + var working = beatmaps.GetWorkingBeatmap(beatmap); + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + { + try + { + sw.Write(working.Beatmap.Serialize()); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + sw.Flush(); + + ms.Position = 0; + beatmaps.UpdateContent(beatmap, ms); + } + } + } + }).ContinueWith(t => Schedule(() => migrateButton.Enabled.Value = true)); + } + } }; } } From 4232a54b32507afeaed5ad317b70ebf9f644160d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 03:04:56 +0900 Subject: [PATCH 034/239] Make TypedListConverter not reconstruct serializers --- .../Converters/TypedListConverter.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 9c35fae7d4..f8897a4e9d 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -20,12 +19,10 @@ namespace osu.Game.IO.Serialization.Converters { var list = new List(); - var localSerializer = createLocalSerializer(); - var obj = JObject.Load(reader); var lookupTable = new List(); - localSerializer.Populate(obj["LookupTable"].CreateReader(), lookupTable); + serializer.Populate(obj["LookupTable"].CreateReader(), lookupTable); foreach (var tok in obj["Items"]) { @@ -33,7 +30,7 @@ namespace osu.Game.IO.Serialization.Converters var typeName = lookupTable[(int)tok["Type"]]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); - localSerializer.Populate(itemReader, instance); + serializer.Populate(itemReader, instance); list.Add(instance); } @@ -45,8 +42,6 @@ namespace osu.Game.IO.Serialization.Converters { var list = (List)value; - var localSerializer = createLocalSerializer(); - var lookupTable = new List(); var objects = new List(); foreach (var item in list) @@ -60,7 +55,7 @@ namespace osu.Game.IO.Serialization.Converters typeId = lookupTable.Count - 1; } - var itemObject = JObject.FromObject(item, localSerializer); + var itemObject = JObject.FromObject(item, serializer); itemObject.AddFirst(new JProperty("Type", typeId)); objects.Add(itemObject); } @@ -68,7 +63,7 @@ namespace osu.Game.IO.Serialization.Converters writer.WriteStartObject(); writer.WritePropertyName("LookupTable"); - localSerializer.Serialize(writer, lookupTable); + serializer.Serialize(writer, lookupTable); writer.WritePropertyName("Items"); writer.WriteStartArray(); @@ -78,12 +73,5 @@ namespace osu.Game.IO.Serialization.Converters writer.WriteEndObject(); } - - private JsonSerializer createLocalSerializer() - { - var localSettings = JsonSerializableExtensions.CreateGlobalSettings(); - localSettings.Converters = localSettings.Converters.Where(c => !(c is TypedListConverter)).ToArray(); - return JsonSerializer.Create(localSettings); - } } } From 9787788081d15fa49071f474db45fca99e01f045 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 03:39:43 +0900 Subject: [PATCH 035/239] Revert unintended change --- osu.Game/IO/Serialization/IJsonSerializable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index e192d702ce..8d10f0b291 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -16,7 +16,7 @@ namespace osu.Game.IO.Serialization public static T Deserialize(this string objString) => JsonConvert.DeserializeObject(objString, CreateGlobalSettings()); - public static void DeserializeInto(this string objString, T target) => JsonConvert.DeserializeAnonymousType(objString, target, CreateGlobalSettings()); + public static void DeserializeInto(this string objString, T target) => JsonConvert.PopulateObject(objString, target, CreateGlobalSettings()); public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); From 41b607c1657cfaac915e66dbe0e0a10b7dbdafaa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 03:40:43 +0900 Subject: [PATCH 036/239] Dont serialize hitobject sample properties copied from the control point --- osu.Game/Audio/SampleInfo.cs | 28 +++++++++++++++++++++++--- osu.Game/Rulesets/Objects/HitObject.cs | 14 ++----------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 171a1bdf75..edfda3bdc9 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -1,8 +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 Newtonsoft.Json; +using osu.Game.Beatmaps.ControlPoints; + namespace osu.Game.Audio { + [Serializable] public class SampleInfo { public const string HIT_WHISTLE = @"hitwhistle"; @@ -10,19 +15,36 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; + [JsonIgnore] + public SoundControlPoint ControlPoint; + + private string bank; /// /// The bank to load the sample from. /// - public string Bank; + public string Bank + { + get { return string.IsNullOrEmpty(bank) ? (ControlPoint?.SampleBank ?? "normal") : bank; } + set { bank = value; } + } + + public bool ShouldSerializeBank() => Bank == ControlPoint.SampleBank; /// /// The name of the sample to load. /// - public string Name; + public string Name { get; set; } + private int volume; /// /// The sample volume. /// - public int Volume; + public int Volume + { + get { return volume == 0 ? (ControlPoint?.SampleVolume ?? 0) : volume; } + set { volume = value; } + } + + public bool ShouldSerializeVolume() => Volume == ControlPoint.SampleVolume; } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index c69979d4cf..92220ff8bd 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -48,21 +48,11 @@ namespace osu.Game.Rulesets.Objects Kiai |= effectPoint.KiaiMode; // Initialize first sample - Samples.ForEach(s => initializeSampleInfo(s, soundPoint)); + Samples.ForEach(s => s.ControlPoint = soundPoint); // Initialize any repeat samples var repeatData = this as IHasRepeats; - repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => initializeSampleInfo(s, soundPoint))); - } - - private void initializeSampleInfo(SampleInfo sample, SoundControlPoint soundPoint) - { - if (sample.Volume == 0) - sample.Volume = soundPoint?.SampleVolume ?? 0; - - // If the bank is not assigned a name, assign it from the control point - if (string.IsNullOrEmpty(sample.Bank)) - sample.Bank = soundPoint?.SampleBank ?? @"normal"; + repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => s.ControlPoint = soundPoint)); } } } From a8db3a9484cebcadde7ac61445a39c5ba1ef709f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 04:09:03 +0900 Subject: [PATCH 037/239] Add progress notification to migration --- osu.Game/Beatmaps/BeatmapManager.cs | 48 +++++++++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 29 +---------- 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c96b889213..6b547afe5d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,6 +20,7 @@ using osu.Game.Beatmaps.IO; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.IO; +using osu.Game.IO.Serialization; using osu.Game.IPC; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -570,6 +571,53 @@ namespace osu.Game.Beatmaps } } + public void MigrateAllToNewFormat() + { + var usableSets = GetAllUsableBeatmapSets(); + + if (usableSets.Count == 0) + return; + + var notification = new ProgressNotification + { + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 1; + foreach (var set in usableSets) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Migrating ({i} of {usableSets.Count})"; + notification.Progress = (float)i++ / usableSets.Count; + + foreach (var beatmap in set.Beatmaps) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + var working = GetWorkingBeatmap(beatmap); + using (var ms = new MemoryStream()) + using (var sw = new StreamWriter(ms)) + { + sw.Write(working.Beatmap.Serialize()); + sw.Flush(); + + ms.Position = 0; + UpdateContent(beatmap, ms); + } + } + } + + notification.State = ProgressNotificationState.Completed; + } + /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index a41da6109c..9ae331daf0 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -65,34 +65,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { migrateButton.Enabled.Value = false; - Task.Run(() => - { - var usableSets = beatmaps.GetAllUsableBeatmapSets(); - foreach (var set in usableSets) - { - foreach (var beatmap in set.Beatmaps) - { - var working = beatmaps.GetWorkingBeatmap(beatmap); - using (var ms = new MemoryStream()) - using (var sw = new StreamWriter(ms)) - { - try - { - sw.Write(working.Beatmap.Serialize()); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - sw.Flush(); - - ms.Position = 0; - beatmaps.UpdateContent(beatmap, ms); - } - } - } - }).ContinueWith(t => Schedule(() => migrateButton.Enabled.Value = true)); + Task.Factory.StartNew(beatmaps.MigrateAllToNewFormat).ContinueWith(t => Schedule(() => migrateButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); } } }; From 0e3b001b133e310064065be3c98f3f13f320321d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 10:37:51 +0900 Subject: [PATCH 038/239] Make maps with storyboards decode correctly with OsuJsonDecoder --- osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs index ed4b8f3857..d00cbbb8fb 100644 --- a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs @@ -18,8 +18,16 @@ namespace osu.Game.Beatmaps.Formats stream.BaseStream.Position = 0; stream.DiscardBufferedData(); - string fullText = stream.ReadToEnd(); - fullText.DeserializeInto(beatmap); + try + { + string fullText = stream.ReadToEnd(); + fullText.DeserializeInto(beatmap); + } + catch + { + // Temporary because storyboards are deserialized into beatmaps at the moment + // This try-catch shouldn't exist in the future + } foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); From 9597f9d46ba306fa2be72c46f7fccec7428797a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 11:10:20 +0900 Subject: [PATCH 039/239] Resharper cleanup --- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 9ae331daf0..f0f5b434cd 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,15 +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 System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; -using osu.Game.IO.Serialization; namespace osu.Game.Overlays.Settings.Sections.Maintenance { From ea2c67ca5fe03415fe7a79831738131f8ebb1585 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 11:10:34 +0900 Subject: [PATCH 040/239] Fix incorrect serialization condition --- osu.Game/Audio/SampleInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index edfda3bdc9..f8b5bf33d9 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Audio set { bank = value; } } - public bool ShouldSerializeBank() => Bank == ControlPoint.SampleBank; + public bool ShouldSerializeBank() => Bank != ControlPoint.SampleBank; /// /// The name of the sample to load. @@ -45,6 +45,6 @@ namespace osu.Game.Audio set { volume = value; } } - public bool ShouldSerializeVolume() => Volume == ControlPoint.SampleVolume; + public bool ShouldSerializeVolume() => Volume != ControlPoint.SampleVolume; } } From 499ecb4eddebc72913a4747715fb111bff7be6bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 11:11:38 +0900 Subject: [PATCH 041/239] Add parity checking OsuJsonDecoder test cases --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 36 +- ...n - The Unforgiving (Armin) [Marathon].osu | 7102 +++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 4 + osu.Game.Tests/packages.config | 1 + 4 files changed, 7135 insertions(+), 8 deletions(-) create mode 100644 osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 1531deb265..c3ebcb9e7c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; +using DeepEqual.Syntax; using NUnit.Framework; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -18,10 +19,13 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { + private const string beatmap_1 = "Soleily - Renatus (Gamu) [Insane].osu"; + private const string beatmap_2 = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; + [Test] public void TestDecodeMetadata() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); var meta = beatmap.BeatmapInfo.Metadata; Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); @@ -39,7 +43,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeGeneral() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(false, beatmapInfo.Countdown); @@ -53,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeEditor() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); var beatmapInfo = beatmap.BeatmapInfo; int[] expectedBookmarks = @@ -74,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeDifficulty() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); @@ -87,7 +91,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeColors() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); Color4[] expected = { new Color4(142, 199, 255, 255), @@ -105,7 +109,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeHitObjects() { - var beatmap = decodeAsJson("Soleily - Renatus (Gamu) [Insane].osu"); + var beatmap = decodeAsJson(beatmap_1); var curveData = beatmap.HitObjects[0] as IHasCurve; var positionData = beatmap.HitObjects[0] as IHasPosition; @@ -124,13 +128,29 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); } + [TestCase(beatmap_1)] + [TestCase(beatmap_2)] + public void TestParity(string beatmap) + { + var beatmaps = decode(beatmap); + beatmaps.jsonDecoded.ShouldDeepEqual(beatmaps.legacyDecoded); + } + /// /// Reads a .osu file first with a , serializes the resulting to JSON /// and then deserializes the result back into a through an . /// /// The .osu file to decode. /// The after being decoded by an . - private Beatmap decodeAsJson(string filename) + private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; + + /// + /// Reads a .osu file first with a , serializes the resulting to JSON + /// and then deserializes the result back into a through an . + /// + /// The .osu file to decode. + /// The after being decoded by an . + private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) { using (var stream = Resource.OpenResource(filename)) using (var sr = new StreamReader(stream)) @@ -145,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return new OsuJsonDecoder().Decode(sr2); + return (legacyDecoded, new OsuJsonDecoder().Decode(sr2)); } } } diff --git a/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu b/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu new file mode 100644 index 0000000000..4f8e8f820f --- /dev/null +++ b/osu.Game.Tests/Resources/Within Temptation - The Unforgiving (Armin) [Marathon].osu @@ -0,0 +1,7102 @@ +osu file format v9 + +[General] +AudioFilename: Within Temptation - The Unforgiving.mp3 +AudioLeadIn: 2000 +PreviewTime: 2513029 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.7 +Mode: 0 +LetterboxInBreaks: 1 + +[Editor] +Bookmarks: 3177331 +DistanceSpacing: 0.9 +BeatDivisor: 4 +GridSize: 4 + +[Metadata] +Title:The Unforgiving +Artist:Within Temptation +Creator:Armin +Version:Marathon +Source: +Tags:Gonzvlo narakucrimson Roddie Vass_Bass ErufenRito Glass Card N'FoRcE force HakunoKaemi metal Why not me Shot in the Dark In the Middle of the Night Faster Fire and Ice Iron Where is the edge Sinead Lost Murder A Demon's Fate Stairway to the skies TU Marathon symphonic metal rock Sharon den Adel collab + +[Difficulty] +HPDrainRate:3 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:2 +SliderTickRate:1 + +[Events] +//Background and Video events +0,0,"Within-Temptation.png" +Video,-300,"Mother Maiden_xvid_003.avi" +Video,910191,"Fire And Ice_x264.avi" +Video,648748,"17 Faster_x264_001.avi" +Video,1730578,"19 Sinead_xvid_001.avi" +Video,34667,"Within Temptation-Shot In The _x264_001.avi" +//Break Periods +2,31107,46554 +2,116273,123530 +2,193254,209002 +2,325697,336343 +2,442893,451543 +2,533893,549943 +2,644043,670986 +2,744936,750986 +2,886436,932774 +2,1017706,1021899 +2,1141547,1152290 +2,1285058,1290472 +2,1372331,1382290 +2,1476876,1505227 +2,1575917,1584318 +2,1666307,1676266 +2,1714619,1747493 +2,1825109,1829544 +2,1903058,1916980 +2,1935879,1949288 +2,1981007,1993647 +2,2205454,2225075 +2,2290311,2308242 +2,2394266,2400928 +2,2473072,2486674 +2,2513371,2525630 +2,2555908,2578758 +2,2686971,2696967 +2,2787469,2800847 +2,2889559,2901019 +2,2925673,2939538 +2,3002710,3007686 +2,3086784,3102501 +//Storyboard Layer 0 (Background) +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +2406,418.848167539267,4,2,0,100,1,0 +15829,418.848167539267,4,2,0,100,1,0 +34572,565.904267861353,4,1,0,100,1,0 +47011,-100,4,2,1,100,0,0 +95696,566.037735849057,4,2,1,100,1,0 +97111,-100,4,1,0,80,0,1 +97247,-100,4,1,0,82,0,1 +97388,-100,4,1,0,84,0,1 +97530,-100,4,1,0,86,0,1 +97671,-100,4,1,0,90,0,1 +97818,-100,4,1,0,93,0,0 +97960,-100,4,1,0,96,0,1 +98096,-100,4,2,1,100,0,1 +106020,-100,4,1,1,80,0,1 +106233,-100,4,1,1,83,0,1 +106374,-100,4,1,1,86,0,1 +106516,-100,4,1,1,89,0,1 +106657,-100,4,1,1,92,0,1 +106799,-100,4,1,1,95,0,1 +106940,-100,4,2,1,98,0,1 +116073,-100,4,2,1,98,0,0 +124134,-100,4,1,0,100,0,0 +124983,-100,4,2,1,100,0,0 +160926,-100,4,1,0,100,0,0 +161209,-100,4,2,1,100,0,0 +165454,-100,4,1,0,100,0,0 +165737,-100,4,2,1,100,0,0 +169983,-100,4,1,0,100,0,0 +170266,-100,4,2,0,87,0,0 +172671,-100,4,1,0,100,0,0 +173101,-100,4,1,0,81,0,1 +173237,-100,4,1,0,84,0,1 +173379,-100,4,1,0,87,0,1 +173520,-100,4,1,0,90,0,1 +174658,-100,4,2,0,100,0,0 +174941,-100,4,2,1,100,0,1 +182436,-100,4,2,0,100,0,1 +183002,-100,4,1,0,100,0,1 +183714,-100,4,2,0,100,0,0 +183997,-100,4,2,1,100,0,1 +191563,-100,4,1,0,80,0,1 +191775,-100,4,1,0,83,0,1 +191917,-100,4,1,0,86,0,1 +192058,-100,4,1,0,89,0,1 +192200,-100,4,1,0,91,0,1 +192341,-100,4,1,0,93,0,1 +192483,-100,4,1,0,95,0,1 +192624,-100,4,1,0,97,0,1 +192766,-100,4,1,0,98,0,1 +192907,-100,4,1,0,99,0,1 +193054,-100,4,2,0,100,0,0 +209888,-100,4,1,0,63,0,0 +211311,-100,4,2,0,5,0,0 +214708,-100,4,2,0,30,0,0 +215549,-100,4,1,0,15,0,0 +215686,566.037735849057,4,1,0,15,1,0 +215761,-100,4,2,0,5,0,0 +217105,-100,4,2,0,31,0,0 +217884,-100,4,1,0,15,0,0 +218450,-100,4,2,0,30,0,0 +218662,-100,4,1,0,15,0,0 +219299,-100,4,2,0,29,0,0 +219511,-100,4,2,0,36,0,0 +220148,-100,4,1,0,16,0,0 +220289,-100,4,2,0,16,0,0 +220997,-100,4,1,0,16,0,0 +221138,-100,4,2,0,16,0,0 +222341,-100,4,1,0,16,0,0 +222907,-100,4,2,0,16,0,0 +223190,-100,4,1,0,16,0,0 +223473,-100,4,2,0,16,0,0 +224605,-100,4,1,0,16,0,0 +225171,-100,4,2,0,16,0,0 +225454,-100,4,1,0,16,0,0 +226091,-100,4,2,0,16,0,0 +226870,-100,4,1,0,16,0,0 +227436,-100,4,2,0,16,0,0 +227719,-100,4,1,0,16,0,0 +228072,-100,4,2,0,16,0,0 +229134,-100,4,1,0,16,0,0 +229770,-100,4,2,0,16,0,0 +229983,-100,4,1,0,16,0,0 +230195,-100,4,2,0,16,0,0 +231469,-100,4,1,0,16,0,0 +231610,-100,4,2,0,16,0,0 +232318,-100,4,1,0,16,0,0 +232601,-100,4,2,0,33,0,0 +233167,-100,4,2,0,21,0,0 +233733,-100,4,1,0,21,0,0 +243355,-100,4,1,0,41,0,0 +246256,-100,4,1,0,26,0,0 +247954,-100,4,1,0,39,0,0 +250510,-100,4,2,0,40,0,0 +250651,-100,4,2,0,50,0,0 +250793,-100,4,2,0,60,0,0 +251912,-100,4,2,1,100,0,1 +267761,-100,4,2,1,100,0,0 +270025,-100,4,2,1,100,0,1 +270171,-76.9230769230769,4,2,1,83,0,1 +285737,-76.9230769230769,4,1,0,36,0,1 +286162,-76.9230769230769,4,1,0,45,0,1 +286445,-76.9230769230769,4,1,0,52,0,1 +286728,-76.9230769230769,4,1,0,60,0,1 +287011,-76.9230769230769,4,1,0,68,0,1 +287294,-76.9230769230769,4,1,0,76,0,1 +287577,-76.9230769230769,4,1,1,83,0,1 +287855,-76.9230769230769,4,2,1,100,0,0 +288138,-76.9230769230769,4,2,1,100,0,1 +296068,-76.9230769230769,4,1,1,54,0,1 +296351,-76.9230769230769,4,1,1,60,0,1 +296634,-76.9230769230769,4,1,1,71,0,1 +296923,-76.9230769230769,4,2,1,93,0,0 +297195,-76.9230769230769,4,2,1,100,0,1 +306252,-76.9230769230769,4,2,1,100,0,0 +324082,-76.9230769230769,4,2,1,100,0,0 +324152,-76.9230769230769,4,2,1,65,0,0 +337093,300,4,2,1,40,1,0 +346663,-100,4,1,0,70,0,0 +346851,-100,4,2,1,70,0,0 +366193,-100,4,2,1,70,0,0 +384870,-100,4,2,0,70,0,0 +384943,-100,4,1,0,70,0,0 +385138,-100,4,2,1,70,0,0 +388663,-100,4,1,0,70,0,0 +389001,-100,4,2,1,70,0,0 +399493,-100,4,1,0,70,0,0 +399651,-100,4,2,1,70,0,0 +408343,-100,4,1,0,70,0,0 +408943,-100,4,1,0,70,0,0 +409093,-100,4,1,0,70,0,0 +409168,-100,4,2,1,70,0,0 +416593,-100,4,1,0,70,0,0 +416670,-100,4,2,0,70,0,0 +416743,-100,4,1,0,50,0,0 +416893,-100,4,1,0,80,0,0 +416968,-100,4,2,0,70,0,0 +417043,-100,4,1,0,50,0,0 +417193,-100,4,1,0,90,0,0 +417270,-100,4,2,0,70,0,0 +417493,-100,4,1,0,100,0,0 +417568,-100,4,2,0,70,0,0 +418693,-100,4,2,1,70,0,1 +428226,-100,4,1,0,70,0,1 +428451,-100,4,2,1,70,0,1 +436634,-100,4,2,0,70,0,1 +436784,-100,4,1,0,70,0,1 +437830,-100,4,1,0,70,0,0 +437893,-100,4,1,0,70,0,1 +442693,-100,4,1,0,70,0,0 +452001,-100,4,2,1,70,0,0 +461293,-100,4,1,0,70,0,0 +461976,-100,4,2,1,70,0,0 +470293,-100,4,1,0,70,0,0 +471576,-100,4,2,0,70,0,0 +471726,-100,4,1,0,70,0,0 +471876,-100,4,2,1,70,0,0 +475693,-100,4,1,0,70,0,0 +475926,-100,4,1,0,70,0,0 +476526,-100,4,1,0,70,0,0 +476676,-100,4,2,1,70,0,0 +479893,-100,4,1,0,70,0,0 +481176,-100,4,2,0,70,0,0 +481326,-100,4,1,0,70,0,0 +481476,-100,4,2,1,70,0,0 +490693,-100,4,1,0,70,0,1 +490851,-100,4,2,1,70,0,1 +499693,-100,4,1,0,60,0,1 +499993,-100,4,1,0,65,0,1 +500143,-100,4,1,0,70,0,0 +500293,-100,4,1,0,70,0,1 +501276,-100,4,2,0,70,0,1 +501426,-100,4,1,0,70,0,1 +501876,-100,4,2,0,70,0,1 +502326,-100,4,1,0,70,0,1 +502776,-100,4,2,0,70,0,1 +502926,-100,4,1,0,70,0,1 +509293,-100,4,1,0,60,0,1 +509593,-100,4,1,0,65,0,1 +514693,-100,4,1,0,70,0,0 +514709,-100,4,1,0,70,0,0 +563151,-83.3333333333333,4,1,0,70,0,0 +574251,-76.9230769230769,4,1,0,70,0,0 +581893,-76.9230769230769,4,1,0,70,0,1 +591193,-76.9230769230769,4,1,0,70,0,0 +591493,-76.9230769230769,4,1,0,70,0,1 +601093,-76.9230769230769,4,1,0,70,0,1 +603726,-83.3333333333333,4,1,0,70,0,1 +605893,-83.3333333333333,4,1,0,70,0,0 +634993,-83.3333333333333,4,1,0,20,0,0 +648736,500,4,2,1,100,1,0 +710611,-100,4,1,0,63,0,0 +710781,-100,4,1,0,65,0,0 +710857,-100,4,1,0,70,0,0 +710982,-100,4,1,0,76,0,0 +711107,-100,4,1,0,79,0,0 +711232,-100,4,1,0,85,0,0 +711357,-100,4,1,0,91,0,0 +711482,-100,4,1,0,94,0,0 +711781,-100,4,1,0,100,0,0 +712736,-76.9230769230769,4,1,0,100,0,1 +712798,-76.9230769230769,4,2,1,100,0,1 +720498,-76.9230769230769,4,1,0,100,0,0 +720736,-76.9230769230769,4,1,0,100,0,1 +721986,-76.9230769230769,4,2,1,100,0,1 +727986,-76.9230769230769,4,1,0,80,0,1 +728111,-76.9230769230769,4,1,0,83,0,1 +728236,-76.9230769230769,4,1,0,86,0,1 +728361,-76.9230769230769,4,1,0,89,0,1 +728486,-76.9230769230769,4,1,0,92,0,1 +728623,-76.9230769230769,4,1,0,95,0,0 +728736,-76.9230769230769,4,1,0,98,0,1 +744736,-100,4,1,0,100,0,0 +751234,-100,4,2,1,100,0,0 +752611,-100,4,1,0,100,0,0 +752861,-100,4,2,1,100,0,0 +759611,-100,4,1,0,100,0,0 +759861,-100,4,2,0,100,0,0 +760173,-100,4,1,0,100,0,0 +760361,-100,4,2,0,100,0,0 +760673,-100,4,1,0,100,0,0 +760861,-100,4,2,1,100,0,0 +784548,-100,4,2,0,100,0,0 +785111,-100,4,1,0,100,0,0 +785361,-83.3333333333333,4,1,0,100,0,0 +785548,-83.3333333333333,4,2,0,100,0,0 +786111,-83.3333333333333,4,1,0,100,0,0 +786361,-71.4285714285714,4,1,0,100,0,0 +786548,-71.4285714285714,4,2,0,100,0,0 +787111,-62.5,4,1,0,100,0,0 +787548,-62.5,4,2,0,100,0,0 +788111,-55.5555555555556,4,1,0,100,0,0 +788548,-55.5555555555556,4,2,0,100,0,0 +789111,-55.5555555555556,4,1,0,100,0,0 +789361,-50,4,1,0,100,0,0 +789548,-50,4,2,0,100,0,0 +790111,-76.9230769230769,4,1,0,100,0,0 +790736,-76.9230769230769,4,1,0,80,0,0 +790986,-76.9230769230769,4,1,0,85,0,0 +791236,-76.9230769230769,4,1,0,90,0,0 +791486,-76.9230769230769,4,1,0,95,0,0 +791736,-76.9230769230769,4,1,0,100,0,0 +792736,-76.9230769230769,4,1,0,100,0,1 +800486,-76.9230769230769,4,1,0,100,0,0 +800736,-76.9230769230769,4,1,0,100,0,1 +808611,-76.9230769230769,4,1,0,100,0,0 +808736,-76.9230769230769,4,1,0,100,0,1 +824611,-76.9230769230769,4,1,0,100,0,0 +824736,-76.9230769230769,4,1,0,100,0,1 +824861,-76.9230769230769,4,1,0,100,0,0 +852361,-76.9230769230769,4,2,0,100,0,0 +858736,-76.9230769230769,4,1,0,100,0,0 +860736,-66.6666666666667,4,1,0,100,0,1 +868486,-66.6666666666667,4,1,0,100,0,0 +868736,-66.6666666666667,4,1,0,100,0,1 +872361,-66.6666666666667,4,1,0,100,0,1 +873861,-66.6666666666667,4,1,0,100,0,1 +884486,-66.6666666666667,4,1,0,100,0,0 +884736,-66.6666666666667,4,1,0,100,0,1 +886236,-66.6666666666667,4,1,0,100,0,0 +912957,856.959223023638,4,2,0,21,1,0 +941887,-117.647058823529,4,2,0,21,0,0 +972298,-100,4,2,0,21,0,0 +974654,-100,4,2,0,21,0,0 +991894,-117.647058823529,4,2,0,21,0,0 +992858,-100,4,2,0,21,0,0 +1015131,-90.9090909090909,4,2,0,21,0,0 +1015452,-90.9090909090909,4,2,0,5,0,0 +1017701,-100,4,2,0,15,0,0 +1022649,428.571428571429,4,2,1,78,1,0 +1030031,-90.9090909090909,4,2,0,79,0,0 +1031103,-100,4,2,1,79,0,0 +1049960,-100,4,1,0,60,0,0 +1050174,-100,4,2,0,100,0,0 +1051674,-100,4,1,0,60,0,0 +1051889,-100,4,2,0,100,0,0 +1052103,-100,4,1,0,60,0,0 +1052317,-100,4,2,0,100,0,0 +1052531,-100,4,1,0,60,0,0 +1052746,-100,4,2,0,100,0,0 +1052960,-100,4,1,0,60,0,0 +1053174,-100,4,2,0,100,0,0 +1053389,-100,4,1,0,80,0,0 +1053603,-100,4,2,0,100,0,0 +1054246,-100,4,1,0,80,0,0 +1054460,-100,4,2,1,81,0,0 +1067103,-100,4,1,0,80,0,0 +1067317,-100,4,2,1,100,0,0 +1069674,-100,4,1,0,85,0,0 +1069996,-100,4,2,1,85,0,0 +1070531,-100,4,1,0,68,0,0 +1070746,-100,4,2,0,100,0,0 +1071389,-100,4,1,0,80,0,0 +1071603,-100,4,2,0,100,0,0 +1072246,-100,4,1,0,60,0,0 +1072460,-100,4,2,0,100,0,0 +1073103,-100,4,1,0,80,0,0 +1073317,-100,4,2,0,100,0,0 +1073960,-100,4,1,0,60,0,0 +1074174,-100,4,2,0,100,0,0 +1074389,-100,4,1,0,60,0,0 +1074603,-100,4,2,0,100,0,0 +1074817,-100,4,1,0,80,0,0 +1075031,-100,4,2,0,100,0,0 +1075674,-100,4,1,0,60,0,0 +1076317,-100,4,2,0,100,0,0 +1076531,-100,4,1,0,80,0,0 +1076746,-100,4,2,0,100,0,0 +1077389,-100,4,2,1,81,0,0 +1081246,-90.9090909090909,4,2,1,82,0,0 +1082317,-100,4,2,1,80,0,0 +1083281,-100,4,2,1,80,0,0 +1093460,-90.9090909090909,4,2,1,80,0,0 +1094317,-100,4,2,1,80,0,0 +1105674,-100,4,2,1,80,0,0 +1118531,-100,4,1,0,80,0,0 +1118746,-100,4,2,1,80,0,0 +1122014,-100,4,1,0,80,0,0 +1122174,-100,4,2,1,80,0,0 +1128817,-133.333333333333,4,2,1,80,0,0 +1135674,-100,4,2,0,22,0,0 +1137505,444.444444444444,4,2,0,22,1,0 +1139282,461.538461538462,4,2,0,16,1,0 +1140205,571.428571428571,4,2,0,19,1,0 +1141347,571.428571428571,4,2,0,19,1,0 +1150313,454.545454545455,4,2,0,100,1,0 +1153040,-100,4,2,1,100,0,0 +1226981,-72.992700729927,4,2,1,100,0,0 +1241222,-68.0272108843537,4,2,1,100,0,1 +1284868,-105.042016806723,4,1,0,100,0,0 +1291224,-80.7754442649435,4,1,0,100,0,0 +1291299,-80.7754442649435,4,2,1,100,0,0 +1291375,-80.7754442649435,4,1,0,100,0,0 +1291451,-80.7754442649435,4,2,1,100,0,0 +1291527,-80.7754442649435,4,1,0,100,0,0 +1291602,-80.7754442649435,4,2,1,100,0,0 +1291678,-74.6268656716418,4,1,0,100,0,0 +1292133,-72.7272727272727,4,2,1,100,0,0 +1328494,-72.992700729927,4,2,1,100,0,1 +1342147,-72.992700729927,4,2,1,100,0,1 +1342737,-72.992700729927,4,2,1,100,0,0 +1343040,-72.992700729927,4,2,1,100,0,1 +1345025,-63.2911392405063,4,2,1,100,0,1 +1345935,-72.992700729927,4,2,1,100,0,1 +1357585,-72.992700729927,4,2,1,100,0,0 +1372139,-72.992700729927,4,2,1,100,0,0 +1408427,-86.2068965517241,4,2,1,100,0,0 +1409571,-87.5656742556918,4,2,1,100,0,0 +1412071,-87.5656742556918,4,1,0,100,0,0 +1412222,-87.5656742556918,4,2,1,100,0,0 +1415767,-72.992700729927,4,2,1,100,0,1 +1430009,-72.992700729927,4,2,1,100,0,0 +1430312,-72.992700729927,4,2,1,100,0,1 +1430632,-63.2911392405063,4,2,1,100,0,1 +1444706,-63.2911392405063,4,2,1,100,0,0 +1444858,-63.2911392405063,4,2,1,100,0,1 +1473949,-63.2911392405063,4,2,1,100,0,0 +1491562,389.61038961039,4,1,0,80,1,0 +1506367,-200,4,1,0,80,0,0 +1507146,-100,4,1,0,80,0,0 +1550782,-100,4,1,0,80,0,1 +1575717,-100,4,1,0,80,0,0 +1584873,-100,4,2,0,70,0,0 +1587511,-100,4,1,0,80,0,0 +1589938,-200,4,1,0,80,0,0 +1590523,-100,4,1,0,80,0,0 +1619354,-100,4,1,0,80,0,1 +1644289,-100,4,1,0,80,0,0 +1662990,-100,4,2,0,40,0,0 +1678575,-100,4,2,0,70,0,0 +1688315,-100,4,1,0,40,0,0 +1688510,-100,4,1,0,50,0,0 +1688704,-100,4,1,0,60,0,0 +1688899,-100,4,1,0,70,0,0 +1689094,-100,4,1,0,80,0,0 +1689289,-100,4,1,0,80,0,0 +1689484,-100,4,1,0,80,0,0 +1691042,-100,4,1,0,80,0,1 +1714419,-100,4,2,0,40,0,0 +1730551,512.820512820513,4,1,0,100,1,0 +1747667,-181.818181818182,4,2,0,35,0,0 +1763137,-120.481927710843,4,2,1,70,0,0 +1795191,-120.481927710843,4,2,1,90,0,0 +1796192,-90.9090909090909,4,2,1,100,0,1 +1824909,-100,4,2,1,100,0,0 +1830166,-119.760479041916,4,2,1,100,0,0 +1874140,-90.9090909090909,4,2,1,100,0,1 +1902858,-100,4,2,1,100,0,0 +1917477,-120.481927710843,4,2,1,100,0,0 +1950551,-120.481927710843,4,2,1,70,0,0 +1951063,-120.481927710843,4,2,1,60,0,0 +1952088,-90.9090909090909,4,2,1,100,0,1 +1967986,-90.9090909090909,4,2,1,100,0,0 +1968499,-90.9090909090909,4,2,1,100,0,1 +1980807,-90.9090909090909,4,2,1,100,0,0 +1994397,428.571428571429,3,2,0,30,1,0 +1995484,-100,3,2,0,48,0,0 +1995913,-100,3,2,0,30,0,0 +2000627,-100,3,2,0,51,0,0 +2001055,-100,3,2,0,30,0,0 +2045825,-100,3,2,0,40,0,0 +2070163,-100,3,1,0,60,0,0 +2070323,-100,3,2,0,60,0,0 +2071539,-100,3,1,0,50,0,0 +2072825,-100,3,1,0,60,0,0 +2073055,-100,3,2,0,60,0,0 +2073913,-100,3,2,1,60,0,0 +2130682,-100,3,2,1,60,0,1 +2141198,-87.719298245614,3,2,1,60,0,1 +2142484,-100,3,2,1,60,0,1 +2153825,-100,3,2,1,60,0,0 +2189734,-87.719298245614,3,2,1,60,0,0 +2197448,-100,3,2,1,60,0,0 +2245012,-100,3,1,0,60,0,0 +2246397,-100,3,1,0,60,0,1 +2246619,-100,3,2,1,60,0,1 +2269111,-100,3,2,1,60,0,0 +2269539,-100,3,2,1,60,0,1 +2290111,-100,3,2,1,60,0,0 +2308992,447.761194029851,4,2,0,29,1,0 +2316044,-100,4,2,1,51,0,0 +2336305,-100,4,1,0,41,0,0 +2337318,-100,4,2,1,51,0,0 +2350969,-100,4,1,0,41,0,0 +2351865,-133.333333333333,4,2,1,60,0,0 +2365521,-133.333333333333,4,1,0,20,0,0 +2365745,-133.333333333333,4,1,0,30,0,0 +2365969,-100,4,1,0,50,0,0 +2366305,-100,4,1,2,60,0,1 +2379408,-100,4,1,2,70,0,1 +2381087,-100,4,1,2,60,0,1 +2394962,-100,4,1,2,60,0,0 +2402350,-100,4,2,1,55,0,0 +2415223,-100,4,1,0,50,0,0 +2416566,-100,4,2,1,50,0,0 +2429775,-100,4,1,0,50,0,0 +2430671,-133.333333333333,4,2,1,50,0,0 +2444327,-133.333333333333,4,2,1,40,0,0 +2444551,-133.333333333333,4,2,1,50,0,0 +2445111,-100,4,1,2,60,0,1 +2470186,-100,4,1,0,65,0,0 +2488096,-100,4,1,0,65,0,1 +2488214,-100,4,1,0,65,0,0 +2502424,-100,4,1,0,65,0,1 +2502542,-100,4,1,0,65,0,0 +2526162,-100,4,1,0,40,0,0 +2526721,-100,4,1,0,50,0,0 +2527169,-100,4,1,0,60,0,0 +2527499,-100,4,1,2,65,0,1 +2541380,-100,4,1,2,65,0,0 +2541827,-100,4,1,2,65,0,1 +2553021,-100,4,1,2,65,0,1 +2555596,-100,4,1,2,5,0,1 +2556156,-100,4,1,2,56,0,0 +2565180,447.761194029851,4,1,0,100,1,0 +2579508,-100,4,2,0,80,0,0 +2586112,-100,4,1,0,69,0,0 +2643985,-100,4,1,0,69,0,1 +2658314,-100,4,1,0,69,0,1 +2671747,-100,4,1,0,69,0,1 +2672642,-100,4,1,0,69,0,0 +2701539,-90.9090909090909,4,1,0,69,0,0 +2715643,-100,4,1,0,69,0,0 +2729956,-100,4,1,0,69,0,1 +2758612,-76.9230769230769,4,1,0,69,0,0 +2761875,-66.6666666666667,4,1,0,69,0,0 +2801605,-133.333333333333,4,1,0,60,0,0 +2815926,-100,4,1,0,60,0,0 +2844582,-100,4,1,0,60,0,1 +2855329,-100,4,1,0,60,0,0 +2858911,-100,4,1,0,60,0,1 +2873239,-100,4,1,0,60,0,0 +2889304,-100,4,2,0,5,0,0 +2895844,370.37037037037,4,2,0,100,1,0 +2901399,-166.666666666667,4,2,0,50,0,0 +2924269,-200,4,2,0,50,0,0 +2940108,-200,4,2,1,50,0,0 +2952145,-100,4,2,1,75,0,0 +2960479,-133.333333333333,4,2,1,75,0,0 +2962331,-100,4,2,1,75,0,0 +2976220,-100,4,2,1,75,0,0 +3016775,-133.333333333333,4,2,1,75,0,0 +3018627,-100,4,2,1,75,0,0 +3057232,-151.515151515152,4,1,0,60,0,0 +3058719,-100,4,2,0,75,0,0 +3059182,-100,4,2,1,75,0,0 +3082562,-100,4,2,0,10,0,0 +3086775,-100,4,2,0,75,0,0 +3103066,-151.515151515152,4,1,0,60,0,0 +3105571,-151.515151515152,4,2,0,75,0,0 +3106126,-151.515151515152,4,1,0,60,0,0 +3108066,-151.515151515152,4,2,0,75,0,0 +3108437,-151.515151515152,4,1,0,60,0,0 +3108626,-151.515151515152,4,2,0,75,0,0 +3109177,-151.515151515152,4,1,0,60,0,0 +3111029,-151.515151515152,4,2,0,75,0,0 +3111400,-151.515151515152,4,1,0,60,0,0 +3111589,-151.515151515152,4,2,0,75,0,0 +3112140,-151.515151515152,4,1,0,60,0,0 +3117978,-100,4,2,1,75,0,0 +3126214,-100,4,2,1,75,0,0 +3165474,-100,4,2,1,75,0,1 +3176960,-100,4,2,1,75,0,0 +3177325,-100,4,2,1,75,0,1 +3177695,-100,4,2,1,75,0,0 + + +[Colours] +Combo1 : 12,50,233 +Combo2 : 253,225,28 +Combo3 : 185,17,17 +Combo4 : 30,136,35 +SliderBorder : 0,0,0 + +[HitObjects] +40,48,2405,1,4 +144,344,2824,1,0 +256,48,3243,1,2 +368,344,3662,1,0 +472,48,4081,1,2 +336,216,4500,2,0,B|308:146|192:136|171:225|171:225,1,200,0|2 +256,256,5128,1,0 +256,336,5337,1,0 +92,88,5756,5,2 +256,144,6175,1,0 +420,88,6594,1,2 +400,252,7013,1,0 +256,336,7432,1,2 +112,252,7851,1,0 +92,88,8269,1,2 +336,172,8688,6,0,B|308:102|192:92|171:181|171:181,1,200,0|2 +348,348,9526,1,0 +164,348,9945,1,2 +80,160,10364,1,0 +256,40,10782,1,2 +432,160,11201,1,0 +256,196,11620,1,2 +256,196,11830,1,0 +256,196,12039,1,0 +176,316,12458,6,0,B|204:386|320:396|341:307|341:307,1,200,2|0 +80,224,13296,1,2 +256,44,13714,1,0 +432,224,14133,1,2 +112,92,14552,1,0 +256,272,14971,1,2 +400,92,15390,1,0 +456,336,15829,5,4 +256,44,16247,1,0 +56,336,16666,1,2 +256,272,17085,1,0 +88,68,17504,1,2 +424,68,17923,1,0 +168,208,18342,1,2 +256,168,18551,1,0 +344,208,18760,1,0 +176,312,19179,6,0,B|204:382|320:392|341:303|341:303,1,200,2|0 +472,192,20017,1,2 +256,40,20436,1,0 +40,192,20855,1,2 +256,348,21274,1,0 +170,80,21692,2,0,B|179:110|211:135|252:141|252:141|280:151|302:184|280:217|256:223|227:217|199:184|225:151|252:141|252:141|296:135|323:114|337:72,1,400,2|2 +432,288,22949,5,0 +256,352,23368,1,2 +80,288,23787,1,0 +164,159,24205,2,0,B|208:128|256:168|256:168|304:128|348:158|348:158,1,200,2|0 +180,36,25043,1,2 +256,60,25253,1,0 +332,36,25462,1,0 +472,120,25881,6,0,B|402:148|392:264|481:285|481:285,1,200,2|0 +336,372,26719,2,0,B|308:302|192:292|171:381|171:381,1,200,2|0 +40,280,27556,2,0,B|110:252|120:136|31:115|31:115,1,200,2|0 +180,68,28394,5,2 +256,20,28603,1,0 +332,68,28813,1,0 +302,153,29022,1,0 +208,152,29232,1,0 +256,192,29336,12,0,30907 +40,116,47304,5,2 +100,44,47587,1,0 +172,104,47870,2,0,B|200:174|316:184|337:95|337:95,1,200,0|2 +412,44,48719,1,0 +472,116,49002,1,0 +428,304,49568,5,2 +354,355,49851,1,0 +342,265,50134,1,0 +168,265,50700,1,2 +156,355,50983,1,0 +82,304,51266,1,0 +212,171,51832,6,0,B|228:191|256:195|256:195|284:191|300:171,1,100,2|0 +333,86,52397,2,0,B|312:44|256:28|256:28|204:40|177:87,1,200,0|0 +256,112,53246,1,2 +255,112,53529,1,0 +340,252,54095,2,0,B|360:348|360:348,1,100,2|0 +340,252,54661,2,0,B|312:322|196:332|175:243|175:243,1,200,0|2 +151,349,55510,2,0,B|152:348|180:252,1,100,0|0 +256,112,56359,5,2 +300,40,56642,1,0 +212,40,56925,1,0 +184,129,57208,2,0,B|190:177|240:200|272:200|324:184|329:130|329:130,1,200,0|2 +348,227,58057,2,0,B|314:260|257:270|201:259|164:228,1,200,0|0 +104,288,58905,5,2 +256,368,59471,1,0 +408,288,60037,1,2 +408,288,60320,1,0 +256,200,60886,5,0 +336,160,61169,2,0,B|308:95|199:74|172:168|172:168,1,200,2|0 +300,28,62301,1,2 +212,32,62584,1,0 +212,32,63150,1,0 +300,32,63433,1,2 +342,197,63999,6,0,B|325:162|279:159|255:183|255:183|230:158|184:162|169:197|169:197,1,200,0|2 +168,280,64847,2,0,B|185:315|232:318|256:294|256:294|280:318|326:315|344:280|344:280,1,200,0|2 +432,272,65696,5,0 +400,356,65979,1,0 +336,188,66545,1,2 +256,144,66828,1,8 +176,188,67111,1,0 +112,28,67677,5,2 +84,112,67960,1,8 +20,44,68243,1,0 +216,88,68809,2,0,B|229:52|285:46|295:91|295:91,1,100,2|8 +256,164,69375,1,0 +432,112,69941,5,2 +492,48,70223,1,8 +400,28,70506,1,0 +332,196,71072,5,2 +256,244,71355,1,8 +180,196,71638,1,0 +336,284,72204,2,0,B|328:340|376:372|376:372,1,100,2|8 +336,284,72770,2,0,B|308:354|192:364|171:275|171:275,1,200,0|2 +137,371,73619,2,0,B|136:372|185:344|175:284,1,100,8|0 +156,108,74468,5,2 +212,36,74751,1,8 +300,36,75034,1,0 +356,108,75317,1,0 +256,124,75883,5,8 +256,124,76165,1,0 +384,332,76731,1,0 +384,332,77014,1,8 +256,220,77580,5,0 +128,332,78146,1,8 +128,332,78429,1,0 +256,308,78995,5,0 +256,308,79278,1,8 +300,232,79561,1,0 +256,220,79702,1,0 +212,232,79844,1,0 +112,92,80410,1,8 +176,32,80693,2,0,B|202:104|314:115|334:26|334:26,1,200,0|0 +400,92,81542,1,8 +332,248,82107,2,0,B|307:287|271:282|271:282|255:302|255:302|239:282|239:282|203:290|178:248,1,200,0|8 +104,300,82956,5,0 +168,364,83239,1,2 +256,380,83522,1,0 +344,364,83805,1,8 +408,300,84088,1,0 +336,240,84371,6,4,B|310:312|198:323|178:234|178:234,1,200,2|8 +156,152,85220,2,0,B|396:152|396:152,1,200,2|0 +336,64,86069,2,0,B|310:-8|198:-19|178:70|178:70,1,200,8|2 +48,184,87201,6,0,B|8:284|8:284,1,100,8|0 +100,264,87767,2,0,B|124:360|124:360,1,100,2|0 +188,300,88332,2,0,B|276:360|276:360,1,100,8|0 +316,280,88898,5,2 +376,344,89464,1,8 +376,344,89747,1,0 +408,260,90313,1,0 +408,260,90596,1,8 +480,316,91162,1,2 +464,200,91728,6,0,B|504:100|504:100,1,100,8|0 +412,120,92294,2,0,B|388:24|388:24,1,100,2|0 +324,84,92860,2,0,B|236:24|236:24,1,100,8|0 +196,104,93426,1,2 +256,192,93567,12,0,95696 +448,352,96262,5,2 +376,296,96545,1,0 +304,352,96828,1,2 +240,288,97111,1,0 +200,272,97252,1,0 +184,232,97394,1,0 +200,192,97535,1,0 +240,176,97677,1,0 +280,192,97818,1,0 +296,232,97960,5,4 +376,200,98243,2,0,B|488:200|488:200,1,100,2|8 +512,120,98809,2,0,B|512:16|512:16,1,100,0|8 +432,64,99375,5,2 +360,8,99658,1,8 +310,88,99941,2,0,B|340:125|338:201|254:252|176:198|152:135|204:82|204:82,1,300,2|8 +152,8,101073,1,2 +80,64,101356,1,8 +16,128,101639,6,0,B|16:240|16:240,1,100,2|8 +96,272,102205,2,0,B|160:220|260:296|192:368|192:368,1,200,2|0 +288,384,103054,2,0,B|320:352|320:352|400:352|400:352,1,100,8|0 +447,299,103620,1,10 +363,264,103903,1,0 +305,199,104186,2,0,B|287:169|287:138|318:107|318:107|349:76|349:30|287:15|271:45|256:61|256:61|241:45|225:15|163:30|163:76|194:107|194:107|225:138|225:169|208:199,1,500,8|2 +112,208,105884,5,0 +40,160,106167,1,0 +24,200,106309,1,0 +32,248,106450,1,0 +56,280,106592,1,0 +96,296,106733,1,0 +144,292,106875,1,0 +184,272,107016,6,0,B|296:272|296:272,1,100,4|0 +328,352,107582,2,0,B|216:352|216:352,1,100,8|0 +376,272,108148,2,0,B|456:352|456:352,1,100,2|0 +488,264,108714,2,0,B|408:184|408:184,1,100,8|0 +328,196,109280,1,2 +256,144,109563,1,0 +184,196,109846,1,8 +160,108,110129,6,0,B|216:12|216:12,1,100,0|2 +301,25,110695,2,0,B|296:16|352:112,1,100,0|8 +256,144,111262,2,0,B|256:352|256:352,1,200,2|0 +344,368,112111,2,0,B|440:296|440:296,1,100,8|0 +168,368,112677,2,0,B|72:296|72:296,1,100,2|0 +177,242,113243,6,0,B|202:312|316:323|340:236|340:236,1,200,8|2 +256,200,114092,1,0 +320,136,114375,6,0,B|336:96|376:72|424:96|424:96,2,100,8|0|2 +256,72,115224,1,0 +192,136,115507,2,0,B|176:96|136:70|88:96|88:96,2,100,8|8|4 +172,64,124280,5,0 +256,36,124563,1,0 +340,64,124846,1,0 +256,224,125412,1,2 +256,136,125695,1,8 +332,184,125979,6,0,B|404:210|415:322|326:342|326:342,1,200,0|2 +256,376,126828,1,8 +180,340,127111,2,0,B|108:314|97:202|186:182|186:182,1,200,0|2 +256,136,127960,1,8 +256,224,128243,1,0 +380,84,128809,6,0,B|472:28,1,100,2|8 +476,120,129375,2,0,B|368:184|368:184|368:260,1,200,0|0 +456,296,130224,1,8 +384,348,130507,1,0 +128,348,131073,5,2 +56,296,131356,1,8 +144,258,131639,2,0,B|144:184|144:184|36:120,1,200,0|0 +46,32,132488,2,0,B|132:84,1,100,8|0 +256,196,133337,5,2 +300,124,133620,1,8 +212,124,133903,1,0 +184,213,134186,2,0,B|190:261|240:284|272:284|324:268|329:214|329:214,1,200,0|8 +256,196,135035,1,0 +412,284,135601,5,0 +412,284,135884,1,8 +256,372,136450,1,0 +100,284,137016,1,8 +100,284,137299,1,0 +28,229,137582,2,0,B|108:40|108:40,1,200,0|8 +484,163,138714,2,0,B|404:352|404:352,1,200,0|8 +316,348,139563,5,0 +196,44,140412,1,8 +256,196,140979,1,0 +352,96,141545,6,0,B|365:60|421:54|431:99|431:99,1,100,8|0 +296,260,142111,2,0,B|283:296|227:302|217:257|217:257,1,100,0|0 +84,96,142677,2,0,B|97:60|153:54|163:99|163:99,1,100,8|0 +336,176,143526,5,0 +256,132,143809,1,8 +176,176,144092,1,0 +80,328,144658,5,0 +148,268,144941,2,0,B|216:360|216:360,1,100,8|0 +432,328,145790,1,0 +364,268,146073,2,0,B|296:360|296:360,1,100,8|0 +260,182,146922,1,0 +256,264,147205,1,8 +260,180,147488,1,0 +80,56,148054,5,0 +148,116,148337,2,0,B|216:24|216:24,1,100,8|0 +432,56,149186,1,0 +364,116,149469,2,0,B|296:24|296:24,1,100,8|0 +444,204,150318,5,0 +420,292,150601,1,8 +344,340,150884,1,0 +168,340,151450,1,0 +92,292,151733,1,8 +68,204,152016,1,0 +256,216,152865,5,8 +256,216,153148,1,0 +360,92,153714,5,0 +300,24,153997,1,8 +212,24,154280,1,0 +152,92,154563,1,0 +211,267,155129,2,0,B|227:291|305:301|313:244|313:244,1,100,8|0 +256,188,155695,1,0 +165,339,156262,2,0,B|213:380|313:386|352:337|352:337,1,200,8|0 +256,188,157394,1,8 +200,128,157677,2,0,B|170:91|172:15|256:-36|334:18|358:81|306:134|306:134,1,300,0|8 +439,58,159092,5,0 +439,58,159658,5,8 +420,149,159941,1,0 +348,198,160224,1,8 +256,192,160507,1,0 +163,185,160790,1,8 +91,234,161073,1,0 +72,325,161356,1,4 +256,348,162205,5,0 +256,192,162771,5,0 +192,132,163054,1,8 +212,92,163195,1,0 +256,76,163337,1,0 +300,92,163479,1,0 +320,132,163620,1,0 +72,58,164186,5,8 +91,149,164469,1,0 +163,198,164752,1,8 +256,192,165035,1,0 +348,185,165318,1,8 +420,234,165601,1,0 +439,325,165884,1,8 +184,304,166733,6,0,B|190:352|240:375|272:375|324:359|329:305|329:305,1,200 +256,256,167582,1,8 +256,168,167865,1,0 +72,120,168714,5,8 +128,48,168997,1,0 +212,16,169280,1,8 +300,16,169563,1,0 +384,48,169846,1,8 +440,120,170129,1,0 +256,192,170412,12,0,172677 +36,64,173101,5,0 +36,64,173243,1,0 +36,64,173384,1,0 +36,64,173526,1,0 +96,132,173809,1,0 +168,80,174092,1,0 +212,64,174233,1,0 +252,80,174375,1,0 +264,124,174516,1,0 +288,160,174658,1,0 +332,168,174799,1,0 +372,148,174941,5,4 +428,76,175224,2,0,B|496:164|496:164,1,100,0|8 +433,225,175790,2,0,B|404:326|404:326,1,100,0|2 +320,292,176356,1,0 +272,368,176639,1,8 +196,320,176922,2,0,B|160:364|88:360|40:304|80:244|80:244,1,200 +112,168,177771,2,0,B|108:104|44:88|44:88,1,100,8|0 +128,32,178337,6,0,B|236:32|236:32,1,100,2|0 +272,112,178903,1,8 +356,80,179186,2,0,B|396:40|472:52|508:132|436:180|436:180,1,200 +384,232,180035,2,0,B|340:276|372:332|372:332,1,100,8|0 +284,360,180601,2,0,B|164:360,1,100,2|0 +124,300,181167,2,0,B|66:343|-4:276|33:216|72:200|72:200|105:183|173:149|86:10|12:108|12:108,1,400,8|8 +4,16,182582,5,0 +92,8,182865,1,2 +168,56,183148,1,0 +208,76,183290,1,0 +232,112,183431,1,0 +256,152,183573,1,0 +280,112,183714,1,0 +304,76,183856,1,0 +344,56,183997,1,4 +420,8,184280,1,0 +508,16,184563,1,8 +484,102,184846,6,0,B|424:92|391:141|391:141,1,100,0|2 +369,222,185412,2,0,B|439:321|439:321,1,100,0|8 +342,362,185979,2,0,B|316:322|256:308|200:320|168:364,1,200,2|0 +85,303,186828,2,0,B|73:321|143:222,1,100,8|0 +117,136,187394,2,0,B|121:141|88:92|28:102,1,100,2|0 +124,32,187960,5,8 +220,104,188243,2,0,B|199:79|201:28|257:-5|309:30|325:72|290:108|290:108,1,200,2|0 +388,32,189092,1,8 +460,100,189375,5,0 +392,172,189658,1,2 +468,236,189941,1,0 +398,303,190224,2,0,B|383:345|327:359|298:317|327:288|312:232|270:246|256:352|256:352|241:246|199:232|185:288|213:317|185:359|114:345|114:303,1,500,8|0 +68,308,191780,5,0 +26,288,191922,1,0 +16,245,192063,1,0 +40,207,192205,1,0 +81,189,192346,1,0 +110,154,192488,1,0 +125,114,192629,1,0 +104,73,192771,1,0 +62,54,192912,1,0 +21,72,193054,1,4 +256,40,209752,5,2 +144,96,210035,1,0 +184,112,210177,1,0 +212,148,210318,1,0 +216,192,210460,1,0 +256,212,210601,1,0 +296,192,210743,1,0 +300,148,210884,1,0 +328,112,211026,1,0 +368,96,211167,1,4 +256,192,211309,12,0,214563 +256,32,215129,5,2 +236,183,215686,2,0,B|172:199|130:270|147:356|224:381|284:386|309:374|391:320|389:233|322:176|264:182|264:182,1,500,0|0 +256,268,217384,1,0 +448,64,217950,5,0 +256,124,218516,1,0 +256,124,218799,1,0 +64,64,219365,1,0 +64,64,219648,1,0 +176,260,220214,2,0,B|204:195|313:174|340:268|340:268,1,200,0|0 +336,337,221063,2,0,B|308:402|199:423|172:329|172:329,1,200,0|0 +256,299,221912,1,0 +256,40,222478,5,0 +172,164,223044,1,0 +256,204,223327,1,0 +340,164,223610,1,0 +460,300,224176,5,0 +256,360,224742,1,0 +52,300,225308,1,0 +52,300,225591,5,0 +256,360,226157,1,0 +256,360,226440,1,0 +460,300,227006,1,0 +340,164,227572,5,0 +256,204,227855,1,0 +172,164,228138,1,0 +172,76,228421,1,0 +256,32,228704,1,0 +340,76,228987,1,0 +256,120,229270,5,0 +376,252,229836,1,0 +293,287,230119,2,0,B|314:312|312:363|256:396|204:361|188:319|223:283|223:283,1,200 +136,252,230969,1,0 +176,72,231535,2,0,B|204:137|313:158|340:64|340:64,1,200,0|0 +256,32,232384,1,0 +256,32,232667,5,2 +480,192,233233,1,2 +256,356,233799,1,0 +32,192,234365,1,0 +336,320,234931,6,0,B|308:255|199:234|172:328|172:328,1,200,0|0 +436,112,236063,2,0,B|371:140|350:249|444:276|444:276,1,200,0|0 +176,72,237195,2,0,B|204:137|313:158|340:64|340:64,1,200,0|0 +76,272,238327,2,0,B|141:244|162:135|68:108|68:108,1,200,0|0 +256,208,239459,5,0 +472,288,240025,1,0 +424,92,240591,1,0 +256,340,241157,1,0 +88,92,241723,1,0 +40,288,242289,1,0 +256,208,242855,1,0 +256,44,243421,5,0 +460,340,244553,1,0 +52,340,245685,1,0 +81,156,246252,6,0,B|81:80|139:80|196:80|196:156|196:156|196:233|254:233|311:233|311:156|311:156|311:80|368:80|437:80|427:164,1,600 +348,328,248516,2,0,B|300:369|200:375|161:326|161:326,1,200 +168,152,249648,2,0,B|256:200|256:200|364:140,1,200 +257,103,250497,2,0,B|256:64,3,33.3333333333333 +257,103,251063,1,0 +452,228,251912,6,0,B|376:320,1,100,4|0 +256,356,252478,2,0,B|256:236,1,100,8|0 +123,305,253044,2,0,B|60:228,1,100,0|0 +120,156,253610,1,8 +184,61,253893,2,0,B|190:109|240:132|272:132|324:116|329:62|329:62,1,200,0|0 +392,156,254742,1,8 +300,232,255025,5,0 +256,244,255167,1,0 +212,232,255308,1,0 +152,324,255591,1,0 +256,368,255874,1,8 +360,324,256157,1,0 +256,136,256723,5,0 +180,56,257006,2,0,B|120:156,1,100,8|0 +332,56,257572,2,0,B|392:156,1,100,0|0 +256,136,258138,1,8 +184,208,258421,2,0,B|204:248|240:250|256:258|256:258|272:268|280:288|268:302|256:302|256:302|240:302|230:290|240:268|256:260|256:260|268:250|304:252|328:208|328:208,1,300,0|8 +384,272,259553,5,0 +352,296,259695,1,0 +336,332,259836,1,0 +312,364,259978,1,0 +276,380,260119,1,0 +236,380,260261,1,0 +200,364,260402,1,8 +176,332,260544,1,0 +160,296,260685,1,0 +128,272,260827,1,0 +116,236,260969,6,0,B|48:156|137:48|228:96|255:150|255:150|282:196|338:291|530:151|381:54|381:54,1,600,4|8 +304,32,262950,6,0,B|276:16|240:16|240:16,3,50 +216,16,263516,2,0,B|192:28|140:20|140:20,3,50,0|0|8|0 +128,24,264082,2,0,B|100:36|72:76|72:76,3,50 +68,88,264648,2,0,B|68:120|44:152|44:152|48:152,3,50,0|0|8|0 +44,172,265214,2,0,B|32:200|40:248|40:248,3,50 +52,260,265780,2,0,B|64:288|64:324|64:324,3,50,0|0|8|0 +92,336,266346,2,0,B|104:356|144:372|144:372,3,50 +172,364,266912,2,0,B|208:348|248:364|248:364,3,50,0|0|8|0 +260,368,267478,2,0,B|288:372|328:356|328:356,2,50 +256,192,267902,12,4,270025 +128,184,270308,5,0 +148,72,270591,1,8 +256,32,270874,1,0 +364,72,271157,1,8 +384,184,271440,1,0 +256,152,271723,1,8 +201,261,272006,2,0,B|177:291|185:358|259:403|325:356|346:301|306:252|306:252,1,259.999995350838 +396,352,272855,5,8 +256,291,273138,1,0 +116,352,273421,1,8 +200,196,273704,2,0,B|228:163|314:147|339:225|339:225,1,129.999997675419,0|8 +376,112,274270,2,0,B|320:40|192:36|144:112|144:112,1,259.999995350838 +100,252,275119,6,0,B|204:352,1,129.999997675419,8|0 +318,342,275686,2,0,B|412:252,1,129.999997675419,8|0 +275,87,276252,2,0,B|305:44|435:126|257:242|257:242|80:146|188:33|251:98,1,519.999990701676,8|8 +412,252,277667,6,0,B|308:352,1,129.999997675419,0|8 +194,342,278233,2,0,B|100:252,1,129.999997675419,0|8 +230,87,278799,2,0,B|164:90|145:147|174:218|231:190|231:190|260:255|260:255|288:190|288:190|340:215|372:147|366:77|281:88|281:88,1,519.999990701676 +416,240,280214,5,8 +256,348,280497,1,0 +96,240,280780,1,8 +204,64,281063,2,0,B|180:94|188:161|262:206|328:159|349:104|309:55|309:55,1,259.999995350838,0|0 +416,240,281912,6,0,B|480:120,1,129.999997675419,8|0 +304,336,282478,2,0,B|300:305|268:290|247:290|214:300|211:335|211:335,1,129.999997675419,8|0 +34,125,283044,2,0,B|96:240,1,129.999997675419,8|0 +256,40,283893,5,0 +432,156,284176,1,8 +360,348,284459,1,0 +152,348,284742,1,8 +80,156,285025,1,0 +256,188,285308,1,8 +314,247,285874,5,0 +314,247,286016,1,0 +314,247,286157,1,0 +314,247,286299,1,0 +314,247,286440,1,0 +314,247,286582,1,0 +314,247,286723,1,0 +314,247,286865,1,0 +314,247,287006,1,0 +314,247,287148,1,0 +314,247,287289,1,0 +314,247,287431,1,0 +314,247,287572,1,0 +314,247,287714,1,0 +314,247,287855,1,0 +314,247,287997,1,0 +314,247,288138,1,4 +320,40,288421,6,0,B|184:40|184:40,1,129.999997675419,0|8 +420,256,288987,2,0,B|420:120|420:120,1,129.999997675419,0|8 +192,344,289553,2,0,B|328:344|328:344,1,129.999997675419,0|8 +92,128,290119,2,0,B|92:264|92:264,1,129.999997675419,0|8 +256,192,290686,1,0 +256,192,290827,1,0 +256,192,290969,1,8 +408,254,291252,6,0,B|312:350,1,129.999997675419,0|8 +316,38,291818,2,0,B|408:130,1,129.999997675419,0|8 +104,130,292384,2,0,B|195:38,1,129.999997675419,0|8 +195,345,292950,2,0,B|104:254,1,129.999997675419,0|8 +256,192,293516,1,0 +408,130,293799,6,0,B|312:34,1,129.999997675419,8|0 +316,345,294365,2,0,B|408:254,1,129.999997675419,8|0 +104,254,294931,2,0,B|195:345,1,129.999997675419,8|0 +195,38,295497,2,0,B|104:130,1,129.999997675419,8|0 +256,192,296063,5,0 +256,192,296204,1,0 +256,192,296346,1,0 +256,192,296487,1,0 +256,192,296629,1,0 +256,192,296770,1,0 +256,192,296912,1,0 +256,192,297053,1,0 +256,192,297195,1,4 +212,324,297478,6,0,B|215:354|247:369|267:369|301:358|304:324|304:324,1,129.999997675419,0|8 +52,248,298044,2,0,B|84:192|84:192|44:120|44:120,1,129.999997675419,0|8 +312,40,298610,2,0,B|256:72|256:72|184:32|184:32,1,129.999997675419,0|8 +460,248,299176,2,0,B|428:192|428:192|468:120|468:120,1,129.999997675419,0|8 +312,348,299742,6,0,B|256:316|256:316|184:356|184:356,1,129.999997675419,0|8 +80,148,300308,2,0,B|50:151|35:183|35:203|46:237|80:240|80:240,1,129.999997675419,0|8 +300,36,300874,2,0,B|297:66|265:81|245:81|211:70|208:36|208:36,1,129.999997675419,0|8 +432,240,301440,2,0,B|462:237|477:205|477:185|466:151|432:148|432:148,1,129.999997675419,0|8 +256,192,302006,5,0 +136,272,302289,2,0,B|110:298|74:278|74:278|94:314|69:338|69:338,1,129.999997675419,8|0 +256,344,302855,1,8 +376,272,303138,2,0,B|401:298|437:278|437:278|417:314|442:338|442:338,1,129.999997675419,0|8 +256,192,303704,5,0 +136,112,303987,2,0,B|110:86|74:106|74:106|94:70|69:46|69:46,1,129.999997675419,8|0 +256,40,304553,1,8 +376,112,304836,2,0,B|401:86|437:106|437:106|417:70|442:46|442:46,1,129.999997675419,0|8 +256,192,305402,6,0,B|296:256|296:256|360:256,1,129.999997675419,0|8 +256,192,305969,2,0,B|216:256|216:256|152:256,1,129.999997675419,0|12 +196,352,306535,2,0,B|232:352|232:352|256:336|256:336|280:352|280:352|328:352,1,129.999997675419,0|8 +416,256,307101,5,0 +444,120,307384,1,8 +348,40,307667,2,0,B|340:101|275:131|234:131|167:110|161:41|161:41,1,259.999995350838,0|0 +68,120,308516,1,8 +96,256,308799,1,0 +212,348,309082,6,0,B|215:318|247:303|267:303|301:314|304:348|304:348,1,129.999997675419,8|0 +320,192,309648,2,0,B|184:192,1,129.999997675419,8|0 +192,48,310214,2,0,B|256:68|256:68|320:48,1,129.999997675419,8|0 +372,176,310780,2,0,B|256:228|256:228|132:172,1,259.999995350838,12|8 +68,292,311629,1,0 +256,368,311912,1,8 +444,292,312195,1,0 +348,45,312761,6,0,B|341:106|277:136|236:136|169:115|163:46|163:46,1,259.999995350838,0|0 +82,161,313610,2,0,B|124:224|188:240|188:240,1,129.999997675419,8|0 +328,238,314176,2,0,B|388:216|430:161,2,129.999997675419,8|0|8 +256,356,315025,1,0 +256,356,315308,5,12 +328,240,315591,2,0,B|452:304|452:304,1,129.999997675419,0|8 +184,240,316157,2,0,B|60:304|60:304,1,129.999997675419,0|8 +151,91,316723,2,0,B|220:112|256:40|256:40|292:112|364:92,1,259.999995350838,0|0 +256,356,317855,5,0 +380,284,318138,1,8 +380,140,318421,1,0 +256,68,318704,1,8 +132,140,318987,1,0 +132,284,319270,1,8 +256,208,319553,1,0 +256,192,319695,12,0,320686 +256,192,320827,12,0,322101 +255,57,322950,5,0 +115,303,323233,1,8 +256,192,323516,1,0 +396,304,323799,1,8 +256,192,324082,6,0,B|256:292|256:292|312:292|380:264|392:164|368:108|332:60|244:56|164:52|120:144|136:200|120:264|244:292|244:292,1,649.999988377094,4|0 +32,64,337093,5,8 +48,64,337243,1,0 +64,64,337393,1,8 +256,92,337693,2,0,B|256:316|256:316,1,200,8|8 +432,352,338293,2,0,B|368:304|368:224|432:176|432:176,1,200,8|8 +82,178,338893,2,0,B|134:224|144:304|80:352,1,200,8|8 +124,339,339343,5,0 +167,327,339493,1,8 +210,313,339643,1,0 +253,301,339793,1,8 +293,102,340093,2,0,B|313:77|311:26|255:-7|203:28|187:70|222:106|222:106,1,200,8|8 +432,192,340693,1,8 +256,296,340993,1,8 +80,192,341293,1,8 +208,360,341593,5,8 +180,272,341743,1,0 +256,216,341893,1,8 +332,272,342043,1,0 +304,360,342193,1,8 +356,44,342493,6,0,B|152:44,1,200,8|8 +356,124,343093,2,0,B|144:124,2,200,8|8|8 +408,272,343993,1,8 +348,344,344143,1,0 +256,372,344293,1,8 +164,344,344443,1,0 +104,272,344593,1,8 +256,192,344893,1,8 +256,192,345043,12,4,346693 +344,36,346993,5,8 +164,36,347293,1,8 +216,171,347593,2,0,B|230:139|284:129|297:175|297:175,1,100,8|8 +329,245,347893,2,0,B|319:311|199:343|183:245|183:245,2,200,8|8|8 +172,356,348793,1,8 +256,384,348943,1,8 +340,356,349093,1,8 +344,56,349393,6,0,B|432:112|432:112|456:232,1,200,8|8 +168,56,349993,1,8 +168,56,350143,1,0 +168,56,350293,2,0,B|80:112|80:112|56:232,1,200,8|8 +348,328,350893,2,0,B|256:368|256:368|148:320,2,200,8|8|4 +256,172,351793,5,8 +388,48,352093,1,8 +256,172,352393,1,8 +256,260,352543,1,8 +256,172,352693,1,8 +124,48,352993,1,8 +124,238,353293,2,0,B|192:272|192:336|192:336|240:336|256:368|256:368|272:336|320:336|320:336|320:272|393:240,1,400,8|4 +256,40,354193,5,8 +76,340,354493,1,8 +436,340,354793,1,8 +376,272,354943,6,0,B|410:236|410:236|476:237,1,100,0|8 +462,146,355243,2,0,B|412:144|412:144|368:94,1,100,0|8 +296,148,355543,2,0,B|282:116|228:106|215:152|215:152,1,100,0|8 +132,106,355843,2,0,B|99:144|99:144|50:146,1,100,0|8 +51,237,356143,2,0,B|101:236|101:236|136:272,1,100,0|4 +216,236,356443,6,0,B|230:268|284:278|297:232|297:232,1,100,0|8 +332,316,356743,2,0,B|352:288|352:288|384:288|384:288|404:260,2,100,0|8|0 +256,360,357193,1,8 +176,316,357343,2,0,B|156:288|156:288|124:288|124:288|104:260,2,100,0|8|0 +212,232,357793,1,8 +300,232,357943,1,0 +340,152,358093,2,0,B|339:105|339:105|380:70,1,100,8|0 +299,18,358393,2,0,B|256:40|256:40|206:15,1,100,8|0 +132,70,358693,2,0,B|173:105|173:105|172:152,1,100,4|0 +96,200,358993,5,8 +20,248,359143,1,0 +68,324,359293,1,8 +144,276,359443,1,0 +212,340,359593,2,0,B|228:340|228:340|248:332|248:332|256:344|256:344|264:332|264:332|280:340|280:340|296:340,1,100,8|0 +368,276,359893,1,8 +444,324,360043,1,0 +492,248,360193,1,8 +416,200,360343,1,0 +344,144,360493,6,0,B|376:130|386:76|340:63|340:63,1,100,8|0 +256,36,360793,1,8 +168,64,360943,2,0,B|136:78|126:132|172:145|172:145,1,100,0|4 +200,228,361243,1,0 +148,300,361393,2,0,B|100:280|100:280|88:232|88:232,2,100,8|0|8 +211,364,361843,2,0,B|256:341|256:341|306:366,1,100,0|8 +364,300,362143,2,0,B|412:280|412:280|424:232|424:232,2,100,0|8|0 +312,228,362593,1,8 +304,140,362743,5,0 +332,56,362893,1,8 +256,4,363043,1,0 +180,56,363193,1,8 +208,140,363343,1,0 +200,228,363493,5,4 +116,192,363643,1,0 +52,252,363793,1,8 +92,336,363943,1,0 +184,320,364093,1,8 +256,372,364243,1,0 +328,320,364393,1,8 +420,336,364543,1,8 +460,252,364693,1,8 +396,192,364843,1,8 +312,228,364993,1,8 +294,139,365143,6,0,B|280:171|226:181|213:135|213:135,2,100,8|8|8 +295,49,365593,2,0,B|281:17|227:7|214:53|214:53,2,100,8|8|4 +416,181,366193,6,0,B|359:309|142:350|89:164|89:164,1,400,0|8 +117,324,367093,2,0,B|176:384|364:408|416:309|426:294|426:294,2,300,0|0|8 +192,209,368293,2,0,B|160:113|256:41|352:113|320:209,2,300,0|0|8 +96,109,369493,6,0,B|148:5|256:-27|364:5|416:109,2,400,0|8|2 +168,224,370993,2,0,B|200:264|256:276|312:264|344:224,2,200,8|0|8 +32,184,371893,6,0,B|92:224|92:224|104:292|172:316,1,200,0|8 +256,204,372493,1,2 +340,315,372793,2,0,B|408:292|420:224|420:224|480:184,1,200,8|0 +360,120,373393,2,0,B|308:128|308:128|200:84|212:20|256:0|300:20|312:84|204:128|204:128|152:120,1,400,8|8 +40,204,374293,6,0,B|108:192|176:232|184:308,1,200,0|8 +184,308,374743,1,4 +328,308,375043,1,2 +328,308,375193,2,0,B|336:232|404:192|472:204,1,200,8|4 +340,156,375793,6,0,B|276:200|260:256|256:276|256:276|252:256|236:200|172:156,1,300,8|2 +172,84,376393,2,0,B|204:36|256:24|308:36|340:84,2,200,8|0|8 +296,152,377293,6,0,B|312:212|256:236|200:260|216:320,1,200,0|8 +216,150,377893,2,0,B|200:212|256:236|312:260|295:320,2,200,0|8|2 +256,16,378793,1,8 +140,96,379093,6,0,B|92:240|232:308|256:352|256:352|280:308|420:240|372:96,1,600,0|8 +304,120,380143,2,0,B|256:144|208:120,1,100,2|0 +68,100,380593,6,0,B|96:304|292:280|324:388,1,400,8|8 +188,385,381493,2,0,B|220:280|416:304|444:100,1,400,2|0 +304,72,382393,1,8 +184,148,382693,2,0,B|212:192|256:216|256:216|300:192|328:148,1,200,0|8 +208,72,383293,1,0 +256,204,383593,1,8 +374,272,383893,6,0,B|342:312|342:312|296:328,1,100,8|8 +216,328,384193,2,0,B|169:312|169:312|137:272,1,100,8|8 +137,195,384493,2,0,B|169:155|169:155|216:140,1,100,8|8 +296,140,384793,2,0,B|342:155|342:155|374:195,1,100,8|0 +436,232,385093,6,0,B|484:112|484:112|448:48,1,200,4|8 +380,8,385543,1,0 +348,80,385693,2,0,B|296:56|296:56|256:72|256:72|216:56|216:56|164:80,1,200,0|8 +132,8,386143,1,0 +62,50,386293,2,0,B|28:112|28:112|76:232,1,200,0|8 +120,300,386743,6,0,B|176:356|256:372|336:356|392:300,1,300,0|8 +424,228,387343,1,2 +344,240,387493,2,0,B|312:280|256:296|200:280|168:240,1,200,0|8 +88,228,387943,1,0 +140,168,388093,1,0 +256,140,388243,6,0,B|256:88,6,50,8|0|8|0|8|0|4 +256,140,389293,1,4 +296,212,389893,6,0,B|284:339|148:363|68:339,2,300,6|2|8 +256,140,390943,1,0 +216,212,391093,2,0,B|227:339|363:363|443:339,2,300,0|2|8 +368,268,392293,6,0,B|412:280|460:252|460:196|460:196|460:128,1,200,0|8 +472,56,392743,2,0,B|428:44|384:68|380:124|380:124|380:192,1,200 +300,192,393193,2,0,B|300:72|300:72|300:16|256:-4|212:16|212:72|212:72|212:192,1,400,8|8 +132,191,393943,2,0,B|132:124|132:124|128:68|84:44|40:56,1,200,2|0 +52,131,394393,2,0,B|52:196|52:196|52:252|100:280|144:268,1,200,8|0 +296,316,394993,6,0,B|440:264|424:68,1,300,8|2 +344,88,395593,2,0,B|364:244|256:288|148:244|168:88,1,400,8|8 +87,71,396343,2,0,B|72:264|216:316,1,300,2|8 +204,108,397093,6,0,B|336:108|336:108|400:140,1,200,0|8 +360,212,397543,1,2 +308,276,397693,2,0,B|176:276|176:276|112:244,1,200,0|8 +152,172,398143,1,0 +128,96,398293,6,0,B|48:32,2,100,8|0|8 +176,32,398743,2,0,B|256:92|256:92|336:32,1,200,0|8 +384,96,399193,2,0,B|464:32,2,100,8|0|4 +404,256,399793,6,0,B|320:280|320:280|348:340|304:388,1,200,8|0 +256,324,400243,1,0 +205,384,400393,2,0,B|164:340|192:280|192:280|108:256,1,200,8|0 +124,180,400843,2,0,B|204:204|204:204|240:216|260:236|264:244|256:256|248:244|252:236|272:216|308:204|308:204|388:180,1,300,2|2 +364,20,401593,6,0,B|292:24|228:68|216:132,1,200,8|0 +296,132,402043,2,0,B|284:68|220:24|148:20,1,200,2|0 +148,100,402493,1,0 +256,216,402793,1,8 +364,100,403093,6,0,B|384:116|400:144|400:204|400:204|400:292,1,200,0|8 +256,356,403693,1,0 +112,290,403993,2,0,B|112:204|112:204|112:144|128:116|148:100,1,200,8|4 +188,256,404593,6,0,B|200:200|256:164|312:200|324:256,2,200,8|0|8 +112,228,405343,1,0 +40,188,405493,2,0,B|60:160|94:147|128:148|156:160|156:160|176:136|216:120,1,200,2|8 +296,120,405943,2,0,B|336:136|356:160|356:160|384:148|417:147|452:160|472:188,1,200 +400,228,406393,1,8 +256,296,406693,6,0,B|336:344|336:344|448:328,2,200,0|8|2 +256,216,407443,1,0 +256,136,407593,2,0,B|176:88|176:88|64:104,2,200,8|0|8 +324,176,408343,5,0 +324,256,408493,1,0 +256,300,408643,1,0 +188,256,408793,1,0 +188,176,408943,1,0 +256,136,409093,1,4 +392,52,409393,6,0,B|396:144|432:192|496:204,1,200,0|8 +496,124,409843,2,0,B|433:135|397:183|393:275,1,200,2|0 +472,272,410293,2,0,B|468:328|432:360|372:360|372:360|316:360,1,200,0|8 +296,284,410743,1,0 +216,284,410893,1,0 +191,360,411043,6,0,B|140:360|140:360|80:360|44:328|40:272,1,200 +118,275,411493,2,0,B|114:183|78:135|16:124,1,200,2|8 +17,203,411943,2,0,B|80:192|116:144|120:52,1,200 +200,72,412393,6,0,B|196:140|256:164|316:140|312:72,1,200,8|0 +376,136,412843,2,0,B|348:208|256:244|164:208|136:136,1,300,2|0 +25,32,413593,5,8 +24,31,413893,2,0,B|61:43|61:43|53:76|53:76|86:88|86:88|73:121|73:121|94:141|94:141|90:178|-3:154|-27:269|49:343|155:302|155:192|155:192|217:192|217:192|217:228|217:228|225:273|267:302|267:302|295:314|308:351|287:384|253:392|221:384|200:351|213:314|241:302|241:302|283:273|291:228|291:228|291:192|291:192|353:192|353:192|353:302|459:343|537:269|513:154|418:178|414:141|414:141|435:121|435:121|422:88|422:88|455:76|455:76|447:43|447:43|484:31,1,1600,0|4 +340,104,416593,5,0 +256,104,416743,1,0 +300,32,416893,1,0 +212,32,417043,1,0 +172,104,417193,1,0 +212,172,417343,1,0 +300,172,417493,1,4 +460,216,417793,5,0 +392,328,418093,1,4 +252,380,418393,1,0 +252,380,418543,1,0 +252,380,418693,1,4 +120,328,418993,1,8 +52,216,419293,1,0 +208,144,419593,5,8 +296,144,419743,1,0 +328,59,419893,2,0,B|318:-7|198:-39|182:59|182:59,1,200,0|8 +128,132,420343,2,0,B|212:176|212:176|212:304,1,200 +300,281,420793,2,0,B|300:176|300:176|384:132,1,200,8|0 +472,156,421243,1,0 +408,220,421393,5,8 +352,384,421693,2,0,B|288:384|288:384|256:368|256:368|224:384|224:384|136:384|136:384,1,200,0|8 +104,220,422293,1,0 +156,48,422593,5,8 +180,140,422743,2,0,B|220:136|248:100|256:81|256:81|264:100|288:132|336:140,1,200 +356,48,423193,1,8 +256,204,423493,5,4 +324,368,423793,2,0,B|396:300,1,100,8|0 +188,368,424243,2,0,B|116:300,1,100,0|8 +216,153,424693,2,0,B|164:204|200:264|235:275|268:272|296:280|352:228|333:181|292:148|292:148,2,300,0|0|8 +348,44,425893,2,0,B|256:8|256:8|160:44,1,200,0|8 +53,188,426493,6,0,B|120:201|143:318|50:338|50:338,1,200,0|8 +216,360,427093,2,0,B|230:328|284:318|297:364|297:364,1,100,8|8 +344,288,427393,2,0,B|368:244|368:244|344:200,1,100,8|8 +296,132,427693,2,0,B|282:164|228:174|215:128|215:128,1,100,8|8 +168,204,427993,2,0,B|144:247|144:247|168:291,1,100,8|8 +256,248,428293,1,4 +456,340,428593,6,0,B|389:327|366:210|459:190|459:190,1,200,8|8 +336,56,429193,1,8 +256,92,429343,1,0 +176,56,429493,1,8 +256,228,429793,1,8 +334,269,429943,2,0,B|315:331|198:354|174:264|174:264,1,200 +120,332,430393,1,8 +105,147,430693,2,0,B|131:68|197:12|304:11|381:62|408:154,1,400,8|8 +392,332,431593,1,8 +184,248,431893,6,0,B|197:291|228:301|256:310|256:310|279:322|279:350|256:361|232:350|232:322|256:310|256:310|279:301|310:291|328:248,2,300,8|8|8 +328,136,433093,2,0,B|314:92|283:82|256:73|256:73|232:61|232:33|256:22|279:33|279:61|256:73|256:73|232:82|201:92|184:136,2,300,8|8|8 +64,60,434293,5,4 +256,248,434743,1,8 +448,60,435193,1,8 +309,184,435493,2,0,B|378:252|330:333|283:348|239:344|201:354|133:282|153:215|208:180|208:180,1,400,4|8 +86,328,436393,5,8 +256,248,436693,1,0 +426,328,436993,1,0 +304,160,437293,1,0 +328,72,437443,1,0 +256,24,437593,1,0 +184,72,437743,1,0 +208,160,437893,1,4 +336,304,438193,2,0,B|317:366|200:389|176:299|176:299,1,200 +49,138,438793,1,0 +149,31,439093,6,0,B|166:122|257:140|257:140|330:158|366:213|366:267|293:340|220:340|147:267|147:213|184:158|257:140|257:140|348:122|366:31,1,800 +453,145,440593,5,0 +370,358,440893,1,0 +142,357,441193,1,0 +59,145,441493,1,0 +256,20,441793,1,0 +179,207,442093,6,0,B|198:145|315:122|339:212|339:212,1,200 +256,256,442543,1,0 +256,352,442693,1,4 +64,332,452293,6,0,B|64:252|64:252|64:204|96:172|136:172,1,200,4|0 +212,84,452893,2,0,B|212:164|212:164|212:212|180:244|140:244,1,200,8|0 +300,300,453493,6,0,B|300:219|300:219|300:171|332:139|371:139,1,200 +448,50,454093,2,0,B|448:130|448:130|448:178|416:210|376:210,1,200,8|0 +200,232,454693,6,0,B|176:184|176:184|132:184|100:172|80:136|84:96,1,200 +136,36,455143,1,2 +160,108,455293,2,0,B|212:108|212:108|256:128|256:128|300:108|300:108|352:108,1,200,8|0 +376,36,455743,1,2 +428,96,455893,2,0,B|432:136|412:172|380:184|336:184|336:184|312:232,1,200 +128,272,456493,2,0,B|152:324|216:332|244:304|256:296|256:296|268:304|296:332|360:324|384:272,1,300,8|0 +428,208,457093,6,0,B|388:180|336:196|312:240|312:240|256:208|256:208|200:240|200:240|176:196|124:180|84:208,1,400,4|8 +28,152,457843,2,0,B|80:116|136:108|176:124|176:124|216:140,1,200 +295,140,458293,2,0,B|336:124|336:124|376:108|432:116|484:152,2,200,2|0|8 +156,232,459193,6,0,B|356:232,1,200 +424,188,459643,1,2 +476,252,459793,1,0 +432,320,459943,2,0,B|404:372|404:372|324:372|324:372|296:320,1,200,0|2 +216,318,460393,2,0,B|188:372|188:372|108:372|108:372|80:320,1,200,0|2 +36,252,460843,1,0 +88,188,460993,1,0 +216,92,461293,6,0,B|176:52,2,50 +296,92,461593,2,0,B|336:52,2,50 +296,172,461893,2,0,B|384:172|384:172|424:172|456:192|472:232,1,200,4|8 +464,312,462343,2,0,B|424:304|400:280|388:252|388:252|320:252|320:252|296:272,1,200,0|2 +216,272,462793,2,0,B|192:252|192:252|124:252|124:252|112:280|88:304|48:312,1,200,8|0 +40,230,463243,2,0,B|56:192|88:172|128:172|128:172|216:172,1,200,0|2 +216,92,463693,2,0,B|216:56|216:56|216:28|240:4|272:4|296:28|296:56|296:56|296:92,1,200,0|8 +424,192,464293,6,0,B|464:264|464:264|384:352,1,200,0|8 +127,351,464893,2,0,B|48:264|48:264|88:192,1,200,0|8 +140,252,465343,1,0 +164,176,465493,2,0,B|204:188|236:244|204:300|204:300|308:300|308:300|276:244|308:188|348:176,1,400,2|0 +324,100,466243,1,0 +256,144,466393,1,8 +188,104,466543,1,0 +256,56,466693,1,0 +408,108,466993,6,0,B|400:152|364:180|320:184|320:184|284:228|256:216|228:228|192:184|192:184|148:180|112:152|104:108,1,400,8|8 +24,244,467893,2,0,B|148:256|148:256|176:284|212:292,1,200,0|8 +299,292,468343,2,0,B|336:284|364:256|364:256|488:244,1,200,2|0 +448,324,468793,6,0,B|404:328|404:328|336:372|256:388|176:372|108:328|108:328|64:324,1,400,8|8 +96,252,469543,2,0,B|200:208|216:104,1,200,0|2 +295,103,469993,2,0,B|312:208|416:252,1,200,8|0 +360,312,470443,5,0 +296,268,470593,1,0 +256,204,470743,1,0 +216,268,470893,1,0 +152,312,471043,1,0 +216,360,471193,1,0 +296,360,471343,1,0 +380,384,471493,6,0,B|436:368|456:304|432:252|376:236,1,200,4|0 +396,160,471943,1,2 +320,184,472093,2,0,B|300:136|300:136|256:116|256:116|212:136|212:136|192:184,1,200,0|8 +116,160,472543,1,2 +134,236,472693,2,0,B|80:252|56:304|76:368|132:384,1,200,0|8 +156,312,473143,2,0,B|356:312,1,200 +336,236,473593,1,8 +256,236,473743,1,0 +176,236,473893,6,0,B|96:240|44:192|32:124,1,200,2|8 +72,56,474343,2,0,B|152:52|204:100|216:168,1,200 +296,166,474793,2,0,B|308:100|360:52|440:56,1,200,8|2 +479,125,475243,2,0,B|468:192|416:240|336:236,1,200 +256,236,475693,1,0 +212,304,475843,1,0 +256,372,475993,1,0 +300,304,476143,1,0 +256,236,476293,6,0,B|256:32,1,200,4|0 +336,36,476743,1,2 +416,36,476893,2,0,B|416:76|384:112|340:116|340:116|340:160|340:200,1,200,0|8 +172,195,477493,2,0,B|172:160|172:116|172:116|128:112|96:76|96:36,1,200,0|8 +176,36,477943,2,0,B|224:44|244:88|244:88|268:88|268:88|288:44|336:36,1,200 +352,116,478393,6,0,B|332:152|316:156|296:164|296:164|296:220|296:220|296:288,2,200,8|2|8 +160,116,479293,2,0,B|180:152|196:156|216:164|216:164|216:220|216:220|216:288,2,200,0|8|0 +120,184,480043,5,0 +140,260,480193,1,0 +216,288,480343,1,0 +256,356,480493,1,0 +296,288,480643,1,0 +372,260,480793,1,0 +392,184,480943,1,0 +377,106,481093,6,0,B|449:94|503:168|451:239,1,200,4|0 +392,184,481543,2,0,B|341:252|395:327|467:315,1,200,2|0 +412,388,481993,2,0,B|312:372|304:316|320:268|280:280|256:260|232:280|192:268|208:316|200:372|100:388,1,400,8|8 +43,315,482743,2,0,B|116:327|170:252|120:184,1,200,2|0 +59,238,483193,2,0,B|8:168|62:94|135:106,1,200,8|0 +296,264,483793,6,0,B|372:264|372:264|400:240|400:240|432:256|432:256|456:244|456:244|480:244,2,200,8|0|8 +256,196,484543,1,2 +216,120,484693,2,0,B|140:120|140:120|112:144|112:144|80:128|80:128|56:140|56:140|32:140,2,200,0|8|0 +351,32,485593,6,0,B|276:104|360:264|480:68|540:348|412:348|352:348|352:348|295:348,1,500,8|0 +216,348,486493,2,0,B|160:348|160:348|100:348|-28:348|32:68|152:264|236:104|160:32,1,500,2|0 +104,88,487393,5,8 +256,204,487693,1,2 +408,88,487993,1,8 +188,164,488293,5,8 +188,84,488443,1,8 +256,40,488593,1,8 +324,84,488743,1,8 +324,164,488893,1,8 +256,204,489043,1,8 +188,248,489193,1,8 +324,248,489343,1,8 +256,292,489493,6,0,B|256:340,8,50,8|0|8|0|8|0|8|0|8 +324,248,490243,1,0 +296,172,490393,1,8 +216,172,490543,1,0 +188,248,490693,1,4 +176,80,490993,6,0,B|195:18|312:-5|336:85|336:85,1,200,8|8 +396,252,491593,2,0,B|392:192|442:160|442:160,1,100,8|0 +488,240,491893,1,8 +340,340,492193,1,8 +295,265,492343,2,0,B|317:234|297:196|271:187|253:182|228:183|180:218|200:250|227:280|227:280,1,200 +172,340,492793,1,8 +24,240,493093,1,8 +73,165,493243,2,0,B|118:192|116:252,1,100,0|8 +169,78,493693,6,0,B|223:78|223:78|255:46|255:46|287:78|287:78|343:78,1,200,8|8 +375,277,494293,6,0,B|346:370|169:405|133:269|133:269,2,300,8|8|8 +315,126,495493,2,0,B|347:77|317:21|282:6|248:-1|209:2|143:56|170:103|211:148|211:148,2,300,8|8|8 +184,248,496693,2,0,B|197:291|228:301|256:310|256:310|279:322|279:350|256:361|232:350|232:322|256:310|256:310|279:301|310:291|328:248,2,300,8|8|8 +101,109,497893,6,0,B|104:69|146:40|188:52|188:52|217:4|256:6|297:6|321:51|321:51|363:38|412:70|414:121,1,400,8|8 +339,285,498793,2,0,B|332:301|311:315|290:309|290:309|275:333|255:336|232:331|221:310|221:310|202:316|175:303|169:277,2,200,8|8|8 +256,86,499693,5,0 +184,138,499843,1,0 +212,222,499993,1,0 +300,222,500143,1,0 +328,138,500293,1,4 +424,276,500593,5,0 +256,372,500893,1,0 +88,276,501193,1,0 +88,276,501343,1,0 +88,276,501493,1,0 +160,92,501793,1,0 +160,92,501943,1,0 +352,92,502243,1,0 +352,92,502393,1,0 +256,248,502693,5,0 +301,170,502843,2,0,B|283:152|255:151|229:152|210:170,1,100 +338,292,503293,2,0,B|316:352|200:377|175:288|175:288,1,200 +91,154,503893,6,0,B|44:150|21:102|10:42|65:7|131:-10|181:42|166:103|166:103,2,300 +421,154,505093,2,0,B|468:150|491:102|502:42|447:7|381:-10|331:42|346:103|346:103,2,300 +213,259,506293,2,0,B|161:310|197:370|232:381|265:378|293:386|349:334|330:287|289:254|289:254,2,300 +170,74,507493,6,0,B|224:74|224:74|256:42|256:42|288:74|288:74|344:74,1,200 +448,224,508093,1,0 +330,360,508393,2,0,B|315:315|255:300|255:300|195:315|182:360,1,200 +64,224,508993,1,0 +256,152,509293,5,0 +328,100,509443,1,0 +300,16,509593,1,0 +212,16,509743,1,0 +184,100,509893,1,4 +256,264,510193,1,0 +376,72,510493,1,0 +136,76,510793,1,0 +380,73,511093,6,0,B|445:147|468:248|406:346|328:392|268:399|149:402|86:328|25:223|84:96|152:64,1,800 +256,196,512593,1,0 +256,196,512893,5,0 +180,240,513043,1,0 +180,328,513193,1,0 +256,372,513343,1,0 +332,328,513493,1,0 +332,240,513643,1,0 +336,152,513793,6,0,B|368:138|378:84|332:71|332:71,1,100,0|0 +256,32,514093,1,0 +176,72,514243,2,0,B|144:86|134:140|180:153|180:153,1,100 +256,196,514543,1,0 +182,251,514693,2,0,B|256:319|256:319|326:249,1,200,4|0 +335,255,515068,1,0 +344,263,515143,1,0 +352,271,515218,1,0 +360,279,515293,2,0,B|415:295|465:274|495:228|488:171,1,200,8|0 +398,188,515743,1,0 +386,180,515818,1,0 +374,172,515893,2,0,B|318:155|268:176|238:222|245:279,2,200,8|0|8 +356,28,516793,1,8 +176,24,517093,5,0 +80,176,517393,1,8 +88,180,517468,1,0 +96,184,517543,1,0 +104,188,517618,1,0 +112,192,517693,2,0,B|168:175|218:196|248:242|241:299,1,200,8|0 +144,288,518143,1,0 +136,292,518218,1,0 +128,296,518293,2,0,B|73:312|23:291|-7:245|0:188,1,200,8|8 +48,100,518743,1,0 +48,100,518818,1,0 +48,100,518893,2,0,B|124:100|124:100|168:100|208:72|216:32,1,200,0|8 +464,284,519493,2,0,B|388:284|388:284|344:284|304:312|296:352,1,200,4|8 +216,352,519943,6,0,B|168:352,2,50,0|0|8 +296,32,520393,2,0,B|344:32,2,50,8|0|0 +204,68,520693,2,0,B|248:120|248:120|380:120,1,200,0|8 +308,316,521293,2,0,B|264:264|264:264|132:264,1,200,0|8 +24,96,521893,6,0,B|56:136|56:136|156:124|156:124|188:160,1,200,0|8 +256,232,522343,2,0,B|256:280,2,50,0|0|8 +324,159,522643,1,0 +324,159,522793,2,0,B|356:124|356:124|456:136|456:136|488:96,1,200,8|8 +428,16,523243,5,4 +24,288,523693,5,8 +84,368,523843,1,4 +256,120,524293,5,4 +452,156,524593,2,0,B|424:100,2,50,8|0|0 +504,240,524893,2,0,B|456:280|360:252|348:176,1,200,8|0 +163,176,525493,2,0,B|152:252|56:280|8:240,1,200,0|8 +60,156,525943,1,0 +60,156,526093,2,0,B|88:100,2,50,8|0|0 +60,156,526393,1,0 +300,336,526693,6,0,B|340:280|340:280|384:292|440:284|460:248,1,200,0|8 +388,180,527143,2,0,B|404:128,2,50,0|0|8 +124,180,527593,2,0,B|108:128,2,50,8|0|0 +52,249,527893,2,0,B|72:284|128:292|172:280|172:280|212:336,1,200,0|8 +256,140,528493,1,0 +256,140,528793,2,0,B|256:40,2,100,8|0|4 +460,192,529393,6,0,B|528:192,2,50,8|0|0 +360,192,529693,1,8 +52,188,529993,2,0,B|-16:188,2,50,8|0|0 +152,192,530293,1,8 +312,308,530593,1,8 +256,108,530893,1,0 +200,308,531193,1,8 +172,112,531493,6,0,B|172:60|212:16|276:12|316:52,1,200,0|8 +340,272,532093,2,0,B|340:324|300:368|236:372|196:332,1,200,0|8 +56,192,532543,5,0 +56,192,532618,1,0 +56,192,532693,1,0 +8,100,532843,1,4 +460,192,533293,5,8 +504,284,533443,1,4 +256,192,550693,12,4,553093 +48,116,553393,6,0,B|114:126|146:246|48:262|48:262,1,200,0|0 +183,36,553993,2,0,B|193:102|313:134|329:36|329:36,1,200,0|0 +464,262,554593,2,0,B|398:252|366:132|464:116|464:116,1,200,0|0 +300,352,555193,1,0 +328,268,555343,1,0 +256,212,555493,1,0 +184,268,555643,1,0 +212,352,555793,1,0 +108,200,556093,5,0 +168,40,556393,1,0 +344,40,556693,1,0 +404,200,556993,1,0 +256,288,557293,1,0 +256,108,557593,5,0 +212,186,557743,1,0 +301,185,557893,1,4 +174,312,558193,2,0,B|183:356|231:356|255:340|255:340|279:356|327:356|335:308,1,200 +470,164,558793,2,0,B|425:158|418:126|411:107|417:88|417:88|398:95|379:89|351:80|344:41,1,200 +166,43,559393,2,0,B|160:80|132:89|113:95|94:88|94:88|100:107|93:126|86:158|42:164,1,200 +152,288,559993,2,0,B|192:384,1,100 +256,320,560293,1,4 +321,380,560443,2,0,B|360:288,1,100 +295,119,560893,2,0,B|317:88|297:50|271:41|253:36|228:37|180:72|200:104|227:134|227:134,1,200 +56,200,561493,5,0 +128,272,561643,1,0 +164,298,561718,1,0 +181,339,561793,1,0 +256,264,561943,1,0 +331,339,562093,1,0 +348,298,562168,1,0 +384,272,562243,1,0 +432,176,562393,1,0 +360,88,562543,1,0 +256,56,562693,1,4 +152,88,562843,1,0 +80,176,562993,1,0 +213,343,563293,6,0,B|199:287|199:287|254:259|254:259|310:287|310:287|296:343,1,240.000005722046 +437,158,563893,2,0,B|471:123|471:123|465:38|381:32|381:32|341:72,1,240.000005722046 +166,67,564493,2,0,B|131:32|131:32|47:38|41:123|41:123|76:158,1,240.000005722046 +200,248,564943,2,0,B|176:304|176:304|200:360,1,120.000002861023,0|4 +312,358,565243,2,0,B|336:304|336:304|312:248,1,120.000002861023 +256,4,565693,5,0 +456,96,565993,1,0 +456,296,566293,1,0 +256,376,566593,1,0 +56,296,566893,1,0 +56,96,567193,1,0 +191,119,567343,2,0,B|231:167|191:231,1,120.000002861023 +320,229,567643,2,0,B|280:165|320:117,1,120.000002861023 +354,331,568093,6,0,B|303:321|271:347|254:380|254:380|238:347|205:321|155:331,1,240.000005722046 +157,90,568693,2,0,B|207:99|239:73|256:40|256:40|272:73|305:99|355:90,1,240.000005722046 +477,296,569293,1,0 +256,204,569593,1,0 +212,348,569743,1,0 +332,264,569893,1,0 +180,264,570043,1,0 +300,348,570193,1,0 +32,296,570493,1,0 +160,96,570793,6,0,B|182:21|324:-6|353:102|353:102,2,240.000005722046 +112,336,571693,2,0,B|161:288|160:224,1,120.000002861023 +256,280,571993,1,0 +352,229,572143,2,0,B|351:288|400:336,1,120.000002861023,0|4 +356,64,572593,6,0,B|306:48|289:14|289:14|256:31|222:14|222:14|205:48|155:64|155:64,1,240.000005722046,0|0 +376,192,573193,2,0,B|136:192,1,240.000005722046 +356,320,573793,2,0,B|306:336|289:370|289:370|256:353|222:370|222:370|205:336|155:320|155:320,1,240.000005722046 +153,67,574393,6,0,B|200:83|208:137|192:165|172:188|144:206|71:203|65:157|70:105|70:105,1,259.999995350838,0|4 +359,67,574993,2,0,B|311:83|303:137|319:165|339:188|367:206|440:203|446:157|441:105|441:105,1,259.999995350838 +367,348,575593,2,0,B|297:348|297:348|256:306|256:306|215:348|215:348|143:348,1,259.999995350838 +64,128,576193,1,0 +364,64,576493,2,0,B|328:10|256:-25|183:10|147:64,1,259.999995350838 +196,179,576943,2,0,B|224:208|286:211|320:176,1,129.999997675419,0|4 +138,298,577393,2,0,B|171:339|257:370|336:339|373:299,1,259.999995350838 +440,144,577993,5,0 +256,32,578293,1,0 +72,144,578593,1,0 +180,308,578893,1,0 +256,360,579043,1,0 +336,296,579193,1,0 +304,216,579343,1,0 +208,216,579493,1,4 +128,152,579643,5,0 +92,56,579793,2,0,B|172:44|212:104|212:104,1,129.999997675419 +303,99,580093,2,0,B|347:46|420:56,1,129.999997675419 +384,152,580393,1,0 +304,216,580543,1,0 +410,291,580693,5,4 +400,311,580768,1,0 +383,327,580843,1,0 +364,339,580918,1,0 +343,345,580993,1,0 +322,347,581068,1,0 +299,342,581143,1,0 +278,333,581218,1,0 +260,320,581293,1,4 +234,333,581368,1,0 +213,342,581443,1,0 +190,347,581518,1,0 +169,345,581593,1,0 +148,339,581668,1,0 +129,327,581743,1,0 +112,311,581818,1,0 +102,291,581893,1,4 +188,252,582043,5,0 +324,252,582193,1,0 +256,188,582343,1,0 +352,152,582493,1,0 +160,152,582643,1,0 +256,92,582793,1,0 +256,92,582943,1,0 +160,52,583093,6,0,B|96:52|32:108|44:192|104:240,1,259.999995350838,0|0 +256,312,583543,1,0 +408,240,583693,2,0,B|468:192|480:108|416:52|352:52,1,259.999995350838 +352,152,584143,1,0 +340,296,584293,2,0,B|316:228|256:200|256:200|196:228|172:296,1,259.999995350838 +256,312,584743,1,0 +172,294,584893,2,0,B|52:244,1,129.999997675419 +36,152,585193,2,0,B|156:204,1,129.999997675419 +236,232,585493,6,0,B|252:180|216:136|216:136|72:72,1,259.999995350838 +292,108,585943,2,0,B|408:44,1,129.999997675419 +320,184,586243,2,0,B|452:184,1,129.999997675419 +312,268,586543,1,0 +312,268,586693,6,0,B|432:316,1,129.999997675419,4|0 +332,376,586993,2,0,B|296:328|292:256|360:184|444:200,1,259.999995350838 +180,376,587593,2,0,B|216:328|220:256|152:184|68:200,1,259.999995350838 +96,124,588043,2,0,B|172:128|216:172,1,129.999997675419 +416,124,588343,2,0,B|340:128|296:172,1,129.999997675419 +140,52,588793,2,0,B|200:64|256:108|256:108|312:64|372:52,1,259.999995350838 +256,12,589243,1,0 +256,12,589393,1,0 +372,224,589693,5,0 +140,224,589993,1,0 +292,360,590293,2,0,B|337:315|305:263|275:254|246:256|222:249|174:294|190:335|226:364|226:364,2,259.999995350838 +364,141,591193,1,0 +256,101,591343,1,0 +148,141,591493,1,4 +363,54,591793,6,0,B|327:0|255:-35|182:0|146:54,2,259.999995350838 +256,103,592543,1,0 +362,150,592693,2,0,B|326:204|254:239|181:204|145:150,1,259.999995350838 +138,299,593143,1,0 +380,297,593443,1,0 +379,296,593593,1,0 +368,184,593893,6,0,B|308:180|244:244|252:324|304:376,1,259.999995350838 +380,288,594343,1,0 +272,320,594493,2,0,B|224:272|152:288,1,129.999997675419 +64,216,594793,2,0,B|20:272|52:340,1,129.999997675419 +144,200,595093,6,0,B|204:204|268:140|260:60|208:8,1,259.999995350838 +132,92,595543,1,0 +240,64,595693,2,0,B|288:112|360:96,1,129.999997675419 +448,168,595993,2,0,B|492:112|460:44,2,129.999997675419,0|0|4 +136,320,596593,1,0 +136,320,596743,1,0 +376,320,597043,1,0 +376,320,597193,1,0 +376,320,597493,6,0,B|424:268|422:191|375:126|311:112,1,259.999995350838 +136,320,597943,2,0,B|87:268|89:191|136:126|200:112,1,259.999995350838 +256,48,598393,1,0 +196,348,598693,2,0,B|256:392|316:348,1,129.999997675419 +256,272,598993,1,0 +256,272,599143,1,0 +308,144,599293,6,0,B|304:76|344:16|432:0|492:60,1,259.999995350838 +412,132,599743,1,0 +336,276,599893,2,0,B|340:204|256:148|172:204|176:276,1,259.999995350838 +100,132,600343,1,0 +25,55,600493,2,0,B|79:0|167:15|207:75|203:143,1,259.999995350838 +256,272,600943,1,0 +256,192,601018,12,0,602293 +256,192,602368,12,0,603493 +180,344,603793,6,0,B|205:286|205:286|257:262|257:262|310:286|310:286|334:344,1,240.000005722046 +488,176,604393,2,0,B|434:168|426:130|417:108|424:85|424:85|402:93|379:86|346:75|337:29,1,240.000005722046 +172,32,604993,2,0,B|164:76|131:86|108:93|86:85|86:85|93:108|85:130|76:168|24:176,1,240.000005722046 +168,320,605593,1,0 +256,360,605743,1,0 +344,320,605893,1,4 +453,145,606193,5,0 +256,20,606493,1,0 +59,145,606793,1,0 +142,357,607093,1,0 +370,358,607393,1,0 +256,188,607693,1,0 +315,98,607843,2,0,B|259:68|186:76|145:139|146:199,1,240.000005722046 +197,285,608293,2,0,B|250:314|325:307|366:246|366:183,1,240.000005722046 +464,288,608893,5,0 +256,156,609193,1,0 +48,288,609493,1,0 +166,112,609793,2,0,B|184:57|256:39|256:39|328:57|344:112,1,240.000005722046 +320,192,610243,2,0,B|320:256|255:272|255:272|182:292|198:370,1,240.000005722046 +314,370,610693,2,0,B|330:292|256:272|256:272|192:256|192:192,1,240.000005722046,4|0 +384,80,611293,5,0 +128,80,611593,1,0 +128,304,611893,1,0 +384,304,612193,1,0 +256,128,612493,1,0 +165,185,612643,2,0,B|217:196|249:228|257:252|257:252|265:228|289:196|352:183,1,240.000005722046 +346,280,613093,6,0,B|327:359|188:394|160:272|160:272,1,240.000005722046 +64,88,613693,1,0 +228,32,613993,2,0,B|256:24|256:24|284:32,4,60.0000014305115 +164,136,614443,2,0,B|200:136|200:136|224:112|224:112|256:152|256:152|288:112|288:112|312:136|312:136|352:136,1,240.000005722046 +420,208,614893,1,0 +368,300,615043,1,0 +256,344,615193,1,0 +144,300,615343,1,0 +92,208,615493,1,4 +420,48,615793,5,0 +92,48,616093,1,0 +92,336,616393,1,0 +420,336,616693,1,0 +256,40,616993,1,0 +88,200,617293,1,0 +160,272,617443,1,0 +256,304,617593,1,0 +352,272,617743,1,0 +424,200,617893,1,0 +164,76,618193,6,0,B|183:155|322:190|350:68|350:68,1,240.000005722046 +345,308,618793,2,0,B|323:230|188:196|161:312|161:312,1,240.000005722046 +388,192,619393,1,0 +124,192,619693,1,0 +200,108,619843,2,0,B|256:128|256:128|312:108,1,120.000002861023 +256,16,620143,1,0 +396,48,620293,6,0,B|451:66|469:138|469:138|451:210|396:226,1,240.000005722046 +116,336,620893,2,0,B|61:318|43:246|43:246|61:174|116:158,1,240.000005722046 +388,280,621493,5,0 +256,48,621793,1,0 +124,280,622093,1,0 +56,196,622243,2,0,B|116:172|116:172|136:116|136:116,1,120.000002861023 +202,192,622543,2,0,B|256:218|256:218|309:192,1,120.000002861023 +377,119,622843,2,0,B|376:116|396:172|396:172|456:196,1,120.000002861023 +400,288,623143,1,0 +304,344,623293,1,0 +256,360,623368,1,0 +208,344,623443,1,0 +128,272,623593,5,0 +160,168,623743,1,0 +256,128,623893,1,0 +352,168,624043,1,0 +384,272,624193,1,0 +256,248,624343,1,0 +352,168,624493,5,0 +368,112,624568,1,0 +352,64,624643,1,0 +312,24,624718,1,0 +256,8,624793,1,0 +200,24,624868,1,0 +160,64,624943,1,0 +144,112,625018,1,0 +160,168,625093,1,4 +84,244,625243,5,0 +152,336,625393,1,0 +256,364,625543,1,0 +360,336,625693,1,0 +428,244,625843,1,0 +428,140,625993,1,0 +360,48,626143,1,0 +256,20,626293,1,0 +152,48,626443,1,0 +84,140,626593,1,0 +206,158,626743,6,0,B|222:129|256:125|289:129|305:158,1,120.000002861023 +376,240,627043,1,0 +304,320,627193,2,0,B|287:349|254:353|220:349|204:320,1,120.000002861023 +136,240,627493,1,0 +256,248,627643,1,0 +312,144,627793,5,0 +312,144,627868,1,0 +312,144,627943,1,0 +344,48,628093,1,0 +256,0,628243,1,0 +168,48,628393,1,0 +200,144,628543,1,0 +96,168,628693,5,0 +88,272,628843,1,0 +192,320,628993,1,0 +256,232,629143,1,0 +312,144,629293,1,0 +416,168,629443,1,0 +424,272,629593,1,0 +320,320,629743,1,0 +256,232,629893,1,0 +200,144,630043,5,0 +208,40,630193,1,0 +256,16,630268,1,0 +304,40,630343,1,0 +312,144,630493,1,0 +416,200,630643,2,0,B|488:304,1,120.000002861023 +408,376,630943,2,0,B|339:277,1,120.000002861023 +256,344,631243,1,0 +172,277,631393,2,0,B|104:376,1,120.000002861023 +27,298,631693,2,0,B|96:200,1,120.000002861023 +93,91,631993,5,0 +206,63,632143,2,0,B|222:33|255:29|289:33|305:63,1,120.000002861023 +419,91,632443,1,0 +352,176,632593,1,0 +360,184,632668,1,0 +368,192,632743,1,0 +400,296,632893,2,0,B|456:278|456:278|476:222,1,120.000002861023 +308,356,633193,2,0,B|289:356|289:356|265:346|265:346|256:360|256:360|246:346|246:346|227:356|227:356|208:356,1,120.000002861023 +35,222,633493,2,0,B|55:278|55:278|112:296,1,120.000002861023 +184,96,633793,5,0 +256,272,633943,1,0 +328,96,634093,1,0 +160,200,634243,1,0 +352,200,634393,1,0 +256,32,634543,1,0 +256,168,634693,5,4 +352,328,634993,5,0 +400,88,635293,1,0 +160,40,635593,1,0 +112,248,635893,1,0 +276,357,636193,5,0 +431,167,636493,1,0 +242,12,636793,1,0 +102,173,637093,1,0 +205,350,637393,5,0 +425,244,637693,1,0 +319,23,637993,1,0 +124,110,638293,1,0 +138,310,638593,5,0 +383,315,638893,1,0 +389,71,639193,1,0 +175,59,639493,1,0 +99,249,639793,5,0 +319,358,640093,1,0 +427,138,640393,1,0 +239,38,640693,1,0 +89,186,640993,5,0 +252,369,641293,1,0 +435,206,641593,1,0 +297,43,641893,1,0 +107,116,642193,5,0 +177,351,642493,1,0 +412,281,642793,1,0 +304,240,642943,5,0 +256,224,643018,1,0 +208,240,643093,1,0 +136,155,643243,2,0,B|187:189|256:155|307:103|307:51|290:17|256:0|221:17|204:51|204:103|256:155|324:189|376:155,1,480.000011444092 +40,44,671736,6,0,B|40:164,1,100,8|8 +120,184,672236,2,0,B|120:76,1,100,8|0 +200,126,672736,2,0,B|217:74|276:53|341:72|356:135,1,200,4|8 +436,72,673486,1,0 +484,148,673736,1,8 +412,204,673986,2,0,B|408:244|424:280|464:296|464:296,1,100,0|8 +404,368,674486,5,0 +325,319,674736,2,0,B|299:294|264:292|232:320|232:320|204:348|157:346|140:313,1,200,8|8 +64,352,675486,2,0,B|16:316|12:208|68:160,1,200 +122,106,676236,2,0,B|174:90|190:38,1,100,8|0 +267,84,676736,2,0,B|243:131|281:187|370:171|373:107|349:70,1,200,8|8 +428,28,677486,5,0 +488,96,677736,1,8 +424,160,677986,1,0 +484,225,678236,2,0,B|470:274|470:274|486:322,1,100,8|0 +412,368,678736,2,0,B|308:368,1,100,8|0 +252,300,679236,1,8 +184,360,679486,5,0 +108,312,679736,2,0,B|48:328|4:284|4:204|68:188|68:188,1,200,8|8 +148,148,680486,2,0,B|194:117|188:60,1,100,0|8 +268,20,680986,1,0 +332,80,681236,2,0,B|448:48,1,100,8|0 +484,124,681736,2,0,B|388:152,1,100,8|0 +376,244,682236,6,0,B|492:212,1,100,8|0 +496,304,682736,1,8 +328,373,683236,2,0,B|313:319|252:296|191:320|176:383,1,200,8|8 +88,328,683986,2,0,B|16:248|48:136|48:136,1,200 +120,192,684736,2,0,B|124:75|216:16,1,200,8|8 +280,80,685486,5,0 +360,32,685736,2,0,B|480:32|480:32,1,100,8|0 +488,120,686236,2,0,B|440:128|440:168|488:176,1,100,8|0 +432,248,686736,2,0,B|480:256|480:296|432:304,1,100,8|0 +360,360,687236,1,8 +288,304,687486,5,0 +216,360,687736,2,0,B|104:360|104:360,1,100,8|0 +72,280,688236,2,0,B|184:280|184:280,1,100,8|0 +136,200,688736,2,0,B|85:203|44:162|36:100|88:60,1,200,12|8 +169,24,689486,1,0 +256,56,689736,2,0,B|360:36,1,100,8|0 +424,88,690236,6,0,B|444:188,1,100,8|0 +368,232,690736,1,8 +216,344,691236,2,0,B|223:318|255:306|287:319|295:351,1,100,8|0 +144,232,692236,1,8 +68,186,692486,2,0,B|88:88,1,100,0|8 +256,200,693236,5,8 +336,72,693736,1,8 +256,24,693986,1,0 +176,72,694236,1,8 +177,236,694736,2,0,B|198:284|254:310|321:285|336:232,1,200,8|8 +408,280,695486,1,0 +408,280,695736,5,8 +256,368,696236,1,8 +104,280,696736,1,4 +104,104,697236,1,8 +256,16,697736,1,8 +408,104,698236,5,8 +408,104,698486,1,0 +408,104,698736,1,8 +338,275,699236,2,0,B|315:320|259:346|192:321|176:269,1,200,8|8 +175,175,699986,2,0,B|198:130|254:104|321:129|337:181,1,200 +257,228,700736,5,8 +256,48,701236,1,8 +176,320,701736,1,8 +256,360,701986,1,0 +336,320,702236,1,8 +352,144,702736,2,0,B|287:143|287:143|255:127|255:127|223:143|223:143|151:143,1,200,8|8 +300,32,703736,5,8 +212,32,703986,1,0 +60,121,704486,6,0,B|94:207|94:207|50:304,1,200,8|0 +49,304,705236,1,8 +124,344,705486,2,0,B|169:247|169:247|257:217,1,200,8|0 +452,120,706236,1,8 +452,120,706486,2,0,B|417:207|417:207|462:304,1,200,8|0 +463,304,707236,1,8 +387,343,707486,2,0,B|342:247|342:247|254:217,1,200,8|0 +158,53,708236,1,8 +158,53,708486,2,0,B|223:52|223:52|255:36|255:36|287:52|287:52|359:52,1,200,8|0 +352,138,709236,1,8 +352,138,709486,2,0,B|287:139|287:139|255:155|255:155|223:139|223:139|151:139,1,200,8|0 +256,285,710236,5,8 +100,340,710486,1,8 +204,340,710736,5,0 +180,304,710861,1,0 +180,260,710986,1,0 +212,220,711111,1,0 +256,208,711236,1,0 +300,220,711361,1,0 +332,260,711486,1,0 +332,304,711611,1,0 +308,340,711736,1,0 +412,340,711986,37,0 +452,164,712486,21,0 +370,97,712736,2,0,B|340:54|262:14|173:45|137:104,1,259.999995350838,4|8 +370,287,713736,2,0,B|340:330|262:370|173:339|137:280,1,259.999995350838,0|8 +194,214,714486,2,0,B|258:263|325:207,1,129.999997675419,0|8 +256,144,714986,1,8 +96,288,715486,6,0,B|32:240|16:192|16:192|32:128|112:80,1,259.999995350838,8|8 +256,192,716236,1,8 +416,288,716486,2,0,B|480:240|496:192|496:192|480:128|400:80,1,259.999995350838,8|0 +200,76,717236,6,0,B|214:55|253:35|298:50|316:80,1,129.999997675419,8|0 +256,336,717736,1,8 +160,180,717986,2,0,B|174:243|252:283|341:252|352:168,2,259.999995350838,8|8|8 +320,344,719236,6,0,B|184:344,1,129.999997675419,8|0 +440,168,719736,2,0,B|360:36,1,129.999997675419,8|0 +72,168,720236,2,0,B|152:36,1,129.999997675419,8|0 +256,216,720736,2,0,B|256:296|256:296|360:292|368:200|340:136|304:100|196:92|156:152|149:244|204:280,1,519.999990701676,4|0 +348,360,722236,5,8 +256,384,722486,1,0 +172,360,722736,1,8 +256,208,723236,1,8 +299,22,723736,1,8 +214,25,723986,2,0,B|173:80|209:160|333:145|330:56|296:19,1,259.999995350838,8|8 +404,172,724986,5,8 +344,240,725236,1,8 +256,260,725486,1,0 +168,236,725736,1,8 +108,172,725986,1,0 +140,340,726486,2,0,B|170:383|248:423|337:392|373:333,1,259.999995350838,8|0 +316,268,727236,2,0,B|252:317|185:261,1,129.999997675419,8|0 +256,176,727736,5,8 +160,128,727986,1,0 +192,96,728111,1,0 +212,48,728236,1,0 +256,20,728361,1,0 +300,48,728486,1,0 +320,96,728611,1,0 +352,128,728736,1,4 +400,224,728986,6,0,B|400:288|400:288|464:352,1,129.999997675419 +352,368,729486,1,0 +279,292,729736,2,0,B|297:263|279:224|217:231|219:275|235:294,1,129.999997675419 +160,368,730236,1,0 +61,333,730486,2,0,B|107:286|107:286|107:222,1,129.999997675419 +195,167,730986,2,0,B|256:143|256:143|316:167,1,129.999997675419 +379,84,731486,2,0,B|256:35|256:35|135:84,1,259.999995350838 +207,173,732236,5,0 +305,174,732486,2,0,B|373:206|373:323|285:353|256:323|256:323|212:279|226:235|285:235|299:279|256:323|256:323|212:353|138:309|138:206|214:170,1,649.999988377094 +162,79,733986,1,0 +350,79,734486,1,0 +365,286,734986,2,0,B|335:243|257:203|168:234|132:293,1,259.999995350838 +195,370,735736,6,0,B|259:419|326:363,2,129.999997675419 +137,282,736486,1,0 +79,182,736736,2,0,B|202:174|221:20|153:-13|84:20|84:89|187:123|256:20|256:20|309:109|412:102|451:11|334:-40|296:20|329:166|352:180|459:186,1,779.999986052513,4|0 +344,248,738486,5,0 +256,192,738736,1,0 +168,248,738986,1,0 +355,355,739486,2,0,B|280:360|256:287|256:287|232:360|155:355,1,259.999995350838 +80,288,740236,1,0 +58,186,740486,6,0,B|28:128|76:66|76:66|158:49|196:100,1,259.999995350838 +256,184,741236,2,0,B|256:216|256:216|240:232|240:232|272:240|272:240|256:256|256:256|256:280,2,129.999997675419 +316,99,741986,2,0,B|354:49|436:66|436:66|484:128|454:186,1,259.999995350838 +315,349,742986,6,0,B|251:398|184:342,1,129.999997675419 +205,246,743486,1,0 +307,246,743736,1,0 +368,160,743986,5,0 +324,156,744111,1,0 +288,132,744236,1,0 +256,100,744361,1,0 +224,132,744486,1,0 +188,156,744611,1,0 +144,160,744736,1,4 +256,12,751736,5,8 +181,62,751986,1,0 +209,150,752236,1,8 +301,150,752486,1,0 +329,62,752736,1,4 +328,243,753236,2,0,B|313:297|252:320|191:296|176:233,1,200,8|8 +344,356,754236,5,8 +256,384,754486,1,0 +168,356,754736,1,8 +126,200,755236,1,8 +183,128,755486,2,0,B|233:132|253:72|253:72|273:132|336:128,1,200 +385,200,756236,1,8 +296,240,756486,2,0,B|288:216|259:203|225:213|217:244,1,100,0|8 +256,372,757236,5,8 +416,228,757736,1,8 +356,56,758236,1,8 +156,56,758736,1,8 +84,228,759236,1,8 +141,298,759486,5,0 +200,225,759736,1,4 +183,187,759861,1,0 +188,145,759986,1,0 +213,111,760111,1,0 +256,94,760236,1,0 +298,111,760361,1,0 +323,145,760486,1,0 +328,187,760611,1,0 +311,225,760736,1,0 +370,298,760986,5,0 +308,368,761236,2,0,B|196:368|196:368,1,100,8|0 +176,280,761736,2,0,B|200:272|213:243|203:209|172:201,1,100,8|0 +256,152,762236,1,8 +336,204,762486,2,0,B|310:211|300:245|313:274|337:282,1,100,0|8 +344,60,763236,2,0,B|256:12|256:12|168:60,1,200,8|8 +169,139,763986,2,0,B|256:186|256:186|344:138,1,200 +256,100,764736,5,8 +256,260,765236,1,8 +120,344,765736,1,8 +256,260,766236,1,8 +392,344,766736,1,8 +446,187,767236,5,8 +385,127,767486,1,0 +368,40,767736,1,8 +296,100,767986,2,0,B|289:126|255:136|226:123|218:99,1,100,0|8 +143,40,768486,1,0 +126,127,768736,1,12 +65,192,768986,1,0 +126,256,769236,5,8 +144,343,769486,1,0 +218,284,769736,2,0,B|226:261|255:248|289:258|296:284,1,100,8|0 +368,343,770236,1,8 +385,256,770486,1,0 +446,192,770736,1,8 +376,38,771236,5,8 +300,84,771486,2,0,B|324:131|286:187|197:171|194:107|218:70,1,200 +135,38,772236,1,8 +100,120,772486,5,0 +40,52,772736,1,8 +57,216,773236,1,8 +181,324,773736,2,0,B|196:375|260:399|323:374|338:309,1,200,8|8 +454,216,774736,1,8 +472,52,775236,5,8 +412,120,775486,1,0 +376,38,775736,1,8 +300,200,776236,2,0,B|324:153|286:97|197:113|194:177|218:214,1,200,8|8 +328,320,777236,2,0,B|278:316|258:376|258:376|238:316|175:320,1,200,8|8 +100,120,778236,5,8 +40,52,778486,1,0 +135,38,778736,1,8 +337,193,779236,2,0,B|314:148|258:122|191:147|175:199,1,200,8|8 +177,284,779986,2,0,B|193:332|254:358|327:334|336:280,1,200 +256,240,780736,5,8 +340,56,781236,1,8 +172,56,781736,1,8 +256,104,781986,1,0 +340,56,782236,1,8 +352,228,782736,2,0,B|287:229|287:229|255:245|255:245|223:229|223:229|151:229,1,200,8|8 +360,352,783736,5,8 +256,384,783986,1,8 +152,352,784236,1,8 +173,256,784486,2,0,B|185:285|256:336|325:292|350:244,1,200,8|0 +256,196,785236,1,0 +309,100,785486,2,0,B|336:52|291:-27|183:-4|172:77|210:125,1,240.000005722046 +112,168,786236,1,0 +128,272,786486,6,0,B|168:232|213:263|213:263|256:289|303:260|303:260|356:231|392:283|392:283,1,279.99999332428 +400,367,787236,1,0 +400,367,787486,2,0,B|354:412|303:377|303:377|254:347|200:380|200:380|140:413|99:354|99:354,1,320 +92,204,788236,5,0 +92,204,788486,2,0,B|135:142|257:127|384:123|441:224|441:224,1,359.999982833863 +442,299,789236,1,0 +442,299,789486,2,0,B|390:365|256:382|119:384|55:280|55:280,1,400 +106,180,790236,5,0 +32,87,790486,1,0 +160,64,790736,1,0 +200,100,790861,1,0 +216,156,790986,1,0 +208,212,791111,1,0 +256,244,791236,1,0 +304,212,791361,1,0 +296,156,791486,1,0 +312,100,791611,1,0 +356,64,791736,1,0 +484,96,791986,5,0 +412,196,792236,1,0 +412,196,792361,1,0 +412,196,792486,1,0 +370,318,792736,2,0,B|340:361|262:401|173:370|137:311,1,259.999995350838,4|0 +316,168,793736,6,0,B|345:220|295:307|176:281|164:192|206:139,1,259.999995350838 +160,68,794486,1,0 +256,8,794736,1,0 +352,68,794986,1,0 +360,240,795486,2,0,B|402:240|402:240|442:240|458:272|458:296|458:296|458:320|442:352|402:352|402:352|354:352|354:352,1,259.999995350838 +256,320,796236,1,0 +152,352,796486,2,0,B|158:352|110:352|110:352|70:352|54:320|54:296|54:296|54:272|70:240|110:240|110:240|152:240,1,259.999995350838 +200,144,797236,6,0,B|256:176|256:176|312:144,1,129.999997675419 +312,48,797736,2,0,B|255:15|255:15|199:47,1,129.999997675419 +368,232,798486,2,0,B|254:297|254:297|142:232,1,259.999995350838 +88,320,799236,6,0,B|200:384,1,129.999997675419 +311,384,799736,2,0,B|424:320,1,129.999997675419 +475,229,800236,1,0 +437,131,800486,1,0 +334,121,800736,2,0,B|354:55|308:3|254:-3|182:5|140:90|200:154|254:187|254:187|312:220|421:280|335:420|211:416|164:335|177:272,1,779.999986052513,4|0 +72,290,802486,5,0 +13,204,802736,1,0 +143,53,803236,2,0,B|173:10|251:-30|340:1|376:60,1,259.999995350838 +370,149,803986,2,0,B|340:192|262:232|173:201|137:142,1,259.999995350838 +192,360,804986,6,0,B|336:360,1,129.999997675419 +324,268,805486,2,0,B|180:268,1,129.999997675419 +128,177,805986,2,0,B|408:177,1,259.999995350838 +206,47,806986,6,0,B|216:16|254:0|298:13|307:47,1,129.999997675419 +304,152,807486,1,0 +256,168,807611,1,0 +208,152,807736,1,0 +128,216,807986,1,0 +160,256,808111,1,0 +208,280,808236,1,0 +256,288,808361,1,0 +304,280,808486,1,0 +352,256,808611,1,0 +384,216,808736,1,4 +464,128,808986,6,0,B|496:256,1,129.999997675419 +320,352,809486,2,0,B|192:384,1,129.999997675419 +16,254,809986,2,0,B|48:128,1,129.999997675419 +197,23,810486,6,0,B|168:76|217:160|334:136|346:48|305:-4,1,259.999995350838 +416,64,811236,1,0 +370,149,811486,2,0,B|340:192|262:232|173:201|137:142,1,259.999995350838 +96,64,812236,1,0 +48,160,812486,2,0,B|96:272|224:320|288:320|416:272|464:160,1,519.999990701676 +512,256,813736,1,0 +512,256,813986,5,0 +256,368,814486,1,0 +0,256,814986,1,0 +0,256,815236,1,0 +72,176,815486,5,0 +104,216,815611,1,0 +110,268,815736,1,0 +208,312,815986,1,0 +256,296,816111,1,0 +304,312,816236,1,0 +402,268,816486,1,0 +408,216,816611,1,0 +440,176,816736,1,4 +360,120,816986,6,0,B|391:110|407:72|394:28|360:19,1,129.999997675419 +256,0,817486,2,0,B|256:128,1,129.999997675419 +152,120,817986,2,0,B|121:110|105:72|118:28|152:19,2,129.999997675419 +215,203,818736,1,0 +300,206,818986,2,0,B|341:261|305:341|181:326|184:237|218:200,1,259.999995350838 +352,352,819986,1,0 +256,384,820236,1,0 +160,352,820486,1,0 +92,159,820986,5,0 +149,69,821236,1,0 +256,36,821486,1,0 +362,69,821736,1,0 +419,159,821986,1,0 +349,362,822486,5,0 +307,353,822611,1,0 +273,328,822736,1,0 +238,302,822861,1,0 +213,260,822986,1,0 +196,226,823111,1,0 +187,183,823236,1,0 +187,140,823361,1,0 +213,106,823486,1,0 +256,89,823611,1,0 +298,106,823736,1,0 +324,140,823861,1,0 +324,183,823986,1,0 +315,226,824111,1,0 +298,260,824236,1,0 +273,302,824361,1,0 +238,328,824486,1,0 +204,353,824611,1,0 +162,362,824736,1,4 +40,240,824986,6,0,B|71:230|87:192|74:148|40:139,1,129.999997675419 +205,34,825486,2,0,B|215:65|253:81|297:68|306:34,1,129.999997675419 +472,144,825986,2,0,B|441:154|425:192|438:236|472:245,1,129.999997675419 +307,350,826486,2,0,B|297:319|259:303|215:316|206:350,1,129.999997675419 +128,56,827236,5,0 +384,56,827736,1,0 +148,348,828236,1,0 +256,296,828486,1,0 +364,348,828736,1,0 +24,104,829236,2,0,B|93:112|135:188|104:277|12:281,1,259.999995350838 +148,36,830236,5,0 +256,88,830486,1,0 +364,36,830736,1,0 +488,280,831236,2,0,B|419:272|377:196|408:107|500:103,1,259.999995350838 +306,327,832236,2,0,B|296:358|258:374|214:361|205:327,1,129.999997675419 +256,192,832736,5,4 +64,48,833236,1,0 +184,280,833736,5,0 +152,152,833986,1,0 +256,80,834236,1,0 +360,152,834486,1,0 +328,280,834736,1,4 +448,48,835236,5,0 +256,192,835736,1,0 +64,336,836236,5,0 +152,240,836486,2,0,B|121:230|105:192|118:148|152:139,1,129.999997675419 +360,240,837236,2,0,B|391:230|407:192|394:148|360:139,1,129.999997675419 +440,336,838236,1,0 +358,236,838486,2,0,B|305:220|265:252|257:292|257:292|249:252|205:218|151:239,1,259.999995350838 +72,336,839236,1,0 +24,216,839486,5,0 +72,96,839736,1,0 +200,32,839986,1,0 +184,80,840111,1,0 +208,128,840236,1,0 +256,144,840361,1,0 +304,128,840486,1,0 +328,80,840611,1,0 +312,32,840736,1,4 +440,96,840986,1,0 +488,216,841236,1,0 +390,300,841486,5,0 +341,342,841611,1,0 +328,280,841736,1,0 +184,280,841986,1,0 +171,342,842111,1,0 +122,300,842236,1,0 +87,178,842486,5,0 +195,106,842736,2,0,B|207:67|256:59|256:59|288:43|272:11|240:11|224:43|256:59|256:59|302:67|321:111,1,259.999995350838 +424,176,843486,1,0 +424,176,843611,1,0 +424,176,843736,1,0 +376,296,843986,1,0 +256,352,844236,1,0 +136,296,844486,1,0 +136,296,844611,1,0 +136,296,844736,1,4 +88,176,844986,1,0 +204,101,845236,2,0,B|214:70|252:54|296:67|305:101,1,129.999997675419 +384,352,845986,5,0 +360,304,846111,1,0 +304,288,846236,1,0 +256,320,846361,1,0 +208,288,846486,1,0 +152,304,846611,1,0 +128,352,846736,1,4 +208,192,846986,5,0 +256,208,847111,1,0 +304,192,847236,1,0 +400,128,847486,1,0 +416,80,847611,1,0 +384,32,847736,1,0 +328,24,847861,1,0 +288,56,847986,1,0 +256,88,848111,1,0 +224,56,848236,1,0 +184,24,848361,1,0 +128,32,848486,1,0 +96,80,848611,1,0 +112,128,848736,1,0 +152,248,848986,5,0 +152,248,849111,1,0 +152,248,849236,1,0 +144,376,849486,1,0 +144,376,849611,1,0 +144,376,849736,1,0 +256,320,849986,1,0 +256,320,850111,1,0 +256,320,850236,1,0 +368,376,850486,1,0 +368,376,850611,1,0 +368,376,850736,1,0 +360,248,850986,1,0 +360,248,851111,1,0 +360,248,851236,1,0 +256,192,851486,5,0 +256,192,851611,1,0 +256,192,851736,1,4 +256,320,851986,1,4 +95,186,852486,6,0,B|159:153|207:57|191:25|127:-7|95:25|63:89|223:153|255:81|255:81|287:25|255:-7|223:25|255:81|255:81|285:136|415:121|383:89|479:-39|301:-25|223:89|431:196|431:196,1,909.999983727932,4|2 +312,280,854486,2,0,B|298:259|259:239|214:254|196:284,1,129.999997675419,0|2 +256,381,854986,1,0 +370,173,855486,2,0,B|340:130|262:90|173:121|137:180,1,259.999995350838,2|0 +208,28,856236,1,2 +304,28,856486,1,0 +256,192,856611,12,0,858236 +256,192,859486,12,4,860736 +191,69,860986,6,0,B|204:43|254:19|302:36|324:74,1,149.999995529652,0|0 +448,172,861486,2,0,B|384:212|384:212|368:304,1,149.999995529652 +256,376,861986,1,0 +140,285,862236,2,0,B|128:212|128:212|64:172,1,149.999995529652 +169,47,862736,2,0,B|216:69|216:133|216:133|256:153|256:153|296:133|296:133|292:69|347:45,1,299.999991059304 +301,358,863736,1,0 +211,358,863986,2,0,B|157:296|201:201|345:218|341:321|296:365,1,299.999991059304 +73,188,864986,5,0 +138,89,865236,1,0 +256,48,865486,1,0 +373,89,865736,1,0 +438,188,865986,1,0 +178,345,866486,2,0,B|212:367|256:331|256:331|284:311|284:279|256:259|228:279|228:311|256:331|256:331|296:367|336:343,1,299.999991059304 +321,225,867236,2,0,B|308:199|258:175|210:192|188:230,1,149.999995529652 +96,148,867736,5,0 +128,108,867861,1,0 +164,80,867986,1,0 +212,64,868111,1,0 +256,56,868236,1,0 +300,64,868361,1,0 +348,80,868486,1,0 +384,108,868611,1,0 +416,148,868736,1,4 +256,224,868986,5,0 +208,364,869236,1,0 +328,276,869486,1,0 +184,276,869736,1,0 +304,364,869986,1,0 +256,224,870236,5,0 +128,112,870486,2,0,B|160:61|249:17|350:49|391:118,1,299.999991059304 +256,200,871236,1,0 +126,291,871486,2,0,B|158:341|247:386|348:354|389:284,1,299.999991059304 +256,200,872236,1,0 +113,156,872486,6,0,B|35:111|52:28|108:-8|156:-7|216:29|209:95|209:95|224:62|256:174|288:62|304:94|304:94|292:32|356:-16|412:-6|465:38|473:120|387:160,1,749.999977648259,0|0 +372,298,873986,2,0,B|311:279|266:316|256:361|256:361|247:316|197:277|136:301,1,299.999991059304 +256,20,874986,5,0 +128,116,875236,1,0 +184,260,875486,1,0 +328,260,875736,1,0 +384,116,875986,1,0 +256,156,876236,5,0 +256,156,876361,1,0 +256,156,876486,1,0 +256,156,876611,1,0 +256,156,876736,1,4 +256,308,876986,5,0 +400,356,877236,1,0 +488,236,877486,1,0 +400,116,877736,1,0 +256,156,877986,1,0 +256,308,878236,1,0 +112,364,878486,1,0 +24,236,878736,1,0 +112,116,878986,1,0 +408,64,879486,6,0,B|459:96|503:185|471:286|402:327,1,299.999991059304 +256,192,880236,1,0 +104,64,880486,2,0,B|53:96|9:185|41:286|110:327,1,299.999991059304 +322,237,881236,2,0,B|309:263|259:287|211:270|189:232,1,149.999995529652 +320,147,881736,2,0,B|307:121|257:97|209:114|187:152,1,149.999995529652 +388,304,882486,6,0,B|356:355|267:399|166:367|125:298,1,299.999991059304 +256,192,883236,1,0 +184,60,883486,1,0 +256,32,883611,1,0 +328,60,883736,1,0 +396,196,883986,1,0 +341,251,884111,1,0 +326,324,884236,1,0 +256,360,884361,1,0 +185,324,884486,1,0 +170,251,884611,1,0 +115,196,884736,1,4 +256,192,884861,12,0,886236 +128,128,933524,5,2 +214,104,933953,1,0 +152,42,934381,1,2 +65,65,934810,1,0 +42,152,935238,1,2 +104,214,935667,1,0 +190,190,936095,1,2 +212,228,936310,1,0 +256,244,936524,1,0 +300,228,936738,1,0 +323,189,936952,5,2 +408,170,937381,1,0 +470,232,937809,1,2 +447,319,938238,1,0 +360,342,938666,1,2 +298,280,939095,1,0 +323,189,939523,1,2 +384,256,939952,1,0 +160,112,940380,5,2 +212,38,940809,1,0 +300,38,941237,1,2 +352,112,941666,1,0 +292,181,942094,2,0,B|272:153|230:153|219:184|219:184,1,84.9999964535238,2|0 +256,260,942951,1,2 +212,288,943165,1,0 +228,340,943380,1,0 +280,340,943594,1,0 +300,292,943808,1,2 +396,256,944236,6,0,B|468:192,1,84.9999964535238,0|2 +416,32,945093,2,0,B|376:108,1,84.9999964535238,0|2 +256,48,945950,1,0 +135,107,946379,2,0,B|96:32,1,84.9999964535238,2|0 +52,199,947236,2,0,B|116:256,1,84.9999964535238,2|0 +292,328,948093,6,0,B|272:356|230:356|219:325|219:325,1,84.9999964535238,2|0 +324,208,948950,2,0,B|352:124,1,84.9999964535238,2|0 +188,208,949807,2,0,B|160:124,1,84.9999964535238,2|0 +256,64,950449,1,0 +256,64,950664,5,2 +400,160,951092,1,0 +340,324,951521,1,2 +172,324,951949,1,0 +112,160,952378,1,2 +256,64,952806,1,0 +256,200,953235,5,2 +188,328,953663,1,0 +256,356,953877,1,0 +324,328,954092,1,2 +428,228,954520,1,0 +392,92,954948,1,2 +256,40,955377,1,0 +120,92,955805,1,2 +84,228,956234,1,0 +220,208,956662,6,0,B|240:236|282:236|293:205|293:205,1,84.9999964535238,2|0 +292,352,957519,2,0,B|272:324|230:324|219:355|219:355,1,84.9999964535238,2|0 +124,244,958376,1,2 +256,140,958805,1,0 +392,244,959233,1,2 +412,80,959662,1,0 +256,264,960090,1,2 +100,80,960519,1,0 +100,80,960733,1,0 +100,80,960947,5,2 +256,348,961376,1,0 +412,80,961804,1,2 +100,304,962233,5,0 +256,36,962661,1,2 +412,304,963090,1,0 +191,224,963518,1,2 +256,276,963732,1,0 +320,224,963947,1,0 +294,150,964161,1,0 +217,150,964375,1,2 +256,360,964804,5,0 +88,256,965232,1,2 +160,64,965660,1,0 +352,64,966089,1,2 +424,256,966517,1,0 +256,192,966946,1,2 +256,192,967160,1,0 +256,192,967374,1,0 +256,192,967589,1,0 +256,192,967803,5,2 +220,352,968231,2,0,B|240:380|282:380|293:349|293:349,1,84.9999964535238,0|2 +364,197,969088,1,0 +396,120,969303,1,0 +148,197,969731,1,0 +116,120,969945,1,0 +256,32,970374,5,2 +256,200,970802,1,0 +256,368,971231,1,2 +196,300,971445,2,0,B|224:300|224:300|256:284|256:284|288:300|288:300|320:300,1,127.499994680286,0|2 +439,128,972516,2,0,B|452:96|439:66|439:66,1,50 +352,57,972945,6,0,B|398:112|337:186|274:153|255:116|255:116|237:84|198:18|65:115|168:182|168:182,1,400,2|2 +120,348,975087,5,0 +178,278,975301,2,0,B|193:324|256:356|318:324|334:278,1,200 +392,348,976372,1,2 +355,173,976801,2,0,B|335:114|257:75|179:114|160:173,1,250 +95,106,978086,5,2 +256,24,978515,1,0 +256,24,978729,1,0 +256,328,979372,1,2 +336,168,979800,2,0,B|312:128|256:112|256:112|200:128|176:168,1,200,0|0 +417,106,981300,5,0 +417,106,981514,1,2 +256,24,981943,1,0 +295,191,982371,2,0,B|282:170|254:162|254:162|226:170|214:191,1,100,2|0 +216,280,983014,2,0,B|232:280|232:280|248:272|248:272|256:280|256:280|256:264|256:264|264:272|264:272|280:280|280:280|296:280,1,100 +344,351,983657,2,0,B|320:383|280:383|256:359|256:359|232:383|192:383|168:351,1,200,2|0 +80,328,984728,5,0 +144,264,984942,1,2 +56,240,985156,1,0 +256,56,985799,1,0 +368,264,986227,1,0 +456,240,986442,1,0 +432,328,986656,1,2 +56,264,987513,6,0,B|102:249|134:186|102:124|56:108,1,200,0|2 +256,92,988798,1,0 +256,92,989013,2,0,B|256:292,1,200 +256,292,990084,1,2 +456,264,990512,2,0,B|410:249|378:186|410:124|456:108,1,200 +256,36,991798,1,2 +196,108,992012,2,0,B|224:108|224:108|256:124|256:124|288:108|288:108|320:108,1,127.499994680286 +346,348,993083,5,2 +256,312,993297,1,0 +168,349,993512,2,0,B|111:265|160:184|191:152|261:139|319:160|356:186|407:266|341:356,1,500 +217,61,996939,6,0,B|224:83|256:100|287:83|295:61,1,100,2|0 +168,192,997796,1,2 +335,302,998225,2,0,B|320:348|257:380|195:348|179:302,1,200,0|2 +344,192,999510,1,0 +188,56,999939,5,2 +256,100,1000153,1,0 +324,56,1000367,1,0 +424,200,1000796,2,0,B|464:228|464:228|480:284,1,100,0|2 +348,360,1001653,1,0 +220,204,1002081,2,0,B|197:237|216:270|229:282|257:288|280:279|295:269|315:237|289:201,1,200,2|0 +164,360,1003367,5,2 +88,200,1003795,2,0,B|48:228|48:228|32:284,1,100 +144,60,1004652,1,2 +194,232,1005081,2,0,B|208:276|208:276|256:300|256:300|304:276|304:276|320:228,1,200 +369,59,1006366,1,2 +448,224,1006795,6,0,B|419:326,1,100,0|2 +256,240,1007651,2,0,B|256:136,1,100,0|2 +64,224,1008508,2,0,B|93:326,1,100,0|2 +256,48,1009794,5,2 +400,152,1010222,1,0 +348,328,1010651,1,2 +164,328,1011079,1,0 +112,152,1011508,1,2 +256,48,1011936,1,0 +256,196,1012793,5,2 +256,348,1013864,5,0 +256,348,1014079,1,2 +256,52,1014507,1,0 +45,192,1015364,2,0,B|61:143|110:127|175:143|142:192|158:256|239:240|256:192|256:192|272:240|353:256|369:192|336:143|401:127|450:143|466:192,1,549.999983608723,2|0 +179,48,1022649,6,0,B|194:94|257:126|319:94|335:48,1,200,8|0 +340,239,1023506,2,0,B|301:254|270:239|255:208|255:208|240:239|209:254|171:239,1,200,8|0 +172,349,1024148,2,0,B|210:333|241:349|256:379|256:379|271:349|302:333|340:349,1,200,0|0 +432,296,1024791,1,0 +456,64,1025220,1,8 +256,152,1025648,5,0 +64,64,1026077,1,8 +112,152,1026291,1,0 +174,64,1026506,2,0,B|201:28|256:10|256:10|310:28|337:64,1,200,0|8 +424,256,1027363,5,0 +360,336,1027577,1,0 +256,368,1027791,1,8 +152,336,1028006,1,0 +88,256,1028220,1,0 +256,40,1028648,1,8 +424,256,1029077,1,0 +408,56,1029506,5,8 +345,355,1029934,1,0 +282,199,1030148,2,0,B|309:172|361:186|361:251|309:263|309:263|270:277|256:315|256:315|243:277|204:263|204:263|152:251|152:186|204:172|230:199,1,439.999986886979 +167,355,1031220,1,8 +73,146,1031648,2,0,B|75:82|134:16|220:36|257:81|257:81|289:32|366:25|442:61|436:157,1,500 +400,256,1032934,1,8 +221,364,1033363,2,0,B|198:331|217:298|230:286|258:280|281:289|296:299|316:331|290:367,1,200,0|8 +112,256,1034220,5,0 +96,144,1034434,1,0 +200,184,1034648,1,8 +312,184,1034863,1,0 +416,144,1035077,1,0 +400,256,1035291,1,0 +333,336,1035506,6,0,B|318:290|255:258|193:290|177:336,1,200,8|0 +96,256,1036148,1,0 +64,152,1036363,2,8,B|160:120|160:120|192:24,1,200,8|0 +320,26,1037220,2,0,B|352:120|352:120|448:152,1,200,8|0 +336,240,1037863,2,0,B|288:240|288:240|256:200|256:200|224:240|224:240|176:240,1,200 +256,320,1038506,1,0 +64,192,1038934,5,8 +256,48,1039363,1,0 +304,184,1039577,1,0 +180,96,1039791,1,8 +332,96,1040006,1,0 +208,184,1040220,1,0 +448,192,1040648,1,8 +216,336,1041077,2,0,B|222:356|256:370|290:356|297:336,1,100 +333,189,1041506,2,0,B|370:117|336:59|311:34|253:28|214:34|176:54|137:117|184:193,2,400,8|8|8 +348,360,1043648,5,0 +256,320,1043863,1,0 +164,360,1044077,2,0,B|118:376|53:357|33:292|50:246,1,200,8|0 +21,137,1044720,1,0 +131,108,1044934,1,8 +160,217,1045148,1,0 +160,216,1045363,2,0,B|256:172|256:260|352:216,1,200,0|8 +460,28,1046220,1,0 +460,28,1046648,6,0,B|480:76|460:124,1,100,8|0 +348,84,1047077,2,0,B|280:176|348:268,1,200,0|8 +356,368,1047720,1,0 +256,336,1047934,1,0 +156,368,1048148,1,0 +163,268,1048363,2,0,B|231:175|163:83,1,200,8|0 +4,192,1049220,5,8 +256,352,1049648,1,0 +256,352,1049863,1,0 +256,352,1050077,6,0,B|352:328,1,100,4|0 +476,252,1050506,2,0,B|380:228,1,100,2|0 +256,148,1050934,2,0,B|352:124,1,100,2|0 +476,48,1051363,2,0,B|380:24,1,100,2|0 +256,48,1051791,6,0,B|160:92|160:4|64:48,1,200,0|0 +100,144,1052434,1,0 +100,144,1052648,2,0,B|120:192|100:240,1,100 +36,320,1053077,2,0,B|129:388|221:320,1,200,0|4 +256,224,1053720,1,0 +291,320,1053934,2,0,B|383:388|476:320,1,200 +512,116,1054791,6,0,B|416:144,1,100 +328,100,1055220,2,0,B|260:192|328:284,1,200,8|0 +312,384,1055863,1,0 +228,332,1056077,2,0,B|200:284|192:240,1,100,8|0 +191,144,1056506,2,0,B|200:100|228:52,1,100 +256,192,1056934,6,0,B|160:236|160:148|64:192,1,200,8|0 +256,192,1057791,2,0,B|352:148|352:236|448:192,1,200,8|0 +492,104,1058434,5,0 +396,112,1058649,2,0,B|354:80|348:28,1,100,8|0 +256,92,1059077,2,0,B|256:192,1,100,0|0 +115,112,1059506,2,0,B|157:80|164:27,1,100,8|0 +20,104,1059934,6,0,B|-48:196|20:288,1,200,0|8 +72,372,1060577,1,0 +160,324,1060791,2,0,B|256:276|352:324,1,200,0|8 +488,192,1061649,6,0,B|508:96,1,100 +372,152,1062077,2,0,B|352:56,1,100,8|0 +256,164,1062506,2,0,B|210:148|145:167|125:232|142:278,1,200,0|8 +370,277,1063363,2,0,B|387:232|367:167|302:148|256:164,1,200,0|8 +136,128,1064006,5,0 +208,76,1064220,2,0,B|256:96|304:76,1,100 +376,128,1064649,2,0,B|476:144,1,100,8|0 +512,272,1065077,6,0,B|413:256,1,100 +348,332,1065506,2,0,B|256:400|164:332,1,200,8|0 +164,52,1066363,2,0,B|256:-16|348:52,1,200,8|0 +356,152,1067006,1,0 +256,192,1067220,1,4 +156,152,1067434,1,0 +160,280,1067649,6,0,B|256:324|256:236|352:280,2,200,0|8|0 +68,320,1068720,1,0 +156,368,1068934,6,0,B|204:388|252:368,1,100,8|0 +356,192,1069363,2,0,B|308:172|260:192,1,100 +258,192,1069791,1,4 +100,276,1070220,1,0 +28,44,1070649,5,4 +120,88,1070863,1,0 +220,84,1071077,2,0,B|320:76,1,100 +412,32,1071506,2,0,B|442:133|378:220,1,200 +424,308,1072149,1,0 +328,340,1072363,1,0 +244,288,1072577,2,0,B|200:316|184:368,1,100 +88,340,1073006,1,0 +120,244,1073220,2,0,B|29:183|23:77,1,200 +112,28,1073863,1,0 +200,80,1074077,6,0,B|202:131|167:170,1,100 +116,260,1074506,2,0,B|216:280,1,100 +297,279,1074934,2,0,B|396:260,1,100 +345,171,1075363,2,0,B|309:131|312:80,1,100 +404,29,1075791,5,0 +481,103,1076006,1,0 +486,209,1076220,1,0 +445,308,1076434,1,0 +348,353,1076649,2,0,B|256:285|164:353,1,200,0|0 +70,309,1077291,1,0 +52,220,1077506,6,0,B|12:191|1:121|51:72|100:71,1,200,8|2 +186,99,1078149,2,0,B|225:127|236:197|186:246|137:247,1,200,0|0 +204,324,1078791,1,2 +304,324,1079006,1,0 +374,247,1079220,2,0,B|325:246|275:197|286:127|326:99,1,200,8|2 +411,71,1079863,2,0,B|460:72|510:121|499:191|460:220,1,200,0|0 +452,320,1080506,1,2 +360,356,1080720,1,0 +300,276,1080934,1,8 +368,204,1081149,1,0 +375,94,1081363,6,0,B|345:34|301:49|256:20|183:94|183:167|256:212|330:167|330:94|256:20|212:49|168:34|138:94,1,439.999986886979,0|2 +144,204,1082434,1,0 +212,276,1082649,1,8 +152,356,1082863,1,0 +60,320,1083077,1,2 +256,368,1083506,5,8 +344,184,1083934,1,0 +256,136,1084149,1,0 +168,184,1084363,1,8 +334,320,1084791,6,0,B|319:274|256:242|194:274|178:320,1,200,0|8 +88,192,1085434,1,0 +178,64,1085649,2,0,B|193:110|256:142|318:110|334:64,1,200,0|8 +424,192,1086291,1,0 +256,328,1086506,5,0 +96,80,1086934,1,8 +416,80,1087363,1,0 +256,194,1087577,1,0 +126,330,1087791,2,0,B|66:314|68:260|85:226|136:209|170:243|170:243|136:174|170:123|187:89|254:60|324:89|341:123|375:174|341:243|341:243|385:204|452:231|448:316|387:332,1,800,8|8 +432,144,1089720,1,8 +256,192,1089934,1,8 +80,144,1090149,1,0 +256,304,1090363,5,8 +200,120,1090577,1,8 +344,232,1090791,1,0 +168,232,1091006,1,8 +312,120,1091220,1,4 +114,294,1091649,6,0,B|68:279|36:216|68:154|114:138,1,200,2|8 +160,48,1092291,1,0 +256,80,1092506,1,2 +352,48,1092720,1,0 +398,138,1092934,2,0,B|444:154|476:216|444:279|398:294,1,200,8|0 +345,359,1093577,2,0,B|276:403|203:333|201:270|252:276|303:269|300:333|221:403|159:358,1,329.999990165234,2|8 +96,288,1094434,1,0 +176,224,1094649,2,0,B|194:180|256:154|319:178|340:230,1,200,2|0 +416,288,1095291,1,0 +337,343,1095506,6,0,B|319:387|257:413|194:389|173:337,1,200,8|2 +256,292,1096149,1,0 +205,208,1096363,1,8 +306,208,1096577,1,0 +376,130,1096791,2,0,B|380:64|353:-4|258:-45|163:0|124:60|137:130,1,400,2|2 +64,200,1097863,5,0 +64,200,1098077,1,8 +216,360,1098506,2,0,B|200:332|211:298|231:286|256:274|280:285|300:295|321:325|297:370,1,200,0|8 +448,200,1099363,1,0 +297,32,1099791,2,0,B|313:60|302:94|282:106|257:118|233:107|213:97|192:67|216:22,2,200,2|0|8 +156,200,1101077,5,2 +179,297,1101291,1,0 +272,322,1101506,1,10 +341,249,1101720,1,0 +317,152,1101934,1,0 +220,128,1102149,1,0 +146,223,1102363,1,8 +193,321,1102577,1,0 +298,327,1102791,1,0 +355,233,1103006,1,0 +308,134,1103220,5,8 +203,128,1103434,1,8 +149,253,1103649,1,8 +227,345,1103863,1,0 +342,321,1104077,1,8 +376,205,1104291,1,0 +297,113,1104506,1,8 +183,137,1104720,1,0 +147,263,1104934,5,12 +327,301,1105363,1,0 +365,121,1105791,1,8 +185,83,1106220,6,0,B|144:55|77:57|40:114|45:163,1,200,0|8 +76,260,1106863,1,0 +160,192,1107077,2,0,B|256:236|256:148|352:192,1,200,0|8 +436,124,1107720,5,0 +466,221,1107934,2,0,B|471:269|434:326|367:328|327:301,1,200,0|8 +256,232,1108577,1,0 +172,176,1108791,6,0,B|152:128|172:80,1,100 +340,208,1109220,2,0,B|360:256|340:304,1,100,8|0 +163,360,1109649,6,0,B|118:376|53:357|33:292|49:246,1,200,0|8 +16,152,1110291,1,0 +108,116,1110506,2,0,B|84:20,1,100 +224,76,1110934,2,0,B|248:173,1,100,8|0 +336,128,1111363,6,0,B|440:173|428:287,1,200,0|8 +84,287,1112220,2,0,B|71:173|176:128,1,200,0|8 +352,348,1113077,6,0,B|256:304|256:392|160:348,1,200,0|8 +112,276,1113720,1,0 +208,260,1113934,2,0,B|188:212|208:164,1,100 +304,164,1114363,2,0,B|324:212|304:260,1,100,8|0 +128,192,1114791,2,0,B|132:124|160:60|256:28|352:60|380:124|384:192,1,400,0|0 +332,328,1116077,1,8 +180,56,1116506,1,0 +372,12,1116934,6,0,B|440:104|372:196,1,200,8|0 +140,372,1117791,2,0,B|72:280|140:188,1,200,8|0 +184,96,1118434,1,0 +300,108,1118649,6,0,B|300:192|212:192|212:280,2,200,4|0|8 +256,20,1119720,1,0 +212,108,1119934,2,0,B|212:192|300:192|300:280,1,200,0|8 +256,364,1120577,1,0 +163,360,1120791,6,0,B|118:376|53:357|33:292|49:246,1,200,0|8 +348,360,1121649,2,0,B|394:376|459:357|479:292|462:246,1,200,0|4 +452,148,1122291,5,0 +356,124,1122506,2,0,B|311:153|308:207,1,100 +352,280,1122934,2,0,B|304:304|256:280|256:280|208:304|160:280,1,200,8|0 +204,207,1123577,2,0,B|200:153|156:124,1,100,0|8 +60,148,1124006,1,8 +60,148,1124220,6,0,B|72:48,1,100 +160,100,1124649,2,0,B|208:124|256:100|256:100|304:76|352:100,1,200,8|0 +440,48,1125291,1,0 +440,48,1125506,6,0,B|452:148,1,100,8|0 +376,268,1125934,2,0,B|364:168,1,100,8|0 +456,304,1126363,6,0,B|436:348|376:380|316:348|296:304,1,200,8|0 +256,192,1127006,1,0 +147,168,1127220,2,0,B|136:268,1,100,8|8 +55,303,1127649,2,0,B|76:348|136:380|196:348|216:303,2,200,8|4|0 +64,220,1128720,1,0 +111,151,1128934,6,0,B|88:80,1,74.9999977648259 +208,32,1129363,2,0,B|224:68|208:104,1,74.9999977648259 +304,156,1129791,2,0,B|288:120|304:84,1,74.9999977648259 +424,80,1130220,2,0,B|400:151,1,74.9999977648259 +482,201,1130649,5,0 +458,275,1130863,1,0 +397,322,1131077,1,0 +320,325,1131291,1,0 +256,284,1131506,1,0 +192,325,1131720,1,0 +115,322,1131934,1,0 +54,275,1132149,1,0 +30,201,1132363,1,0 +256,192,1132470,12,2,1135791 +80,56,1136220,5,0 +212,252,1136649,2,0,B|196:224|207:190|227:178|252:166|276:177|296:187|317:217|293:262,1,200,2|0 +432,56,1137505,1,2 +369,247,1137949,2,0,B|379:196|354:120|287:88|223:88|161:122|127:184|140:247,1,400,0|2 +68,288,1139060,1,0 +48,200,1139282,5,2 +256,224,1139743,1,0 +464,200,1140205,1,2 +346,34,1140776,2,0,B|307:69|273:52|256:35|256:35|238:52|204:69|164:31,1,200,0|2 +256,320,1153040,5,8 +256,252,1153343,1,8 +256,240,1153494,1,8 +256,228,1153646,1,8 +256,216,1153797,1,0 +256,204,1153949,1,4 +380,136,1154252,6,0,B|412:196,1,66.6666666666667,0|8 +456,124,1154555,2,0,B|423:62,1,66.6666666666667,0|0 +324,64,1154858,2,2,B|256:92|188:64,1,133.333333333333,2|0 +88,62,1155312,2,0,B|56:124,1,66.6666666666667,8|0 +99,197,1155616,2,0,B|132:136,1,66.6666666666667,0|0 +256,228,1156070,6,0,B|256:296,2,66.6666666666667,0|8|0 +256,140,1156525,1,0 +196,80,1156676,2,2,B|256:53|256:53|316:80,1,133.333333333333,2|0 +388,140,1157131,2,0,B|456:156,2,66.6666666666667,8|0|0 +316,201,1157585,6,0,B|255:227|255:227|195:201,1,133.333333333333,4|0 +124,140,1158040,2,0,B|56:156,1,66.6666666666667,8|0 +44,336,1158494,2,2,B|184:304,1,133.333333333333,2|0 +256,256,1158949,2,0,B|256:324,1,66.6666666666667,8|0 +468,336,1159403,6,0,B|328:304,1,133.333333333333,4|0 +324,212,1159858,2,0,B|360:152,1,66.6666666666667,8|0 +256,140,1160161,1,0 +188,212,1160312,2,0,B|152:152,1,66.6666666666667,2|0 +204,52,1160616,5,8 +228,40,1160767,1,8 +256,36,1160919,1,8 +288,40,1161070,1,0 +316,52,1161222,1,4 +468,156,1161525,6,0,B|468:224,1,66.6666666666667,0|8 +468,316,1161828,1,0 +380,344,1161979,1,0 +288,352,1162131,2,0,B|288:284|288:284|336:228,1,133.333333333333,2|0 +248,192,1162585,6,0,B|200:248,1,66.6666666666667,8|0 +140,312,1162888,1,0 +80,244,1163040,2,0,B|176:140,1,133.333333333333,0|0 +256,100,1163494,2,0,B|256:30,2,66.6666666666667,8|0|0 +337,141,1163949,2,0,B|432:244,1,133.333333333333,2|0 +339,268,1164403,2,0,B|305:222|256:203|206:222|172:268,1,200,8|4 +172,93,1165161,5,0 +171,92,1165312,2,0,B|205:137|256:156|305:137|339:92,1,200,8|2 +358,254,1166070,2,0,B|332:320,1,66.6666666666667,0|8 +256,360,1166373,1,0 +179,319,1166525,2,0,B|154:254,1,66.6666666666667,0|4 +316,112,1166979,6,0,B|304:44,2,66.6666666666667,8|8|8 +256,180,1167434,1,0 +196,112,1167585,2,8,B|208:44,2,66.6666666666667,8|8|0 +152,224,1168040,6,0,B|216:252,1,66.6666666666667,8|8 +360,224,1168343,2,0,B|296:252,1,66.6666666666667,0|4 +256,32,1168949,5,8 +420,112,1169252,1,0 +494,80,1169403,2,2,B|462:12|375:0|319:68|343:144|343:144|356:188|356:188|379:259|323:287,1,400,2|0 +144,268,1170616,2,0,B|136:232|144:200,1,66.6666666666667,0|8 +172,120,1170919,1,0 +224,56,1171070,2,0,B|256:32|288:56,1,66.6666666666667,0|2 +340,120,1171373,1,0 +367,199,1171525,2,0,B|376:232|368:268,1,66.6666666666667,0|8 +187,287,1172131,6,0,B|132:260|154:188|154:188|168:144|168:144|192:68|136:0|48:12|16:80,1,400,4|2 +24,164,1173191,1,0 +92,112,1173343,1,0 +164,68,1173494,2,0,B|196:48|232:60|259:88|259:88|283:112,1,133.333333333333,8|0 +328,184,1173949,1,2 +256,224,1174100,1,0 +184,184,1174252,1,0 +227,112,1174403,2,0,B|252:88|252:88|280:60|316:48|348:68,1,133.333333333333,8|0 +452,192,1174858,5,4 +256,356,1175161,1,2 +60,192,1175464,1,2 +256,28,1175767,1,4 +256,196,1176070,6,0,B|292:136,1,66.6666666666667,0|8 +256,196,1176373,2,0,B|220:136,1,66.6666666666667 +174,68,1176676,2,0,B|99:68|51:153|99:239|174:239,1,266.666666666667,4|2 +338,67,1177585,2,0,B|412:67|461:152|412:239|338:239,1,266.666666666667,4|2 +192,152,1178494,2,0,B|228:184|256:152|284:120|320:152,2,133.333333333333,4|2|2 +80,281,1179403,6,0,B|136:352|256:396|376:352|441:272,1,400,4|2 +448,188,1180464,1,0 +368,164,1180616,1,0 +356,248,1180767,2,0,B|332:272|292:276,1,66.6666666666667,8|0 +156,248,1181070,6,0,B|180:272|218:275,1,66.6666666666667,8|4 +212,192,1181373,2,0,B|188:168|150:165,1,66.6666666666667,0|0 +68,148,1181676,1,8 +84,64,1181828,1,8 +168,80,1181979,1,8 +256,106,1182131,6,0,B|256:36,1,66.6666666666667 +344,80,1182434,1,0 +428,64,1182585,1,8 +444,148,1182737,1,8 +362,164,1182888,2,0,B|324:168|300:192,1,66.6666666666667,0|4 +68,292,1183494,5,8 +40,212,1183646,2,0,B|84:192|140:220|148:268,2,133.333333333333,0|2|0 +32,128,1184403,1,8 +116,136,1184555,2,0,B|188:152|216:228,1,133.333333333333,0|4 +395,136,1185161,6,0,B|324:152|296:225,1,133.333333333333,0|0 +408,352,1185767,2,0,B|479:336|507:262,1,133.333333333333,2|0 +396,136,1186373,1,0 +104,352,1186676,6,0,B|32:336|4:262,1,133.333333333333,4|0 +80,220,1187131,1,8 +164,204,1187282,2,0,B|148:136,1,66.6666666666667 +192,69,1187585,2,0,B|256:87|256:87|319:69,1,133.333333333333,2|0 +364,135,1188040,2,0,B|348:204,1,66.6666666666667,8|0 +256,344,1188494,5,4 +140,120,1188797,1,2 +332,284,1189100,1,2 +256,44,1189403,1,4 +180,284,1189706,1,2 +372,120,1190009,1,2 +256,344,1190312,1,4 +488,244,1190767,6,0,B|436:40,1,200,8|2 +300,140,1191525,2,0,B|364:164,1,66.6666666666667,0|8 +212,140,1191979,2,0,B|148:164,1,66.6666666666667,8|2 +75,40,1192585,2,0,B|24:244,1,200,8|2 +204,268,1193343,5,0 +228,280,1193494,1,8 +256,284,1193646,1,8 +288,280,1193797,1,0 +316,268,1193949,1,4 +80,203,1194403,6,0,B|141:127|256:85|370:127|431:203,1,400,8|10 +212,312,1195767,2,0,B|180:252,1,66.6666666666667 +300,312,1196222,2,0,B|332:248,1,66.6666666666667,8|0 +212,144,1196676,5,8 +208,108,1196828,1,0 +224,76,1196979,1,0 +256,64,1197131,1,8 +288,76,1197282,1,0 +304,108,1197434,1,0 +300,144,1197585,1,12 +392,264,1197888,5,2 +392,264,1198040,1,2 +256,344,1198343,1,2 +256,344,1198494,1,0 +120,264,1198797,1,2 +176,104,1199100,1,2 +332,104,1199403,1,0 +256,216,1199706,5,0 +256,200,1199858,1,8 +256,184,1200009,1,0 +256,168,1200161,1,0 +256,152,1200312,1,2 +304,284,1200616,1,0 +284,304,1200767,1,8 +256,312,1200919,1,0 +228,304,1201070,1,0 +208,284,1201222,1,2 +84,192,1201525,6,0,B|52:280|52:280,1,66.6666666666667,0|8 +32,108,1201979,2,0,B|64:32|64:32,1,66.6666666666667,0|2 +201,101,1202434,2,0,B|219:71|252:59|293:64|315:108,1,133.333333333333 +313,250,1203040,2,0,B|296:278|260:291|216:285|192:238,1,133.333333333333,2|0 +155,317,1203494,6,0,B|182:349|233:356,1,66.6666666666667,8|0 +298,353,1203797,2,0,B|332:345|357:317,1,66.6666666666667,0|2 +256,188,1204252,1,0 +256,168,1204403,1,8 +256,148,1204555,1,0 +256,128,1204706,1,0 +256,104,1204858,1,4 +428,192,1205161,6,0,B|460:280|460:280,1,66.6666666666667,0|8 +480,108,1205616,2,0,B|448:32|448:32,1,66.6666666666667,0|2 +311,57,1206070,2,0,B|292:30|265:30|254:19|254:19|246:30|220:30|203:55,1,133.333333333333 +121,176,1206676,2,0,B|130:226|215:249|256:199|256:199|288:247|388:235|392:162,1,333.333333333333,2|0 +464,232,1207585,1,2 +337,345,1207888,5,0 +294,354,1208040,1,8 +256,336,1208191,1,0 +217,354,1208343,1,0 +178,345,1208494,1,2 +48,232,1208797,1,0 +48,232,1208949,1,8 +224,165,1209252,6,0,B|256:176|256:176|290:164,1,66.6666666666667,0|2 +197,56,1209706,2,0,B|255:28|255:28|317:55,2,133.333333333333,0|0|2 +116,184,1210616,1,0 +147,197,1210767,1,8 +169,224,1210919,1,0 +181,256,1211070,1,0 +180,292,1211222,1,2 +332,292,1211525,1,8 +331,256,1211676,1,8 +343,224,1211828,1,8 +365,197,1211979,1,0 +396,184,1212131,1,4 +336,44,1212434,6,0,B|336:77|336:77|314:106,1,66.6666666666667,0|8 +176,44,1212888,2,0,B|175:77|175:77|197:106,1,66.6666666666667,0|2 +200,244,1213343,2,0,B|217:272|253:285|297:279|321:232,1,133.333333333333 +99,267,1213949,2,0,B|131:363|263:412|385:368|423:243,1,400,2|2 +180,145,1215312,6,0,B|195:97|255:86|255:86|311:94|333:148,1,200,8|4 +124,44,1216222,5,8 +256,168,1216525,1,0 +256,168,1216676,1,2 +388,44,1216979,1,0 +435,226,1217282,1,2 +353,350,1217585,2,0,B|284:340|117:251|74:123|147:-32|400:-9|466:163|336:289|227:339|160:351,1,800,2|4 +77,224,1219706,1,0 +77,224,1219858,5,8 +120,48,1220161,1,2 +120,48,1220312,1,2 +256,120,1220616,1,0 +392,48,1220919,1,0 +400,200,1221222,2,0,B|382:282|318:334|255:334|166:330|116:273|107:181|107:181,2,400,2|2|4 +256,40,1223494,5,8 +256,184,1223797,1,2 +256,184,1223949,1,2 +319,332,1224252,2,0,B|300:360|264:373|220:367|196:320,1,133.333333333333,0|0 +96,200,1224858,1,2 +184,72,1225161,5,0 +256,40,1225312,1,8 +328,72,1225464,1,0 +416,200,1225767,1,0 +296,356,1226070,1,8 +320,280,1226222,1,8 +256,232,1226373,1,0 +192,280,1226525,1,0 +216,356,1226676,1,4 +336,111,1227131,6,0,B|352:47|288:15|256:15|223:15|159:47|175:111,2,274.000001469851,2|2|8 +256,304,1228343,1,0 +193,277,1228494,2,0,B|181:298|174:343|209:397|268:399|316:376|345:340|324:276|318:268,1,274.000001469851,2|8 +496,128,1229403,1,0 +256,32,1229706,5,0 +216,156,1229858,1,8 +320,80,1230009,1,8 +192,80,1230161,1,8 +296,156,1230312,1,4 +16,128,1230767,5,2 +175,288,1231222,2,0,B|159:352|223:384|256:384|288:384|352:352|336:288,1,274.000001469851,2|8 +438,116,1231979,1,0 +361,51,1232131,2,0,B|334:77|294:90|255:51|255:24|255:24|255:51|215:90|175:77|149:51,1,274.000001469851,2|8 +64,336,1233040,1,2 +256,224,1233343,5,0 +296,356,1233494,1,8 +192,276,1233646,1,8 +320,276,1233797,1,0 +216,356,1233949,1,4 +362,100,1234403,6,0,B|338:31|253:0|175:28|151:108,1,274.000001469851,2|2 +448,336,1235312,1,8 +256,212,1235616,1,0 +203,281,1235767,2,0,B|184:332|184:332|220:332|256:368|256:368|292:332|328:332|328:332|308:279,1,274.000001469851,2|8 +64,337,1236676,1,2 +180,88,1237131,6,0,B|203:42|256:31|310:43|333:90,1,182.666667646567,8|0 +362,187,1237585,2,0,B|324:295|164:293|148:177|148:177,3,274.000001469851,4|8|2|8 +40,360,1239252,1,8 +127,312,1239403,2,0,B|166:350|217:355,1,91.3333338232835,2|0 +305,353,1239706,2,0,B|346:350|385:312,1,91.3333338232835,0|8 +472,360,1240009,1,2 +474,357,1240085,1,0 +476,354,1240161,5,8 +488,248,1240312,1,0 +408,208,1240464,1,0 +320,144,1240616,1,0 +256,64,1240767,1,8 +192,144,1240919,1,0 +112,208,1241070,1,0 +24,248,1241222,1,4 +328,340,1241676,6,0,B|320:312|296:280|256:268|216:280|192:312|184:340,1,196.000001869202,8|0 +120,252,1242131,2,0,B|180:208|256:180|332:208|392:252,1,294.000002803803,2|8 +472,96,1242888,1,0 +392,36,1243040,6,0,B|332:80|256:108|180:80|120:36,1,294.000002803803,4|8 +16,68,1243646,2,0,B|8:168,1,98.0000009346008 +112,140,1243949,2,0,B|196:196|204:308,1,196.000001869202,2|0 +308,305,1244403,2,0,B|316:196|400:140,1,196.000001869202,8|2 +368,36,1244858,6,0,B|304:60|256:128|256:128|208:60|144:36,2,294.000002803803,4|8|2 +503,165,1246070,2,0,B|496:68,1,98.0000009346008,0|8 +424,148,1246373,2,0,B|431:245,1,98.0000009346008,0|0 +340,300,1246676,6,0,B|340:224|292:192|256:192|220:192|172:160|172:84,1,294.000002803803,4|8 +339,85,1247585,2,0,B|340:160|292:192|256:192|220:192|172:224|171:299,1,294.000002803803,2|8 +80,356,1248191,2,0,B|68:304|76:260,1,98.0000009346008,8|0 +136,168,1248494,6,0,B|212:168|228:256|304:256,1,196.000001869202,4|0 +412,248,1248949,2,0,B|480:252|496:180|496:180|512:104,1,196.000001869202,8|0 +424,40,1249403,2,0,B|356:36|340:108|340:108|324:184,1,196.000001869202,2|0 +324,184,1249858,1,8 +100,248,1250161,5,0 +100,248,1250312,2,0,B|32:252|16:180|16:180|0:104,1,196.000001869202,4|0 +88,40,1250767,2,0,B|156:36|172:108|172:108|188:184,1,196.000001869202,8|0 +292,212,1251222,2,0,B|384:220|384:220|396:164|372:120,1,196.000001869202,2|0 +324,60,1251676,1,8 +256,28,1251828,1,0 +188,60,1251979,1,0 +140,120,1252131,6,0,B|116:164|128:220|128:220|220:212,1,196.000001869202,4|0 +328,204,1252585,2,0,B|384:200|384:200|440:196|472:248|452:308|392:312|392:312|340:316,2,294.000002803803,8|2|8 +328,204,1253646,1,0 +174,316,1253949,2,0,B|120:312|120:312|60:308|40:248|72:196|128:200|128:200|184:204,1,294.000002803803,4|8 +192,96,1254555,2,0,B|96:88,2,98.0000009346008,0|8|4 +418,87,1255161,6,0,B|320:96,1,98.0000009346008,0|8 +280,184,1255464,2,0,B|378:175,1,98.0000009346008,8|0 +376,272,1255767,6,0,B|280:280|280:280|224:284|204:336,1,196.000001869202,4|0 +308,336,1256222,2,0,B|288:284|232:280|232:280|136:272,1,196.000001869202,8|0 +40,264,1256676,2,0,B|52:180|140:176|140:176|228:172|240:88,1,294.000002803803,2|8 +144,80,1257282,1,0 +136,272,1257585,5,4 +256,116,1257888,1,0 +256,116,1258040,1,8 +376,272,1258343,1,0 +376,272,1258494,2,0,B|424:252|460:200|440:136|416:108,1,196.000001869202,2|0 +332,60,1258949,2,0,B|280:60|256:8|256:8|232:60|180:60,1,196.000001869202,8|0 +96,108,1259403,2,0,B|71:136|51:200|87:252|135:272,1,196.000001869202,4|0 +180,360,1259858,2,0,B|232:360|256:308|256:308|280:360|332:360,1,196.000001869202,8|0 +424,320,1260312,2,0,B|368:276|336:192|368:108|424:64,2,294.000002803803,2|8|4 +256,176,1261676,5,8 +87,320,1262131,2,0,B|144:276|176:192|144:108|88:64,1,294.000002803803,2|8 +32,144,1262737,1,2 +32,240,1262888,1,0 +132,236,1263040,6,0,B|328:228,1,196.000001869202,4|0 +380,148,1263494,2,0,B|184:156,1,196.000001869202,8|0 +184,156,1263949,1,2 +332,60,1264252,2,0,B|429:55,1,98.0000009346008,0|8 +436,232,1264706,2,0,B|338:236,1,98.0000009346008,0|4 +82,329,1265161,6,0,B|180:324,1,98.0000009346008,0|8 +173,148,1265616,2,0,B|76:152,1,98.0000009346008,0|2 +132,236,1266070,1,0 +132,236,1266222,2,0,B|328:228,1,196.000001869202,8|0 +432,192,1266676,5,4 +256,128,1266979,1,0 +256,128,1267131,1,8 +80,192,1267434,1,0 +80,192,1267585,1,2 +352,240,1267888,1,0 +352,240,1268040,1,8 +160,240,1268343,1,0 +160,240,1268494,1,2 +316,372,1268949,5,8 +256,380,1269100,1,8 +196,372,1269252,1,2 +168,288,1269403,5,8 +256,276,1269555,1,0 +344,284,1269706,1,2 +372,200,1269858,5,8 +256,176,1270009,1,0 +140,200,1270161,1,2 +256,100,1270312,5,4 +384,280,1270767,6,0,B|436:280|488:252|512:192|488:132|436:104|384:104,1,294.000002803803,8|2 +300,126,1271373,2,0,B|300:224,1,98.0000009346008,0|2 +256,296,1271676,1,8 +212,224,1271828,2,0,B|212:126,1,98.0000009346008,0|2 +128,103,1272131,2,0,B|76:104|24:132|0:192|24:252|76:280|128:280,3,294.000002803803,4|2|0|2 +340,76,1273949,6,0,B|384:108|412:184|384:260|332:308|256:328|180:308|128:260|100:184|128:108|172:76,2,588.000005607605,4|2|4 +48,40,1276222,1,8 +36,136,1276373,1,0 +72,228,1276525,1,0 +140,296,1276676,2,0,B|220:304|256:224|256:224|292:304|372:296,1,294.000002803803,2|8 +440,228,1277282,1,0 +476,136,1277434,1,0 +464,40,1277585,1,4 +256,248,1278040,5,8 +256,64,1278494,1,2 +160,164,1278646,5,0 +160,260,1278797,1,0 +256,288,1278949,1,8 +352,260,1279100,1,0 +352,164,1279252,1,2 +256,136,1279403,1,4 +456,56,1279858,6,0,B|456:350,1,294.000002803803,8|2 +56,350,1280767,2,0,B|56:56,1,294.000002803803,8|4 +360,88,1281676,2,0,B|360:212|360:212|360:284|328:324|256:352|184:324|152:284|152:212|152:212|152:88,1,588.000005607605,8|8 +20,284,1283040,5,4 +8,208,1283191,1,0 +16,128,1283343,1,2 +80,80,1283494,1,8 +160,80,1283646,1,0 +228,120,1283797,1,2 +256,192,1283949,1,8 +284,264,1284100,1,0 +352,304,1284252,1,0 +432,304,1284403,1,8 +496,256,1284555,1,8 +504,176,1284706,1,0 +492,100,1284858,1,4 +208,192,1291222,5,0 +148,112,1291373,1,0 +148,112,1291449,1,0 +148,112,1291525,1,0 +52,132,1291676,1,0 +32,228,1291828,1,0 +100,292,1291979,1,0 +184,281,1292131,6,0,B|205:317|263:349|322:311|334:277,1,178.666658215332,4|2 +309,192,1292585,2,0,B|301:140|257:124|257:124|205:104|209:48,1,183.333327869574,8|0 +305,56,1293040,2,0,B|309:104|257:124|257:124|213:140|201:196,2,183.333327869574,2|0|0 +392,40,1293797,1,0 +464,96,1293949,1,2 +392,264,1294252,5,0 +336,328,1294403,1,8 +256,352,1294555,1,0 +176,328,1294706,1,0 +120,264,1294858,1,2 +120,120,1295161,1,0 +176,56,1295312,1,8 +256,32,1295464,1,0 +336,56,1295616,1,0 +392,120,1295767,1,2 +256,200,1296070,5,0 +256,200,1296222,1,8 +256,24,1296525,5,0 +256,24,1296676,1,2 +201,169,1296979,1,0 +331,76,1297282,1,2 +175,76,1297585,1,0 +297,171,1297888,1,0 +300,259,1298040,6,0,B|255:244|255:244|212:259,1,91.6666639347872,8|0 +212,349,1298343,2,0,B|256:363|256:363|299:349,1,91.6666639347872 +392,184,1298797,1,8 +346,101,1298949,1,8 +256,67,1299100,1,0 +165,101,1299252,1,8 +119,184,1299403,1,4 +256,276,1299706,5,0 +182,318,1299858,2,0,B|202:359|257:382|312:360|331:316,1,183.333327869574,2|0 +332,233,1300312,2,0,B|310:192|258:168|200:192|183:234,2,183.333327869574,2|0|0 +422,128,1301222,2,0,B|352:92|336:12|336:12|256:108|256:108|180:8|180:8|156:92|94:128,2,549.999983608723,2|2|4 +188,308,1303494,6,0,B|201:351|256:361|256:361|307:354|327:304,1,183.333327869574,8|0 +256,256,1303949,1,2 +360,80,1304252,1,0 +152,80,1304555,1,2 +68,233,1304858,2,0,B|52:304|140:352|191:289|191:289|209:334|255:347|302:334|320:291|320:291|370:351|463:307|443:225,2,549.999983608723,0|2|4 +256,68,1306979,5,0 +256,68,1307131,1,8 +256,252,1307434,1,0 +182,199,1307585,2,0,B|203:199|203:199|228:176|228:176|256:152|256:152|284:176|284:176|308:199|308:199|328:199,2,183.333327869574,2|0|0 +46,322,1308494,2,0,B|68:284|182:252|208:368|160:384|164:318|240:304|256:251|256:251|272:304|352:320|352:384|304:368|333:281|364:284|455:262|475:337|475:337,2,549.999983608723,2|2|4 +256,384,1310767,5,8 +332,219,1311070,1,0 +332,219,1311222,1,2 +179,217,1311525,1,0 +230,40,1311828,2,0,B|216:49|207:74|216:97|232:112|256:118|280:112|296:97|304:74|296:49|282:40,1,183.333327869574,2|0 +448,74,1312434,6,0,B|489:156,1,91.6666639347872,2|8 +444,238,1312737,2,0,B|416:326,1,91.6666639347872,0|8 +324,366,1313040,2,0,B|310:324|256:313|256:313|206:321|186:370,1,183.333327869574,8|0 +95,325,1313494,2,0,B|68:238,1,91.6666639347872,8|0 +23,156,1313797,2,0,B|64:74,1,91.6666639347872,8|4 +336,47,1314403,6,0,B|296:135|336:224,1,183.333327869574,8|0 +376,304,1314858,2,0,B|296:308|256:361|256:361|211:308|135:304,1,274.999991804362,2|8 +43,288,1315464,2,0,B|51:240|83:208,1,91.6666639347872,2|8 +174,224,1315767,6,0,B|215:135|175:47,1,183.333327869574,4|0 +83,51,1316222,2,0,B|139:107|127:200|43:240|-24:204,1,274.999991804362,8|2 +68,360,1316979,6,0,B|144:308,1,91.6666639347872,0|8 +256,372,1317282,2,0,B|256:280,1,91.6666639347872,8|0 +256,188,1317585,1,4 +444,360,1317888,6,0,B|368:308,1,91.6666639347872,0|8 +209,116,1318343,2,0,B|254:100|297:116,1,91.6666639347872 +344,43,1318646,2,0,B|256:2|167:43,1,183.333327869574,0|8 +44,176,1319252,1,0 +116,120,1319403,2,0,B|180:176|168:288,1,183.333327869574,2|0 +256,316,1319858,2,0,B|256:135,1,183.333327869574,8|0 +396,120,1320312,2,0,B|332:176|344:288,1,183.333327869574,2|0 +428,324,1320767,6,0,B|420:280|424:232,1,91.6666639347872,8|8 +464,84,1321070,2,0,B|472:128|468:176,1,91.6666639347872,0|4 +320,336,1321525,6,0,B|324:292|308:244,1,91.6666639347872,0|8 +256,172,1321828,1,0 +210,102,1321979,2,0,B|255:86|298:102,1,91.6666639347872 +191,339,1322434,6,0,B|187:295|202:249,1,91.6666639347872,0|8 +256,172,1322737,1,0 +312,100,1322888,1,0 +348,14,1323040,2,0,B|401:10|401:10|457:6|485:38|489:83,1,183.333327869574,2|0 +404,123,1323494,2,0,B|320:171|304:268,1,183.333327869574,8|0 +207,268,1323949,2,0,B|191:171|107:123,1,183.333327869574,2|0 +107,123,1324403,1,8 +192,92,1324555,5,0 +256,28,1324706,1,0 +320,92,1324858,1,4 +256,192,1325009,12,4,1326676 +60,192,1327131,5,0 +60,192,1327282,1,0 +160,136,1327434,1,0 +160,124,1327509,1,0 +160,112,1327585,5,2 +256,28,1327737,1,0 +352,112,1327888,1,0 +452,192,1328040,1,8 +352,272,1328191,1,0 +256,356,1328343,1,0 +160,272,1328494,1,4 +40,166,1328797,6,0,B|40:120|64:80,1,91.3333338232835,0|8 +255,146,1329252,2,0,B|268:176|244:208|256:236,1,91.3333338232835,0|2 +471,166,1329706,2,0,B|472:120|448:80,1,91.3333338232835,0|8 +364,312,1330161,5,0 +364,312,1330312,2,0,B|336:380|256:408|175:380|147:312,1,274.000001469851,4|8 +192,232,1330919,1,0 +256,168,1331070,1,0 +320,232,1331222,2,0,B|364:232|408:208|424:163|416:107,1,182.666667646567,2|0 +340,64,1331676,2,0,B|296:96|256:64|256:64|216:32|172:64,1,182.666667646567,8|2 +96,107,1332131,6,0,B|88:164|104:208|148:232|192:232,1,182.666667646567,4|0 +284,232,1332585,2,0,B|332:92|470:44,1,274.000001469851,8|2 +227,151,1333494,2,0,B|178:290|40:338,1,274.000001469851,8|4 +40,156,1334252,6,0,B|84:168|84:168|132:168,1,91.3333338232835,0|8 +179,91,1334555,2,0,B|203:55|256:27|308:55|332:91,1,182.666667646567,0|2 +360,180,1335009,1,0 +336,268,1335161,1,0 +256,308,1335312,1,8 +176,268,1335464,1,8 +152,180,1335616,1,0 +148,88,1335767,6,0,B|208:56|208:56|256:84|316:88,1,182.666667646567,4|0 +408,84,1336222,2,0,B|376:144|376:144|404:192|408:252,1,182.666667646567,8|0 +364,344,1336676,2,0,B|304:312|304:312|256:340|196:344,1,182.666667646567,2|0 +104,300,1337131,2,0,B|136:240|136:240|108:192|104:132,1,182.666667646567,8|0 +27,183,1337585,6,0,B|75:244|175:236|215:147|179:83,1,274.000001469851,4|8 +256,32,1338191,2,0,B|256:216,1,182.666667646567,0|2 +332,84,1338797,1,0 +332,83,1338949,2,0,B|296:148|336:236|436:244|484:183,1,274.000001469851,8|4 +256,192,1339555,12,6,1342131 +112,112,1342282,5,0 +168,40,1342434,1,0 +256,12,1342585,1,8 +344,40,1342737,1,0 +400,112,1342888,1,0 +332,170,1343040,6,0,B|312:129|257:106|202:128|183:172,1,182.666667646567,6|0 +181,260,1343494,2,0,B|201:301|256:324|311:302|330:258,1,182.666667646567,10|0 +256,216,1343949,1,0 +208,384,1344252,2,0,B|304:384|304:384,1,91.3333338232835,0|8 +256,216,1344706,1,0 +124,38,1345161,2,0,B|138:78|197:93|226:63|226:19|190:5|160:49|208:93|256:93|256:93|303:93|351:49|321:5|285:19|285:63|318:93|387:71|395:34,1,421.333338858287,2|0 +440,216,1346070,5,2 +388,304,1346222,1,10 +256,140,1346525,1,0 +204,212,1346676,2,0,B|212:264|256:280|256:280|308:300|304:356,1,182.666667646567 +208,348,1347131,2,0,B|204:300|256:280|256:280|300:264|312:208,1,182.666667646567,8|0 +256,140,1347585,1,0 +72,216,1347888,1,2 +124,304,1348040,1,10 +160,156,1348343,6,0,B|213:103|186:3|123:-2|81:55|123:108|202:81|257:14|257:14|310:81|390:108|432:55|390:-2|326:3|299:103|352:156,1,639.333336762985,0|2 +344,236,1349555,6,0,B|388:316,1,91.3333338232835,0|0 +256,364,1349858,2,0,B|256:252,1,91.3333338232835,8|0 +124,315,1350161,2,0,B|168:236,1,91.3333338232835,0|4 +44,44,1350767,1,8 +335,282,1351222,2,0,B|376:257|405:184|376:114|328:68|258:49|185:68|135:114|111:184|135:257|178:284,1,548.000002939701,2|2 +468,44,1352585,1,8 +357,277,1353040,6,0,B|339:348|254:387|171:346|154:277,1,274.000001469851,4|8 +22,192,1353797,2,0,B|5:-6|238:-24|305:41|372:108|388:158|354:192|256:441|156:192|122:158|138:108|205:41|272:-24|505:-6|483:194,1,1096.0000058794,0|0 +377,321,1355767,2,0,B|319:381|256:336|256:336|187:384|131:316,1,274.000001469851,2|2 +200,268,1356373,1,0 +140,212,1356525,1,0 +132,132,1356676,1,4 +180,64,1356828,1,0 +256,40,1356979,1,0 +332,64,1357131,1,6 +380,132,1357282,1,0 +372,212,1357434,1,0 +312,268,1357585,1,4 +52,80,1358494,6,0,B|52:37|113:29|132:69|132:69|150:110|197:110|214:69|214:69|229:29|279:29|296:69|296:69|312:110|360:110|376:69|376:69|397:29|458:37|458:81,2,548.000002939701,8|0|8 +256,192,1360767,1,0 +52,302,1361222,2,0,B|52:345|113:353|132:313|132:313|150:272|197:272|214:313|214:313|229:353|279:353|296:313|296:313|312:272|360:272|376:313|376:313|397:353|458:345|458:301,2,548.000002939701,0|8|0 +256,44,1363494,5,0 +244,104,1363949,1,8 +264,152,1364403,1,0 +244,208,1364858,1,4 +256,356,1365767,1,8 +256,356,1366373,5,0 +256,356,1366525,1,0 +256,356,1366676,1,0 +308,196,1367131,1,0 +176,288,1367585,1,8 +336,288,1368040,1,0 +204,196,1368494,1,0 +361,51,1369403,6,0,B|334:77|294:90|255:51|255:24|255:24|255:51|215:90|175:77|149:51,1,274.000001469851,8|0 +140,60,1370009,1,0 +127,69,1370161,1,0 +117,79,1370312,1,0 +256,192,1370767,1,0 +344,296,1371222,1,8 +320,324,1371373,1,0 +292,340,1371525,1,0 +260,344,1371676,1,8 +220,340,1371828,1,0 +192,324,1371979,1,0 +168,296,1372131,1,4 +256,192,1383040,12,0,1386676 +256,48,1387131,5,4 +256,216,1387585,1,0 +256,216,1388040,1,8 +176,236,1388191,1,0 +96,256,1388343,1,0 +40,200,1388494,6,0,B|20:156|40:112,1,91.3333338232835 +164,88,1388797,2,0,B|144:128|144:128|108:156,1,91.3333338232835,0|4 +210,327,1389403,6,0,B|256:344|300:328,1,91.3333338232835,0|0 +208,192,1389706,2,0,B|254:175|298:191,1,91.3333338232835,0|4 +472,200,1390161,5,0 +472,200,1390312,2,0,B|492:156|472:112,1,91.3333338232835 +348,88,1390616,2,0,B|368:128|368:128|404:156,1,91.3333338232835,0|4 +256,216,1391222,5,0 +256,216,1391676,1,8 +336,236,1391828,1,0 +416,256,1391979,1,0 +384,332,1392131,6,0,B|296:308,1,91.3333338232835 +128,332,1392434,2,0,B|216:308,1,91.3333338232835,0|4 +384,128,1393040,6,0,B|296:152,1,91.3333338232835 +128,128,1393343,2,0,B|216:152,1,91.3333338232835,0|4 +408,232,1393797,5,0 +408,232,1393949,2,0,B|316:232,1,91.3333338232835 +104,232,1394252,2,0,B|195:232,1,91.3333338232835,0|4 +195,232,1394858,1,0 +302,40,1395312,6,0,B|210:40,1,91.3333338232835,8|0 +296,116,1395616,2,0,B|380:152,1,91.3333338232835 +300,220,1395919,2,0,B|256:228|256:228|212:220,1,91.3333338232835 +216,116,1396222,2,0,B|48:188,1,182.666667646567,4|0 +80,312,1396676,5,8 +164,260,1396828,1,0 +256,300,1396979,1,0 +256,300,1397131,1,4 +395,232,1397282,6,0,B|353:313,1,91.3333338232835,0|0 +256,300,1397585,1,8 +159,313,1397737,2,0,B|117:232,1,91.3333338232835,0|0 +256,116,1398040,1,4 +256,116,1398494,1,0 +356,336,1398949,6,0,B|356:244,1,91.3333338232835,8|0 +256,348,1399252,2,0,B|256:256,1,91.3333338232835 +156,336,1399555,2,0,B|156:244,1,91.3333338232835 +256,116,1399858,1,4 +280,32,1400009,2,0,B|208:32,2,45.6666669116418 +356,84,1400312,1,8 +376,180,1400464,1,0 +348,276,1400616,1,0 +256,316,1400767,1,8 +164,276,1400919,1,0 +136,180,1401070,1,0 +152,88,1401222,6,0,B|112:24,2,45.6666669116418 +208,152,1401525,1,0 +256,72,1401676,2,0,B|256:24,2,45.6666669116418,8|0|0 +304,148,1401979,1,0 +360,88,1402131,2,0,B|400:24,2,45.6666669116418 +304,148,1402434,1,0 +256,216,1402585,2,0,B|256:308,1,91.3333338232835,8|0 +256,384,1402888,1,0 +176,352,1403040,5,2 +156,316,1403116,1,0 +136,280,1403191,1,0 +176,208,1403343,1,0 +184,128,1403494,2,0,B|204:84|256:60|308:84|328:128,1,182.666667646567,8|0 +336,208,1403949,1,8 +376,280,1404100,1,2 +356,316,1404176,1,0 +336,352,1404252,1,0 +256,384,1404403,5,8 +196,268,1404555,1,2 +316,268,1404706,1,0 +416,368,1404858,6,0,B|476:344|492:280|492:280|508:216,1,182.666667646567,4|0 +500,136,1405312,2,0,B|404:172|328:104,1,182.666667646567,8|0 +344,23,1405767,2,0,B|256:-16|167:23,1,182.666667646567,4|2 +11,135,1406373,6,0,B|107:171|180:105,1,182.666667646567,0|2 +330,277,1406979,2,0,B|404:212|500:248,1,182.666667646567,8|0 +180,277,1407585,2,0,B|107:212|11:247,1,182.666667646567,10|2 +120,332,1408040,5,8 +256,248,1408191,1,8 +392,332,1408343,1,0 +384,147,1408494,6,0,B|360:95|304:55|239:63|183:135|195:192|256:232|316:192|328:135|272:63|207:55|151:95|127:147,1,464.000007743836,4|2 +80,208,1409555,2,0,B|104:244|96:292,1,76.1333349488259 +12,180,1409858,2,0,B|12:136|40:100,1,76.1333349488259,8|0 +473,282,1410312,6,0,B|500:248|500:204,1,76.1333349488259,2|0 +415,95,1410616,2,0,B|408:140|432:176,1,76.1333349488259,0|8 +364,268,1410919,2,0,B|280:252,1,76.1333349488259,2|0 +148,116,1411222,2,0,B|232:132,1,76.1333349488259,2|0 +144,304,1411525,6,0,B|116:272|112:228,1,76.1333349488259,0|8 +368,80,1411828,2,0,B|396:112|400:156,1,76.1333349488259,2|8 +256,192,1412131,5,4 +256,192,1412282,12,8,1413494 +84,108,1413949,6,0,B|20:160,1,76.1333349488259,4|0 +84,251,1414252,2,0,B|128:180,1,76.1333349488259,8|2 +216,125,1414555,1,2 +216,125,1414631,1,0 +216,125,1414706,2,0,B|200:209,1,76.1333349488259,0|8 +312,208,1415009,2,0,B|296:125,1,76.1333349488259,0|2 +384,180,1415312,2,0,B|428:252,1,76.1333349488259,2|0 +493,160,1415616,2,0,B|428:108,1,76.1333349488259,0|4 +336,272,1416222,6,0,B|352:336|288:368|256:368|223:368|159:336|175:272,2,274.000001469851,2|2|8 +256,80,1417434,1,0 +193,107,1417585,2,0,B|181:86|174:41|209:-13|268:-15|316:8|345:44|324:108|313:114,1,274.000001469851,2|8 +496,256,1418494,1,2 +256,352,1418797,5,0 +216,228,1418949,1,8 +320,304,1419100,1,8 +192,304,1419252,1,8 +296,228,1419403,1,4 +16,256,1419858,5,2 +175,95,1420312,2,0,B|159:31|223:0|256:0|288:0|352:31|336:95,1,274.000001469851,2|8 +438,268,1421070,1,0 +361,333,1421222,2,0,B|334:307|294:294|255:333|255:360|255:360|255:333|215:294|175:307|149:333,1,274.000001469851,2|8 +80,288,1421828,2,0,B|108:248|160:236,1,91.3333338232835 +112,168,1422131,2,0,B|160:160|188:120,1,91.3333338232835,8|0 +256,76,1422434,1,0 +324,120,1422585,2,0,B|352:160|400:168,1,91.3333338232835,8|0 +357,237,1422888,2,0,B|400:248|432:288,1,91.3333338232835,0|4 +150,100,1423494,6,0,B|174:31|259:0|337:28|361:108,1,274.000001469851,2|2 +64,336,1424403,1,8 +256,212,1424706,1,0 +309,281,1424858,2,0,B|328:332|328:332|292:332|256:368|256:368|220:332|184:332|184:332|204:279,1,274.000001469851,2|8 +448,337,1425767,1,2 +180,88,1426222,6,0,B|203:42|256:31|310:43|333:90,1,182.666667646567,8|0 +362,187,1426676,2,0,B|324:295|164:293|148:177|148:177,3,274.000001469851,4|8|2|8 +40,360,1428494,1,2 +127,312,1428646,2,0,B|166:350|217:355,1,91.3333338232835,2|0 +305,353,1428949,2,0,B|346:350|385:312,1,91.3333338232835,8|8 +476,354,1429252,1,8 +488,248,1429403,5,8 +408,208,1429555,1,0 +320,144,1429706,1,0 +256,64,1429858,1,8 +192,144,1430009,1,0 +112,208,1430161,1,0 +24,248,1430312,1,4 +337,289,1430767,6,0,B|321:338|256:371|190:338|174:289,1,210.666669429143,2|0 +256,248,1431222,1,2 +256,56,1431525,1,0 +175,87,1431676,2,0,B|191:127|256:151|256:151|313:129|336:87,1,210.666669429143,8|0 +416,144,1432131,1,2 +416,336,1432434,2,0,B|368:312|368:312|344:264,1,105.333334714572,0|8 +166,266,1432888,2,0,B|144:312|144:312|96:336,1,105.333334714572,0|2 +96,144,1433343,5,0 +174,91,1433494,2,0,B|229:91|256:35|256:35|282:91|337:91,1,210.666669429143,8|0 +400,184,1433949,2,0,B|430:234|430:234|457:284|446:343|379:351|379:351,1,210.666669429143,2|0 +294,306,1434403,2,0,B|299:294|311:265|305:238|281:222|254:214|225:221|205:241|201:269|207:289|220:306,1,210.666669429143,8|0 +132,351,1434858,2,0,B|65:343|54:284|81:234|81:234|112:184,1,210.666669429143,2|0 +136,96,1435312,5,8 +215,45,1435464,2,0,B|222:20|255:3|288:20|296:45,1,105.333334714572,2|0 +376,96,1435767,1,2 +440,276,1436070,2,0,B|412:308|412:308|376:312|376:312|360:336,1,105.333334714572,0|8 +308,256,1436373,2,0,B|320:220|320:220|352:208|352:208|364:172,1,105.333334714572,2|0 +256,160,1436676,2,0,B|264:136|264:136|240:96|240:96|256:68|256:68,2,105.333334714572,8|0|0 +149,176,1437131,2,0,B|160:208|160:208|192:220|192:220|204:256,1,105.333334714572,8|0 +150,333,1437434,2,0,B|136:312|136:312|100:308|100:308|72:276,1,105.333334714572,0|4 +256,124,1437888,5,0 +176,92,1438040,2,0,B|192:43|257:10|323:43|339:92,1,210.666669429143,8|0 +360,192,1438494,2,0,B|136:192,1,210.666669429143,2|0 +176,292,1438949,2,0,B|192:341|257:374|323:341|339:292,1,210.666669429143,8|0 +416,348,1439403,5,2 +256,252,1439706,1,0 +256,252,1439858,1,8 +96,348,1440161,1,0 +96,348,1440312,1,2 +112,160,1440616,1,0 +164,80,1440767,1,8 +256,52,1440919,1,0 +348,80,1441070,1,0 +392,156,1441222,1,6 +256,192,1441373,12,2,1443040 +256,344,1443191,5,0 +344,312,1443343,1,10 +404,240,1443494,1,0 +404,144,1443646,1,8 +344,72,1443797,1,0 +256,40,1443949,1,8 +168,72,1444100,1,0 +108,144,1444252,1,8 +108,240,1444403,1,8 +168,312,1444555,5,8 +256,344,1444706,1,8 +344,312,1444858,1,4 +412,136,1445161,1,2 +452,52,1445312,1,8 +256,84,1445616,1,2 +256,176,1445767,1,0 +100,136,1446070,1,2 +60,52,1446222,1,8 +134,257,1446525,22,0,B|160:337|259:373|350:340|378:247,1,316.000004143715,2|2 +432,331,1447131,2,0,B|504:216|466:84|466:84,1,210.666669429143,8|0 +402,67,1447585,1,2 +256,188,1447888,1,0 +350,175,1448040,2,0,B|363:214|334:293|255:300|213:278,1,210.666669429143,8|0 +161,208,1448494,2,0,B|148:169|177:90|256:83|298:105,1,210.666669429143,2|0 +256,188,1448949,1,8 +112,68,1449252,1,0 +35,130,1449403,2,0,B|28:248|84:336,1,210.666669429143,2|0 +172,356,1449858,1,8 +300,236,1450161,38,0,B|323:218|338:177|323:136|295:111|254:100|213:111|185:136|170:177|185:218|209:236,1,316.000004143715,2|0 +104,228,1450767,1,8 +108,132,1450919,1,8 +164,56,1451070,1,0 +256,28,1451222,1,8 +348,56,1451373,1,8 +404,132,1451525,1,0 +408,228,1451676,1,8 +344,300,1451828,1,0 +256,332,1451979,1,8 +168,300,1452131,1,4 +44,44,1452585,21,2 +256,232,1453040,1,2 +312,60,1453343,1,0 +168,160,1453646,1,2 +348,160,1453949,1,2 +204,60,1454252,1,2 +256,136,1454403,1,8 +468,44,1454858,1,2 +448,232,1455161,5,0 +448,232,1455312,1,8 +296,352,1455616,1,0 +218,297,1455767,2,0,B|189:364|93:358|82:299|67:262|90:207|157:188|201:214|200:251,1,316.000004143715,2|8 +294,87,1456676,2,0,B|323:20|418:25|429:84|444:121|422:177|354:195|311:170|311:132,1,316.000004143715,2|8 +436,286,1457434,21,2 +350,317,1457585,1,8 +256,302,1457737,1,2 +185,247,1457888,1,2 +161,160,1458040,1,8 +177,66,1458191,1,0 +256,26,1458343,1,2 +334,66,1458494,1,10 +350,160,1458646,1,8 +326,247,1458797,1,0 +256,302,1458949,1,8 +161,317,1459100,1,8 +75,286,1459252,1,0 +40,200,1459403,38,0,B|8:160|16:88|64:48|136:56,1,210.666669429143,4|2 +200,112,1459858,1,8 +256,184,1460009,1,0 +312,112,1460161,1,2 +388,55,1460312,2,0,B|448:48|496:88|504:160|472:200,1,210.666669429143,0|0 +392,248,1460767,1,8 +296,288,1460919,2,0,B|289:263|256:246|223:263|215:288,1,105.333334714572,2|0 +128,240,1461222,1,0 +256,360,1461525,21,0 +256,360,1461676,1,8 +216,176,1461979,2,0,B|223:201|256:218|289:201|297:176,1,105.333334714572,2|0 +120,112,1462434,1,0 +168,32,1462585,1,8 +256,8,1462737,1,0 +344,32,1462888,1,0 +392,112,1463040,1,4 +256,192,1463191,12,2,1464858 +278,319,1465312,6,0,B|239:286|239:286|175:289|175:289,1,105.333334714572,8|0 +360,268,1465616,2,0,B|348:219|348:219|378:162|378:162,1,105.333334714572,2|8 +233,64,1465919,2,0,B|272:97|272:97|336:94|336:94,1,105.333334714572,0|8 +151,115,1466222,2,0,B|163:164|163:164|133:221|133:221,1,105.333334714572,8|0 +94,295,1466525,5,8 +194,352,1466676,2,0,B|203:291|254:272|254:272|315:249|310:184,1,210.666669429143,4|0 +328,96,1467131,1,8 +256,40,1467282,1,0 +184,96,1467434,1,0 +201,192,1467585,2,0,B|196:249|257:272|257:272|308:291|318:352,1,210.666669429143,2|0 +418,295,1468040,5,8 +472,216,1468191,2,0,B|416:192|424:128|424:128,1,105.333334714572 +344,80,1468494,2,0,B|304:96|264:80|256:48|256:48|248:80|208:96|168:80,1,210.666669429143,2|0 +168,176,1468949,2,0,B|216:208|216:208|256:184|256:184|296:208|296:208|344:176,1,210.666669429143,8|0 +256,280,1469403,5,2 +40,216,1469706,2,0,B|97:187|88:128|88:128,1,105.333334714572,0|8 +159,47,1470009,1,0 +257,24,1470161,1,0 +355,49,1470312,2,0,B|483:105|483:330|316:388|254:334|254:334|168:248|196:161|312:161|339:248|254:334|254:334|168:392|24:307|24:105|173:40,1,1264.00001657486,2|8 +264,76,1472282,5,8 +264,76,1472434,1,0 +264,76,1472585,1,8 +264,76,1472737,1,0 +264,76,1472888,1,8 +264,76,1473040,1,4 +400,320,1473494,1,6 +112,320,1473949,1,4 +256,192,1474100,12,0,1476676 +256,44,1505977,5,4 +172,96,1506172,1,4 +256,148,1506367,2,2,B|256:364,1,200,4|6 +100,332,1507536,6,0,B|80:216|132:128,1,200,0|8 +300,36,1508315,2,0,B|372:104|488:120,1,200 +472,220,1508899,1,0 +376,188,1509094,1,0 +216,308,1509484,2,0,B|112:308|16:376,1,200,8|0 +68,172,1510263,5,4 +220,44,1510652,1,0 +228,144,1510847,1,0 +308,204,1511042,1,8 +428,44,1511432,1,0 +488,236,1511821,1,0 +440,324,1512016,1,0 +348,284,1512211,1,0 +160,356,1512600,2,0,B|192:256|164:156,1,200,8|0 +348,76,1513380,5,6 +152,44,1513769,2,0,B|108:60|96:128,2,100,0|0|8 +256,216,1514549,1,0 +256,216,1514938,2,0,B|368:216,2,100 +64,164,1515717,2,0,B|124:72|224:52,1,200,8|0 +420,84,1516497,5,4 +384,280,1516886,1,0 +192,332,1517276,1,8 +68,176,1517665,1,0 +268,200,1518055,2,0,B|320:224|364:192,2,100 +116,104,1518834,5,8 +120,204,1519029,1,0 +220,188,1519224,1,0 +216,88,1519419,2,2,B|316:60|428:92,1,200 +364,176,1520003,1,0 +428,252,1520198,1,2 +260,364,1520587,5,0 +252,264,1520782,1,0 +156,288,1520977,2,2,B|116:188|180:88,1,200 +256,36,1521562,1,0 +396,176,1521951,5,8 +304,132,1522146,1,0 +204,124,1522341,1,0 +120,180,1522536,2,2,B|100:296|212:356,1,200 +256,268,1523120,1,0 +316,348,1523315,2,2,B|415:295|395:179,1,200 +296,168,1523899,1,0 +268,72,1524094,2,2,B|180:120|56:68,1,200 +32,164,1524678,1,0 +172,304,1525068,1,8 +216,216,1525263,5,0 +316,224,1525458,1,0 +332,124,1525652,2,2,B|460:112|492:216,1,200 +420,280,1526237,1,0 +336,336,1526432,2,2,B|240:352|128:336,1,200 +56,280,1527016,1,0 +68,180,1527211,2,2,B|172:116|204:20,1,200 +256,116,1527795,1,0 +432,212,1528185,1,8 +336,244,1528380,5,0 +408,316,1528574,1,0 +316,356,1528769,2,2,B|112:356,1,200 +16,356,1529354,1,0 +32,156,1529743,6,0,B|16:108|32:52,1,100,8|0 +104,128,1530133,2,0,B|120:180|176:208,1,100 +200,108,1530523,2,0,B|222:195|310:225|393:202|416:96,1,300,4|0 +476,184,1531302,1,8 +452,320,1531497,5,0 +360,360,1531691,1,0 +276,308,1531886,2,2,B|180:348|80:256,1,200 +32,184,1532471,1,0 +32,184,1532665,2,2,B|60:52|176:52,1,200 +260,64,1533250,1,0 +260,64,1533445,2,2,B|344:148|260:248,1,200 +196,316,1534029,2,0,B|80:316,2,100,0|0|8 +336,316,1534613,5,0 +432,296,1534808,1,0 +436,196,1535003,2,2,B|428:84|300:32,1,200 +256,116,1535587,1,0 +188,44,1535782,2,2,B|83:85|75:197,1,200 +176,184,1536367,2,0,B|184:240|135:282,1,100,0|2 +200,356,1536756,1,4 +256,164,1537146,1,0 +320,352,1537536,1,8 +448,292,1537730,6,0,B|412:244|444:184,2,100,0|0|2 +484,96,1538510,1,0 +392,64,1538704,1,0 +312,124,1538899,1,2 +216,300,1539289,1,0 +124,340,1539484,1,0 +108,240,1539678,1,2 +188,180,1539873,2,0,B|156:80|44:40,2,200,0|0|8 +280,72,1540847,5,2 +380,84,1541042,1,0 +424,172,1541237,1,2 +440,272,1541432,1,0 +372,344,1541626,2,2,B|264:384|188:292,1,200,2|0 +256,220,1542211,6,0,B|144:220,1,100,8|0 +168,120,1542600,2,0,B|56:120,1,100 +40,216,1542990,2,0,B|104:304|216:320,1,200,4|0 +256,232,1543574,1,0 +256,232,1543769,1,8 +388,272,1543964,5,0 +404,172,1544159,1,0 +364,80,1544354,2,2,B|160:80,1,200 +72,124,1544938,1,0 +160,168,1545133,2,2,B|108:268|192:360,1,200 +260,288,1545717,1,0 +336,352,1545912,1,2 +388,160,1546302,1,0 +484,184,1546497,1,0 +464,88,1546691,2,2,B|364:48|248:88,1,200 +256,180,1547276,1,0 +256,192,1547471,12,6,1549224 +408,32,1550782,5,6 +104,132,1551172,1,0 +408,228,1551562,1,8 +100,312,1551951,1,2 +256,272,1552341,5,0 +256,172,1552536,1,0 +256,272,1552730,1,0 +256,72,1553120,1,8 +56,72,1553510,6,2,B|84:164|20:264,1,200,2|6 +160,120,1554289,2,2,B|256:80|356:124,1,200,2|10 +492,268,1555068,2,2,B|429:169|457:77,1,200 +356,80,1555652,1,0 +260,60,1555847,1,2 +256,220,1556237,1,8 +256,220,1556626,1,0 +224,316,1556821,1,0 +124,308,1557016,5,6 +256,60,1557406,1,0 +380,312,1557795,1,8 +256,60,1558185,2,2,B|256:264,1,200,2|0 +256,260,1558769,1,0 +256,260,1558964,1,0 +256,192,1559354,12,0,1561302 +68,116,1561691,5,0 +168,116,1561886,1,8 +216,136,1561984,1,8 +260,116,1562081,1,8 +360,116,1562276,1,8 +324,208,1562471,1,8 +424,192,1562665,1,0 +360,116,1562860,1,0 +432,280,1563055,6,0,B|384:300|320:292,1,100,0|6 +184,160,1563639,2,0,B|128:160|104:108,2,100,0|0|8 +368,80,1564419,2,2,B|360:196|276:256,1,200 +228,168,1565003,1,0 +280,252,1565198,1,0 +100,336,1565587,6,2,B|32:272|32:136,1,200,10|2 +84,64,1566172,2,0,B|140:64|180:96,1,100,0|4 +256,276,1566756,1,2 +368,108,1567146,1,8 +488,268,1567536,2,2,B|440:348|304:328,1,200 +212,348,1568120,1,0 +200,248,1568315,2,2,B|136:164|12:176,1,200,2|10 +168,40,1569094,1,0 +256,84,1569289,2,0,B|276:28,2,50,0|0|4 +472,192,1569873,5,0 +256,336,1570263,1,8 +40,192,1570652,2,2,B|248:192,1,200,2|0 +240,192,1571237,1,0 +240,192,1571432,1,0 +256,192,1571821,12,0,1573769 +392,292,1574159,5,0 +360,156,1574354,1,8 +236,92,1574549,1,0 +128,180,1574743,1,0 +112,320,1574938,1,8 +240,264,1575133,1,0 +128,180,1575328,1,0 +284,152,1575523,5,0 +300,200,1575620,1,0 +316,248,1575717,1,4 +68,48,1585068,5,2 +132,280,1585652,1,2 +192,48,1586237,1,2 +432,192,1586626,5,2 +192,192,1587211,1,2 +316,88,1587600,5,0 +416,72,1587795,1,0 +316,88,1587990,2,2,B|344:196|288:288,1,200,2|2 +220,352,1588574,1,0 +200,256,1588769,2,2,B|124:192|12:168,1,200 +64,80,1589354,1,0 +64,80,1589549,1,2 +260,44,1589938,2,2,B|412:44,1,150,0|8 +488,104,1590717,5,0 +472,204,1590912,1,0 +376,236,1591107,1,2 +204,340,1591497,1,0 +248,252,1591691,1,0 +148,260,1591886,1,2 +28,100,1592276,1,0 +128,104,1592471,1,0 +208,44,1592665,2,2,B|324:32|380:132,1,200 +428,216,1593250,1,0 +484,24,1593639,1,8 +452,120,1593834,5,0 +428,216,1594029,1,0 +340,164,1594224,1,2 +256,344,1594613,1,0 +256,244,1594808,1,0 +256,344,1595003,1,2 +60,300,1595393,1,0 +156,276,1595587,1,0 +132,180,1595782,2,2,B|108:72|20:8,1,200 +20,316,1596756,1,8 +160,328,1596951,5,0 +260,348,1597146,1,0 +328,276,1597341,1,2 +300,180,1597536,1,0 +204,164,1597730,1,2 +204,164,1598315,1,8 +28,260,1598704,5,0 +80,172,1598899,1,0 +156,240,1599094,1,0 +320,168,1599484,1,0 +280,76,1599678,1,0 +380,84,1599873,1,8 +404,180,1600068,6,0,B|428:284,2,100,0|8|10 +256,44,1600847,1,0 +256,144,1601042,1,0 +256,44,1601237,1,2 +56,68,1601626,2,0,B|88:120|56:168,1,100 +52,264,1602016,2,2,B|96:364|232:352,1,200 +312,328,1602600,1,0 +152,208,1602990,1,8 +208,80,1603185,5,2 +304,100,1603380,1,0 +400,80,1603574,1,2 +464,268,1603964,1,0 +404,348,1604159,1,0 +304,352,1604354,1,2 +128,256,1604743,1,0 +224,280,1604938,1,0 +316,236,1605133,2,2,B|440:236|500:136,2,200,2|0|2 +224,280,1606107,1,8 +256,144,1606302,5,0 +160,120,1606497,1,0 +96,196,1606691,1,2 +292,232,1607081,1,0 +348,316,1607276,1,0 +444,292,1607471,1,2 +248,328,1607860,1,0 +248,328,1608055,2,2,B|148:292|108:176,1,200,2|0 +116,88,1608639,1,2 +200,144,1608834,2,0,B|252:164|312:144,2,100,0|0|8 +252,60,1609419,6,0,B|300:40|356:64,1,100,2|0 +440,100,1609808,2,0,B|420:148|456:216,1,100,2|0 +408,288,1610198,1,2 +124,192,1610782,5,8 +252,344,1611172,1,0 +156,316,1611367,1,0 +60,348,1611562,1,0 +44,148,1611951,2,0,B|112:68|224:64,1,200,0|8 +356,36,1612536,5,0 +328,132,1612730,1,0 +428,140,1612925,2,2,B|448:256|344:324,1,200 +276,256,1613510,1,0 +204,324,1613704,2,2,B|100:324|64:188,1,200 +36,104,1614289,1,0 +116,44,1614484,2,2,B|324:44,1,200 +416,52,1615068,1,0 +480,128,1615263,2,2,B|472:232|352:284,1,200 +356,180,1615847,2,2,B|296:172|272:244,2,100 +132,352,1616626,5,8 +60,232,1616821,1,8 +120,104,1617016,1,8 +240,32,1617211,1,8 +376,72,1617406,1,8 +416,208,1617600,1,0 +236,204,1617795,6,2,B|310:134|424:135,1,200,2|0 +472,328,1618574,1,8 +388,276,1618769,1,0 +476,228,1618964,1,0 +472,328,1619159,1,0 +324,340,1619354,6,0,B|220:364|124:320,1,200,6|0 +44,140,1620133,1,8 +132,188,1620328,1,0 +204,116,1620523,2,2,B|312:116|396:36,1,200 +456,120,1621107,1,0 +364,156,1621302,1,2 +468,328,1621691,1,8 +284,248,1622081,2,2,B|212:188|212:188|192:64,1,200,2|6 +28,192,1622860,5,2 +184,316,1623250,1,8 +248,124,1623639,2,2,B|352:60|458:182|384:292,1,300,2|0 +389,283,1624419,1,2 +200,216,1624808,1,8 +44,340,1625198,1,0 +44,340,1625393,5,0 +44,340,1625490,1,0 +44,340,1625587,2,0,B|88:136,1,200,6|0 +276,80,1626367,2,0,B|372:52|476:88,2,200,8|2|0 +184,124,1627341,1,0 +256,192,1627536,1,0 +256,192,1627925,12,0,1629873 +256,36,1630263,5,0 +292,56,1630458,1,8 +312,88,1630652,1,0 +316,128,1630847,1,0 +308,168,1631042,1,8 +296,208,1631237,1,0 +272,240,1631432,1,0 +244,268,1631626,1,0 +204,276,1631821,6,2,B|91:252|43:156,1,200,6|2 +304,48,1632600,2,0,B|428:72|444:204,2,200,8|2|0 +304,48,1633574,1,0 +304,48,1633769,1,2 +456,284,1634159,2,0,B|372:344|264:304,2,200,8|2|4 +240,104,1635328,6,2,B|140:108|72:204,1,200,2|8 +340,108,1636107,2,2,B|304:212|172:196,1,200 +180,196,1636691,1,0 +180,196,1636886,2,2,B|196:324|328:324,1,200,2|10 +452,84,1637665,1,0 +452,84,1637860,5,8 +452,84,1637958,1,8 +452,84,1638055,2,0,B|348:116|244:68,1,200,6|0 +64,124,1638834,1,8 +64,124,1639224,2,2,B|64:228|152:308,1,200 +244,316,1639808,1,0 +256,216,1640003,1,0 +256,192,1640393,12,8,1641951 +416,192,1642536,5,0 +416,192,1642730,2,8,B|432:116,2,66.6666666666667,0|8|8 +352,272,1643120,2,0,B|368:356,2,66.6666666666667,8|8|0 +292,192,1643510,2,0,B|272:120,2,66.6666666666667,0|8|8 +232,272,1643899,2,0,B|204:348,2,66.6666666666667,8|0|0 +132,296,1644289,1,4 +104,96,1644678,5,0 +196,136,1644873,1,0 +288,96,1645068,1,8 +456,208,1645458,1,0 +356,192,1645652,1,0 +264,228,1645847,2,2,B|152:228|60:324,2,200,10|2|10 +432,116,1647016,1,6 +264,228,1647406,5,8 +344,172,1647600,2,0,B|364:124,2,50 +248,152,1647990,1,0 +176,80,1648185,1,8 +76,96,1648380,1,0 +32,188,1648574,1,0 +84,272,1648769,1,0 +132,260,1648867,1,0 +176,280,1648964,6,2,B|248:344|368:312,1,200,10|2 +248,152,1649743,1,8 +424,56,1650133,2,2,B|320:28|224:56,1,200,2|10 +256,252,1650912,2,2,B|368:252|432:364,1,200,2|10 +220,344,1651691,5,2 +120,328,1651886,1,0 +128,228,1652081,1,8 +36,48,1652471,2,2,B|244:48,1,200,2|10 +436,60,1653250,2,2,B|420:160|468:268,1,200,2|10 +376,304,1653834,5,0 +292,192,1654029,1,0 +268,236,1654126,1,0 +220,248,1654224,1,0 +172,236,1654321,1,0 +148,192,1654419,1,8 +84,68,1654613,1,0 +32,196,1654808,5,0 +32,196,1655003,1,0 +32,196,1655198,1,10 +256,348,1655587,1,2 +476,192,1655977,1,10 +256,40,1656367,2,0,B|256:152,2,100,2|0|6 +160,192,1657146,5,0 +256,220,1657341,1,0 +340,164,1657536,1,8 +156,92,1657925,1,0 +80,156,1658120,1,0 +92,256,1658315,1,10 +276,176,1658704,6,2,B|312:288|196:344,2,200,2|10|2 +212,100,1659678,1,0 +308,68,1659873,1,8 +472,184,1660263,2,0,B|472:244|424:280,2,100,0|0|8 +376,360,1661042,1,0 +324,272,1661237,1,0 +324,272,1661334,1,0 +324,272,1661432,6,0,B|216:248|128:328,1,200,8|0 +52,268,1662016,1,0 +36,168,1662211,2,0,B|84:68|196:64,1,200,2|2 +256,144,1662795,1,0 +256,192,1662990,12,0,1666107 +256,192,1677016,12,4,1678574 +256,180,1678964,5,0 +256,180,1679159,1,0 +256,180,1679354,1,2 +160,308,1679743,1,0 +100,252,1679938,1,0 +56,184,1680133,2,0,B|140:92,1,100,2|0 +196,76,1680523,2,0,B|308:76,3,100,0|0|2|0 +376,76,1681302,2,0,B|412:124|396:176,1,100 +364,244,1681691,5,4 +208,272,1682081,2,0,B|208:392,2,100,0|0|2 +68,192,1682860,1,0 +140,156,1683055,1,0 +152,76,1683250,1,2 +256,200,1683639,1,0 +364,80,1684029,1,2 +428,128,1684224,1,0 +440,208,1684419,1,0 +392,272,1684613,1,0 +392,272,1684808,6,0,B|304:264|180:304,1,200,4|0 +256,152,1685587,1,2 +256,152,1685782,1,0 +256,152,1685977,1,0 +100,188,1686367,1,2 +256,228,1686756,1,0 +412,188,1687146,1,2 +364,124,1687341,1,0 +292,92,1687536,1,0 +220,124,1687730,1,0 +172,188,1687925,1,2 +472,312,1688315,5,0 +400,348,1688510,1,0 +300,348,1688704,1,8 +184,312,1688899,1,8 +136,180,1689094,1,8 +248,68,1689289,1,8 +400,164,1689484,1,6 +56,68,1690263,5,0 +32,132,1690393,1,0 +36,200,1690523,1,8 +68,256,1690652,1,8 +124,296,1690782,1,8 +192,300,1690912,1,0 +252,272,1691042,6,2,B|360:300|428:200,1,200,6|2 +300,48,1691821,1,8 +156,188,1692211,1,2 +284,240,1692406,1,0 +396,172,1692600,2,0,B|452:160|492:200,2,100,0|0|2 +256,316,1693380,1,8 +220,120,1693769,6,2,B|128:48|32:120,1,200,2|6 +56,316,1694549,2,2,B|260:316,1,200,2|10 +452,280,1695328,1,2 +424,184,1695523,1,0 +444,88,1695717,1,0 +348,120,1695912,1,0 +248,128,1696107,1,2 +256,328,1696497,1,8 +92,212,1696886,1,0 +92,212,1697081,5,0 +92,212,1697178,1,0 +92,212,1697276,2,2,B|191:234|304:212,2,200,6|2|10 +200,44,1698445,2,2,B|292:20|416:52,1,200 +444,136,1699029,1,0 +408,232,1699224,1,0 +256,192,1699613,12,0,1701562 +428,192,1701951,5,4 +376,192,1702049,1,0 +328,192,1702146,1,0 +280,192,1702243,1,0 +228,192,1702341,6,0,B|240:260,2,66.6666666666667,0|0|0 +132,228,1702730,2,0,B|100:300,2,66.6666666666667,0|0|0 +100,132,1703120,2,0,B|28:152,2,66.6666666666667 +192,88,1703510,6,2,B|284:44|404:92,1,200,6|2 +420,284,1704289,2,2,B|332:360|228:284,1,200,10|2 +156,352,1704873,1,0 +156,252,1705068,1,0 +60,280,1705263,1,0 +52,180,1705458,2,2,B|148:132|192:32,1,200,2|10 +360,284,1706237,6,2,B|448:204|384:92,2,200,2|6|2 +256,112,1707406,1,8 +184,300,1707795,2,2,B|96:268|56:136,1,200 +32,56,1708380,1,0 +132,56,1708574,2,2,B|352:56,1,200,2|10 +480,188,1709354,5,8 +460,288,1709549,1,8 +412,280,1709646,1,8 +380,240,1709743,1,6 +200,328,1710133,2,0,B|160:236|196:120,2,200,0|8|2 +100,152,1711302,1,0 +28,220,1711497,1,0 +16,120,1711691,1,8 +96,60,1711886,1,0 +196,56,1712081,2,2,B|308:32|400:88,1,200 +356,176,1712665,1,0 +308,264,1712860,1,4 +256,192,1712958,12,0,1714419 +256,308,1748243,6,0,B|256:364,2,54.9999983608723 +256,252,1749012,2,0,B|244:216|268:192|256:144,1,109.999996721745,0|0 +284,36,1750038,1,0 +228,36,1750294,1,0 +347,99,1750807,6,0,B|392:128|424:192|392:260|348:284,1,219.999993443489 +256,192,1752602,1,0 +164,100,1753115,2,0,B|120:124|88:192|120:256|164:284,1,219.999993443489 +332,292,1755166,54,0,B|320:352|256:392|192:352|180:292,1,219.999993443489 +176,236,1756448,1,0 +228,216,1756704,1,0 +284,216,1756961,1,0 +336,236,1757217,1,0 +436,188,1757730,1,0 +348,124,1758243,5,0 +308,84,1758499,2,0,B|256:68|256:68|204:84,1,109.999996721745,0|0 +204,140,1759268,2,0,B|200:184|256:220|312:184|308:140,1,164.999995082617,0|0 +356,332,1760807,6,0,B|324:360|288:364|256:336|256:336|224:364|188:360|156:332,1,219.999993443489,0|0 +204,272,1762345,2,0,B|256:300|308:272,1,109.999996721745,2|0 +336,188,1763371,2,0,B|304:168|256:188|256:188|208:208|176:196,1,165.99999386549,4|8 +100,164,1764140,6,0,B|76:132|74:87,1,82.9999969327451,0|8 +140,32,1764653,2,0,B|220:56,1,82.9999969327451,0|8 +300,76,1765166,1,0 +324,156,1765422,2,0,B|304:188|256:220|208:188|186:154,1,165.99999386549,2|8 +212,76,1766192,1,0 +292,55,1766448,2,0,B|372:32,1,82.9999969327451,2|0 +436,86,1766961,2,0,B|436:132|412:164,1,82.9999969327451,8|0 +336,236,1767474,2,0,B|256:196|176:236,2,165.99999386549,4|8|2 +416,252,1768756,5,0 +432,172,1769012,1,8 +348,156,1769268,2,0,B|260:155|212:76,1,165.99999386549,2|0 +299,76,1770038,2,0,B|251:155|163:155,1,165.99999386549,2|8 +80,144,1770807,1,0 +132,80,1771063,1,2 +52,68,1771320,1,0 +16,196,1771576,2,0,B|36:360,1,165.99999386549,4|8 +116,340,1772345,2,0,B|108:280|108:280|100:220|144:188,1,165.99999386549,2|0 +224,168,1773115,5,8 +288,216,1773371,1,0 +367,196,1773627,2,0,B|412:164|404:104|404:104|396:44,2,165.99999386549,2|8|8 +320,128,1774909,1,0 +256,72,1775166,1,2 +196,128,1775422,1,0 +180,208,1775679,6,0,B|200:284|272:284|272:284|308:284,1,165.99999386549,4|8 +392,288,1776448,2,0,B|372:364|300:364|300:364|264:364,1,165.99999386549,2|0 +180,360,1777217,2,0,B|156:272|64:256,1,165.99999386549,2|8 +40,176,1777986,5,0 +96,116,1778243,1,8 +176,100,1778499,1,0 +256,112,1778756,1,2 +344,192,1779012,1,0 +256,272,1779268,1,8 +168,192,1779525,1,0 +256,112,1779781,1,4 +324,44,1780038,54,0,B|408:32,1,82.9999969327451,0|8 +500,96,1780551,2,0,B|416:108,1,82.9999969327451,0|2 +364,192,1781063,2,0,B|388:272,1,82.9999969327451,0|8 +336,347,1781576,2,0,B|256:314|170:348,1,165.99999386549,4|0 +148,192,1782345,2,0,B|124:272,1,82.9999969327451,2|0 +64,216,1782858,5,8 +88,136,1783115,2,0,B|112:56,1,82.9999969327451,2|0 +196,52,1783627,2,0,B|236:76|256:108|256:108|276:76|316:52,1,165.99999386549,4|0 +364,120,1784397,5,2 +256,176,1784653,1,0 +148,120,1784909,1,2 +336,224,1785166,1,0 +176,224,1785422,1,2 +256,360,1785679,1,0 +256,276,1785935,1,8 +256,192,1786192,1,0 +344,136,1786448,6,0,B|360:52,1,82.9999969327451,8|0 +256,1,1786961,2,0,B|256:84,1,82.9999969327451,2|0 +168,136,1787474,2,0,B|152:52,1,82.9999969327451,8|0 +44,176,1787986,2,0,B|24:340,1,165.99999386549,4|8 +112,296,1788756,2,0,B|116:212,1,82.9999969327451,0|2 +204,81,1789268,2,0,B|204:164,1,82.9999969327451,0|8 +280,244,1789781,5,0 +468,208,1790038,2,0,B|488:44,1,165.99999386549,4|8 +400,88,1790807,2,0,B|396:172,1,82.9999969327451,0|2 +309,302,1791320,2,0,B|308:220,1,82.9999969327451,0|8 +256,136,1791833,1,0 +184,56,1792089,2,0,B|208:24|256:8|300:24|330:57,1,165.99999386549,4|8 +328,156,1792858,5,0 +256,224,1793115,2,0,B|176:276|208:368,1,165.99999386549,2|8 +305,366,1793884,2,0,B|338:275|254:224,1,165.99999386549,2|8 +256,124,1794653,1,8 +256,48,1795166,5,0 +256,244,1795422,1,8 +184,124,1795679,1,8 +328,124,1795935,1,8 +400,244,1796192,1,4 +340,336,1796448,6,2,B|316:280|256:248|196:280|171:338,1,219.999993443489,2|0 +64,352,1797217,2,0,B|40:244,1,109.999996721745,2|0 +92,148,1797730,1,8 +184,84,1797986,2,0,B|268:100|316:156|332:228,1,219.999993443489,4|0 +256,308,1798756,1,8 +180,228,1799012,2,0,B|197:155|245:99|329:83,1,219.999993443489,2|0 +432,120,1799781,1,8 +456,336,1800038,6,0,B|348:288|348:160,1,219.999993443489,4|0 +312,56,1800807,1,8 +200,56,1801063,1,0 +256,216,1801320,1,2 +256,104,1801576,1,0 +164,160,1801833,1,8 +108,68,1802089,1,0 +165,160,1802345,2,0,B|164:288|56:336,1,219.999993443489,4|8 +12,236,1803115,5,0 +64,216,1803243,2,0,B|144:236|200:304,1,164.999995082617,2|0 +308,276,1803884,1,8 +232,196,1804140,1,2 +96,132,1804397,2,0,B|72:75|124:3|204:35|212:103,1,219.999993443489,4|8 +300,36,1805166,1,0 +356,36,1805294,2,0,B|388:32|408:16|408:16|424:40|448:52,1,109.999996721745,2|0 +500,72,1805679,5,2 +492,180,1805935,1,8 +388,148,1806192,1,0 +416,252,1806448,2,0,B|440:309|388:381|308:349|300:281,1,219.999993443489,4|8 +208,220,1807217,2,0,B|199:152|119:120|67:192|91:249,1,219.999993443489,2|0 +176,320,1807986,1,8 +280,352,1808243,1,0 +404,276,1808499,5,4 +404,276,1808756,2,0,B|336:192,1,109.999996721745,2|8 +108,107,1809268,2,0,B|178:193,1,109.999996721745,2|0 +328,100,1809781,2,0,B|360:48|408:28,2,109.999996721745,0|8|2 +185,283,1810551,2,0,B|152:336|104:356,2,109.999996721745,0|2|8 +296,228,1811320,6,0,B|260:212|252:172|216:156,2,109.999996721745,0|2|8 +368,144,1812089,5,8 +308,52,1812345,1,8 +256,36,1812474,1,0 +204,52,1812602,1,4 +144,144,1812858,1,2 +92,240,1813115,1,8 +148,336,1813371,1,0 +256,344,1813627,2,0,B|308:324|324:264,1,109.999996721745,2|0 +268,168,1814140,5,8 +204,256,1814397,2,0,B|292:332|408:316,1,219.999993443489,4|0 +484,236,1815166,1,8 +492,124,1815422,2,0,B|464:52|368:32|312:100,1,219.999993443489,2|0 +216,156,1816192,1,8 +56,48,1816448,6,0,B|8:168|92:252,1,219.999993443489,4|0 +200,256,1817217,2,0,B|312:256,1,109.999996721745,8|0 +420,240,1817730,2,0,B|360:332,1,109.999996721745,2|0 +312,360,1818115,1,2 +200,360,1818371,1,0 +152,332,1818499,2,0,B|92:240,1,109.999996721745,2|4 +256,252,1819012,5,0 +208,100,1819268,1,8 +332,196,1819525,1,0 +180,196,1819781,1,2 +304,100,1820038,5,0 +256,168,1820294,1,8 +400,108,1820551,1,0 +420,160,1820679,1,0 +440,212,1820807,1,4 +356,284,1821063,2,0,B|304:288|256:244|256:244|208:288|156:284,1,219.999993443489,2|0 +52,320,1821833,1,2 +72,212,1822089,5,0 +92,160,1822217,1,0 +112,108,1822345,1,8 +156,8,1822602,2,0,B|207:4|255:48|255:48|303:4|354:7,1,219.999993443489,4|0 +352,120,1823371,1,8 +256,172,1823627,1,2 +160,120,1823884,1,0 +192,224,1824140,5,8 +180,276,1824268,1,0 +204,324,1824397,1,8 +256,344,1824525,1,0 +308,324,1824653,1,2 +332,276,1824781,1,0 +320,224,1824909,1,4 +152,140,1830294,6,0,B|232:111,1,83.5000010451675,0|8 +360,244,1830807,2,0,B|280:272,1,83.5000010451675,0|2 +124,240,1831320,2,0,B|204:212,1,83.5000010451675,0|8 +388,144,1831833,2,0,B|308:172,1,83.5000010451675,0|2 +208,60,1832345,2,0,B|236:140,1,83.5000010451675,0|8 +304,324,1832858,2,0,B|276:244,1,83.5000010451675,0|4 +440,232,1833627,5,8 +428,152,1833884,1,0 +360,200,1834140,1,2 +504,180,1834397,5,0 +492,100,1834653,1,8 +416,72,1834909,1,0 +332,68,1835166,2,0,B|292:68|292:68|256:44|256:44|220:68|220:68|180:68,1,167.000002090335,2|8 +96,72,1835935,1,0 +20,100,1836192,1,2 +8,180,1836448,1,0 +84,148,1836704,1,8 +176,284,1837217,6,0,B|343:284,2,167.000002090335,0|8|0 +100,164,1838499,6,0,B|180:192,1,83.5000010451675,2|8 +412,164,1839012,2,0,B|332:192,1,83.5000010451675,0|2 +76,72,1839525,2,0,B|156:100,1,83.5000010451675,0|8 +436,72,1840038,2,0,B|356:100,1,83.5000010451675,0|2 +256,52,1840551,1,0 +256,136,1840807,1,8 +256,176,1840935,1,0 +256,216,1841063,1,8 +256,300,1841320,1,4 +92,312,1841833,6,0,B|48:304|28:264,1,83.5000010451675,8|0 +104,232,1842345,1,2 +168,180,1842602,1,0 +192,100,1842858,1,8 +196,16,1843115,1,0 +116,36,1843371,2,0,B|120:124|42:176,2,167.000002090335,4|8|2 +176,152,1844653,6,4,B|256:136|256:136|338:152,1,167.000002090335,4|0 +395,36,1845422,2,0,B|392:124|468:178,1,167.000002090335,4|8 +492,96,1846192,2,0,B|368:205,1,167.000002090335,2|2 +292,172,1846961,1,8 +152,104,1847217,6,0,B|116:188|17:185,1,167.000002090335,2|0 +8,268,1847986,2,0,B|85:296,1,83.5000010451675,8|0 +221,264,1848499,2,0,B|143:235,1,83.5000010451675,2|0 +220,344,1849012,2,0,B|299:374,1,83.5000010451675,8|0 +432,356,1849525,5,4 +508,328,1849781,2,0,B|488:288|445:276,1,83.5000010451675,0|8 +364,268,1850294,2,0,B|358:176|289:122,1,167.000002090335 +352,68,1851063,2,0,B|296:8,1,83.5000010451675,8|0 +216,7,1851576,2,0,B|160:68,1,83.5000010451675,2|0 +80,88,1852089,2,0,B|44:168,1,83.5000010451675,8|0 +16,240,1852602,1,2 +32,312,1852858,2,0,B|104:332|124:260|189:280,1,167.000002090335 +323,280,1853627,6,0,B|388:260|408:332|481:311,1,167.000002090335,4|8 +456,240,1854397,2,0,B|388:260|408:332|340:352,1,167.000002090335,0|2 +256,280,1855166,1,8 +148,324,1855422,1,0 +104,220,1855679,1,2 +212,176,1855935,1,0 +256,280,1856192,5,8 +104,220,1856448,1,0 +164,64,1856704,1,2 +316,124,1856961,1,0 +316,124,1857089,1,8 +316,124,1857217,1,8 +420,252,1857474,1,8 +420,252,1857602,1,8 +420,252,1857730,5,4 +476,192,1857986,1,0 +500,272,1858243,2,0,B|496:332|424:344|424:344|372:352,1,167.000002090335,8|2 +300,312,1859012,1,0 +256,240,1859268,1,8 +212,312,1859525,1,0 +137,351,1859781,2,0,B|87:344|87:344|15:332|10:271,1,167.000002090335,4|8 +96,264,1860551,2,0,B|176:244,1,83.5000010451675,0|2 +124,180,1861063,2,0,B|200:148,2,83.5000010451675,0|8|0 +120,96,1861833,6,0,B|176:28|271:47,1,167.000002090335,4|8 +292,160,1862602,1,0 +256,264,1862858,1,2 +220,160,1863115,1,0 +312,224,1863371,1,8 +200,224,1863627,1,0 +243,48,1863884,6,0,B|336:28|393:97,1,167.000002090335,4|8 +460,168,1864653,1,0 +408,252,1864909,1,2 +424,348,1865166,1,0 +324,336,1865422,1,8 +244,280,1865679,1,8 +224,316,1865807,1,8 +188,336,1865935,5,4 +168,256,1866192,2,0,B|212:188|306:172,1,167.000002090335,2|0 +336,96,1866961,2,0,B|376:112|384:160,1,83.5000010451675,2|0 +424,232,1867474,1,8 +344,256,1867730,2,0,B|300:188|206:170,1,167.000002090335,4|0 +128,158,1868499,2,0,B|136:112|176:96,1,83.5000010451675,8|0 +120,12,1869012,2,0,B|112:58|72:74,1,83.5000010451675,2|0 +0,116,1869525,1,8 +40,188,1869781,1,0 +112,148,1870038,1,4 +256,64,1870551,6,0,B|288:132|224:236|256:310,2,250.500003135502,8|2|8 +256,64,1872217,1,0 +380,64,1872474,2,0,B|420:80,1,41.7500005225837 +440,192,1872858,2,0,B|376:164|376:164,1,41.7500005225837 +240,168,1873371,5,8 +204,216,1873499,1,8 +144,224,1873627,1,8 +92,188,1873756,1,0 +88,128,1873884,1,8 +124,76,1874012,1,8 +184,68,1874140,5,4 +292,76,1874397,2,0,B|364:88|360:168|360:168|356:260,1,219.999993443489,2|0 +324,364,1875166,1,2 +256,276,1875422,1,0 +188,364,1875679,1,8 +156,260,1875935,2,0,B|152:168|152:168|148:88|220:76,1,219.999993443489,2|0 +324,104,1876704,1,8 +256,192,1876961,2,0,B|184:104|72:80,1,219.999993443489,2|0 +168,24,1877730,1,8 +256,192,1877986,6,0,B|328:280|440:304,1,219.999993443489,4|0 +352,372,1878756,1,8 +252,328,1879012,2,0,B|216:280|160:276,1,109.999996721745,0|2 +28,300,1879525,2,0,B|64:348|120:352,1,109.999996721745,0|8 +76,224,1880038,1,0 +36,120,1880294,6,0,B|32:76|104:40|120:84|136:128|208:92|196:48,1,219.999993443489,4|8 +204,156,1881063,1,0 +216,208,1881192,2,0,B|228:244|256:244|284:244|296:208,1,109.999996721745,2|0 +308,156,1881576,1,2 +360,24,1881833,1,8 +452,132,1882089,1,2 +476,272,1882345,6,0,B|480:316|408:352|392:308|376:264|304:300|316:344,1,219.999993443489,4|8 +256,252,1883115,1,0 +200,344,1883371,1,2 +108,284,1883627,2,0,B|84:176,1,109.999996721745,0|8 +164,100,1884140,2,0,B|176:208,1,109.999996721745,0|2 +256,132,1884653,2,0,B|256:20,1,109.999996721745,0|8 +348,100,1885166,2,0,B|336:208,1,109.999996721745,0|2 +404,284,1885679,2,0,B|428:176,1,109.999996721745,0|8 +452,68,1886192,1,0 +256,40,1886448,5,4 +152,80,1886704,1,0 +256,196,1886961,1,8 +360,304,1887217,1,2 +256,344,1887474,1,0 +256,196,1887730,1,0 +104,192,1887986,5,8 +144,296,1888243,1,0 +256,196,1888499,1,0 +368,88,1888756,1,2 +408,192,1889012,1,8 +256,196,1889268,1,0 +104,192,1889525,1,2 +144,88,1889781,5,0 +256,40,1890038,1,8 +368,88,1890294,1,8 +396,136,1890422,1,0 +420,184,1890551,1,4 +416,296,1890807,2,0,B|368:312|316:288|316:288|256:256|196:288|196:288|144:312|96:296,1,329.999990165234,2|0 +148,200,1891833,1,0 +256,176,1892089,1,8 +403,79,1892345,6,0,B|456:100|456:100|432:152|456:204|456:204|403:224,1,219.999993443489,4|0 +256,256,1893115,1,8 +109,224,1893371,2,0,B|56:204|56:204|80:152|56:100|56:100|108:80,1,219.999993443489,2|0 +256,48,1894140,1,8 +344,196,1894397,6,0,B|300:264|204:264|160:196,1,219.999993443489,4|0 +152,84,1895166,1,8 +256,48,1895422,1,0 +360,84,1895679,1,2 +440,160,1895935,2,0,B|456:212,1,54.9999983608723,0|2 +412,312,1896320,2,0,B|396:260,1,54.9999983608723,0|2 +256,176,1896704,5,4 +208,328,1896961,1,0 +332,232,1897217,1,8 +180,232,1897474,1,0 +304,328,1897730,1,2 +256,256,1897986,1,0 +116,308,1898243,5,8 +80,264,1898371,1,0 +72,208,1898499,1,2 +84,156,1898627,1,0 +116,112,1898756,1,4 +312,12,1899268,5,8 +412,52,1899525,1,0 +324,120,1899781,1,2 +428,156,1900038,1,0 +428,336,1900294,5,8 +360,248,1900551,2,0,B|292:204|220:316|152:268,1,219.999993443489,4|0 +80,184,1901320,1,8 +188,160,1901576,1,0 +192,104,1901704,1,8 +196,48,1901833,1,8 +316,48,1902089,5,8 +320,104,1902217,1,8 +324,160,1902345,1,0 +340,212,1902474,1,8 +376,256,1902602,1,8 +424,280,1902730,1,0 +480,288,1902858,1,4 +256,132,1917730,5,0 +316,192,1917986,1,0 +256,252,1918243,1,8 +196,192,1918499,1,0 +256,92,1918756,5,2 +360,272,1919012,1,0 +152,272,1919268,1,4 +80,232,1919525,6,0,B|93:151,1,82.9999969327451,0|2 +56,47,1920038,2,0,B|16:120,2,82.9999969327451,0|8|0 +136,32,1920807,1,2 +208,68,1921063,1,0 +352,100,1921320,6,0,B|388:112|432:92|432:92|420:140|436:176,1,165.99999386549,4|2 +424,256,1922089,2,0,B|452:288|496:292,2,82.9999969327451,0|8|0 +360,204,1922858,2,0,B|312:272,1,82.9999969327451,2|0 +208,360,1923371,6,0,B|147:316|111:364|49:325,1,165.99999386549,4|2 +60,240,1924140,2,0,B|108:240|132:208,1,82.9999969327451,0|8 +148,88,1924653,2,0,B|152:132|193:153,1,82.9999969327451,0|2 +212,232,1925166,2,0,B|180:308,1,82.9999969327451 +256,336,1925679,1,0 +332,308,1925935,6,0,B|299:231,1,82.9999969327451,0|0 +312,148,1926448,1,8 +404,60,1926704,1,0 +376,180,1926961,1,2 +328,68,1927217,1,0 +436,132,1927474,1,4 +136,180,1927986,5,2 +108,60,1928243,1,0 +200,140,1928499,1,8 +76,132,1928756,1,0 +184,68,1929012,1,2 +136,180,1929268,1,0 +176,296,1929525,6,0,B|340:296,1,165.99999386549,4|2 +400,236,1930294,2,0,B|330:191,1,82.9999969327451,0|8 +256,152,1930807,1,0 +181,191,1931063,2,0,B|112:236,1,82.9999969327451,2|0 +80,128,1931576,6,0,B|112:48|112:48|188:24,1,165.99999386549,4|2 +256,100,1932345,1,0 +324,24,1932602,2,0,B|400:48|400:48|432:128,1,165.99999386549,8|2 +408,204,1933371,1,0 +388,240,1933499,1,0 +356,268,1933627,1,0 +308,200,1933884,1,0 +256,264,1934140,1,0 +204,200,1934397,1,0 +256,136,1934653,1,8 +104,204,1934909,5,0 +124,240,1935038,1,0 +156,268,1935166,1,0 +256,264,1935422,1,0 +256,280,1935679,1,0 +136,128,1950038,6,0,B|134:99|134:99,5,27.6666656442484 +208,128,1950551,2,0,B|208:95|208:95,5,27.6666656442484 +280,128,1951063,2,0,B|280:97|280:97,5,27.6666656442484 +352,128,1951576,2,0,B|352:99|352:99,5,27.6666656442484 +404,184,1952089,6,0,B|384:244|304:256|304:256|216:268,1,219.999993443489,4|8 +104,280,1952858,1,0 +128,328,1952986,2,0,B|188:328|228:364,1,109.999996721745,2|0 +287,368,1953371,2,0,B|328:332|388:332,1,109.999996721745,2|8 +356,228,1953884,6,0,B|384:184|356:124|356:124|384:68|356:16,1,219.999993443489,4|0 +256,60,1954653,1,8 +155,16,1954909,2,0,B|128:68|156:124|156:124|128:184|156:228,1,219.999993443489,2|0 +256,184,1955679,1,8 +448,128,1955935,6,0,B|468:208|416:288|348:296,1,219.999993443489,4|0 +176,200,1956704,1,8 +256,52,1956961,1,0 +336,200,1957217,1,2 +176,104,1957474,1,0 +336,104,1957730,1,8 +256,248,1957986,1,0 +64,127,1958243,6,0,B|44:208|96:288|164:296,1,219.999993443489,4|8 +208,196,1959012,1,2 +256,168,1959140,1,0 +304,196,1959268,1,2 +388,80,1959525,1,0 +256,20,1959781,1,8 +124,80,1960038,1,2 +76,204,1960294,5,4 +84,336,1960551,2,0,B|112:288,1,54.9999983608723,2|0 +160,244,1960807,1,10 +256,152,1961063,2,0,B|256:208,1,54.9999983608723,2|0 +256,272,1961320,1,2 +428,336,1961576,2,0,B|400:288,1,54.9999983608723,2|0 +352,244,1961833,1,10 +256,332,1962089,1,0 +160,244,1962345,5,2 +216,244,1962474,1,0 +272,232,1962602,1,0 +320,204,1962730,1,0 +344,156,1962858,1,8 +348,100,1962986,1,0 +328,48,1963115,1,0 +284,16,1963243,1,0 +228,16,1963371,5,2 +184,48,1963499,1,0 +164,100,1963627,1,0 +168,156,1963756,1,0 +192,204,1963884,1,8 +240,232,1964012,1,0 +296,244,1964140,1,0 +352,244,1964268,1,0 +408,236,1964397,5,4 +408,124,1964653,1,0 +256,148,1964909,1,8 +316,44,1965166,1,2 +196,44,1965422,1,0 +108,208,1965679,5,2 +256,312,1965935,1,8 +404,208,1966192,1,4 +256,192,1966448,12,4,1968499 +76,64,1969012,5,8 +180,104,1969268,1,0 +140,208,1969525,2,0,B|112:260|56:272,2,109.999996721745,2|0|8 +40,160,1970294,6,0,B|72:276|180:320,1,219.999993443489,4|0 +256,192,1971063,1,8 +332,64,1971320,2,0,B|440:108|472:224,1,219.999993443489,2|0 +256,288,1972089,1,8 +40,223,1972345,6,0,B|72:108|180:64,1,219.999993443489,4|0 +200,172,1973115,2,0,B|312:172,2,109.999996721745,8|0|2 +148,268,1973884,1,0 +204,272,1974012,2,0,B|256:288|256:288|308:272,1,109.999996721745,2|0 +364,268,1974397,1,2 +190,203,1974653,6,0,B|116:168|116:64|192:28,1,219.999993443489,4|8 +256,116,1975422,1,0 +320,28,1975679,2,0,B|396:64|396:168|320:204,1,219.999993443489,4|8 +256,292,1976448,1,0 +168,360,1976704,6,0,B|204:284|308:284|344:360,2,219.999993443489,4|8|2 +144,252,1977986,1,0 +40,216,1978243,1,8 +56,324,1978499,1,4 +256,44,1979012,1,0 +456,324,1979525,1,2 +192,244,1980038,5,8 +180,192,1980166,1,0 +204,144,1980294,1,8 +256,124,1980422,1,0 +308,144,1980551,1,2 +332,192,1980679,1,0 +320,244,1980807,1,4 +256,64,1994397,5,2 +336,320,1994825,1,0 +128,160,1995254,1,0 +384,160,1995682,1,0 +176,320,1996111,1,0 +256,192,1996539,1,0 +336,320,1996968,1,2 +336,144,1997397,6,0,B|320:101|256:69|192:101|176:149,1,200 +384,32,1998254,1,2 +256,192,1998682,1,0 +128,32,1999111,1,0 +176,240,1999539,2,0,B|192:283|256:315|320:283|336:235,1,200,2|0 +128,352,2000397,5,0 +256,192,2000825,1,0 +384,352,2001254,1,0 +472,200,2001682,2,0,B|456:96|456:96|384:32,1,200,0|2 +213,100,2002539,2,0,B|192:128|208:176|255:196|304:176|320:128|296:95,1,200 +126,33,2003397,2,0,B|56:96|56:96|40:200,1,200,2|0 +176,352,2004254,6,0,B|200:312|256:296|256:296|312:312|336:352,1,200,0|2 +301,182,2005111,1,0 +213,182,2005325,1,0 +304,24,2005754,2,0,B|349:50|349:50|368:96,1,100,0|2 +208,24,2006397,2,0,B|163:50|163:50|144:96,1,100 +339,233,2007254,6,0,B|380:288|348:384|255:424|157:384|125:288|173:223,1,400,2|0 +86,73,2008539,2,0,B|108:33|161:17|214:22|256:77|256:77|299:21|352:13|416:31|429:79|429:79,1,400,2|0 +336,248,2009825,2,0,B|320:202|256:170|192:202|176:245,1,200,2|0 +176,336,2010468,2,0,B|191:381|255:413|319:381|335:338,1,200 +256,296,2011111,5,2 +336,136,2011539,1,0 +176,136,2011968,1,0 +256,176,2012182,1,0 +336,136,2012397,2,0,B|368:80|336:32|304:16|304:16|256:52|256:52|208:16|208:16|176:32|136:80|180:144,1,400,2|0 +94,276,2013682,2,0,B|134:359|222:383|262:386|300:380|377:360|420:275,1,400,2|0 +256,192,2014968,5,2 +104,112,2015397,1,0 +184,64,2015611,2,0,B|199:18|263:-13|327:18|343:61,1,200 +408,112,2016254,1,2 +256,192,2016682,5,0 +168,360,2017111,1,0 +256,320,2017325,1,0 +340,360,2017539,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,1,600,2|2 +32,240,2019254,2,0,B|32:140,1,100 +104,84,2019682,1,0 +176,248,2020111,2,0,B|200:288|256:304|256:304|312:288|336:248,1,200,2|0 +336,156,2020754,2,0,B|312:116|256:100|256:100|200:116|176:156,1,200 +256,200,2021397,5,2 +356,348,2021825,1,0 +480,240,2022254,2,0,B|480:140,1,100,2|0 +408,84,2022682,1,0 +300,228,2023111,6,0,B|321:256|305:304|258:324|209:304|193:256|217:223,1,200,0|2 +256,148,2023754,1,0 +256,60,2023968,1,0 +100,84,2024397,1,2 +100,84,2024611,1,0 +100,84,2024825,1,0 +175,247,2025254,2,0,B|190:293|254:324|318:293|334:250,2,200,2|0|0 +342,361,2026539,5,2 +256,200,2026968,1,0 +256,200,2027182,1,0 +169,361,2027611,1,0 +169,361,2027825,1,2 +64,203,2028254,6,0,B|92:107|92:107|168:43,1,200,0|2 +343,42,2029111,2,0,B|420:107|420:107|448:203,1,200 +336,339,2029968,1,2 +256,312,2030182,1,0 +176,340,2030397,2,0,B|160:282|194:232|256:224|256:224|296:208|328:160|296:112|256:104|216:112|176:160|216:208|256:224|256:224|320:232|355:286|334:348,1,600,0|2 +440,192,2032111,5,0 +344,40,2032539,1,0 +256,16,2032754,1,0 +168,40,2032968,1,2 +72,192,2033397,1,0 +211,312,2033825,2,0,B|190:340|206:388|253:408|302:388|318:340|294:307,1,200,0|2 +208,156,2034682,5,0 +256,24,2034897,1,0 +304,156,2035111,1,2 +184,72,2035325,1,0 +324,76,2035539,1,0 +408,236,2035968,5,2 +256,340,2036397,1,0 +104,236,2036825,1,2 +172,68,2037254,1,0 +172,68,2037468,1,0 +340,68,2037897,1,0 +340,68,2038111,1,0 +256,200,2038539,5,2 +336,360,2038968,2,0,B|312:320|256:304|256:304|200:320|176:360,1,200,0|2 +104,196,2039825,1,0 +256,100,2040254,1,2 +408,200,2040682,1,0 +256,192,2040897,12,0,2043254 +256,24,2044325,5,2 +301,102,2044539,2,0,B|322:130|306:178|259:198|210:178|194:130|218:97,1,200 +340,232,2045397,1,2 +256,260,2045611,1,0 +172,232,2045825,1,4 +76,360,2046254,1,0 +436,360,2047111,1,2 +334,144,2047539,6,0,B|320:183|265:230|188:199|179:142|179:142,1,200,0|2 +212,52,2048182,2,0,B|224:38|241:23|296:26|305:57|305:57,1,100 +64,336,2049682,5,2 +173,204,2050111,2,0,B|187:236|247:285|320:257|341:200|341:200,1,200,0|2 +374,109,2050754,2,0,B|347:44|270:-14|156:19|137:117|137:117,1,300 +448,336,2052254,1,2 +256,192,2052682,5,0 +192,250,2052897,2,0,B|157:294|184:334|233:346|256:312|256:312|282:348|328:336|364:292|309:241,2,300,0|2|0 +56,324,2054611,1,0 +56,324,2054825,5,2 +176,96,2055254,2,0,B|200:56|256:40|256:40|312:56|336:96,1,200,0|2 +256,144,2055897,1,0 +336,188,2056111,2,0,B|312:228|256:244|256:244|200:228|176:188,1,200 +344,356,2057397,2,0,B|312:320|276:336|256:356|256:356|236:332|192:320|160:368,1,200,2|0 +256,264,2058039,1,0 +359,34,2058682,6,0,B|413:71|379:165|309:209|236:120|188:73|200:24|245:-2|264:-12|283:-2|302:16|321:73|283:120|231:211|109:185|106:73|123:44|171:20,1,600,2|2 +256,208,2060397,1,0 +212,372,2060825,1,2 +300,372,2061039,1,0 +136,168,2061682,6,0,B|163:233|240:291|354:258|373:160|373:160,1,300,0|0 +337,73,2062539,2,0,B|323:41|263:-8|190:20|169:77|169:77,1,200,2|0 +254,129,2063182,1,0 +176,352,2063825,1,2 +256,316,2064039,1,0 +336,352,2064254,1,0 +256,192,2064468,12,0,2065968 +46,270,2066397,6,0,B|106:194|106:194|46:110,1,200,2|0 +299,34,2067254,2,0,B|320:62|304:110|257:130|208:110|192:62|216:29,1,200,2|0 +454,270,2068111,2,0,B|394:194|394:194|454:110,1,200,2|0 +298,350,2068968,2,0,B|319:322|303:274|256:254|207:274|191:322|215:355,1,200,4|0 +480,60,2070254,6,0,B|480:107|412:116|391:71|391:71|372:26|320:26|301:71|301:71|284:116|230:116|211:71|211:71|194:26|140:26|123:71|123:71|99:116|33:107|33:58,2,600 +256,200,2073254,5,0 +168,360,2073682,1,0 +340,360,2074111,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,2,600,8|8|8 +472,200,2077325,6,0,B|472:100,1,100,8|8 +392,56,2077754,1,0 +336,128,2077968,2,0,B|312:168|256:184|256:184|200:168|176:128,1,200,8|0 +40,200,2078825,2,0,B|40:100,1,100,8|0 +116,52,2079254,1,4 +177,271,2079682,6,0,B|201:231|257:215|257:215|313:231|337:271,1,200 +299,355,2080325,2,0,B|283:372|256:377|256:377|227:372|213:354,1,100,0|8 +144,90,2080968,6,0,B|153:55|199:44|221:77|199:100|209:146|242:133|255:48|255:48|265:133|298:146|310:100|288:77|310:44|364:50|366:88,1,400,0|8 +340,268,2082254,1,0 +256,304,2082468,1,8 +172,268,2082682,1,0 +328,360,2083111,5,8 +256,196,2083539,1,0 +184,360,2083968,1,0 +112,194,2084397,2,0,B|172:135|127:69|127:69|216:94|257:0|257:0|292:94|382:69|382:69|339:136|399:194,2,600,8|8|8 +206,348,2087397,6,0,B|310:348,1,100,8|8 +355,271,2087825,2,0,B|154:271,1,200,8|8 +208,40,2088682,6,0,B|312:40,1,100,8|8 +357,117,2089111,2,0,B|156:117,1,200,8|4 +52,260,2089968,1,0 +212,344,2090397,2,0,B|224:358|241:373|296:370|305:339|305:339,1,100,0|2 +256,268,2090825,1,8 +460,260,2091254,1,0 +416,84,2091682,5,2 +352,148,2091897,1,0 +328,60,2092111,1,8 +160,148,2092539,1,0 +96,84,2092754,1,8 +184,60,2092968,1,0 +129,290,2093397,6,0,B|157:341|243:400|353:360|384:290|384:290,1,300,8|0 +300,228,2094254,2,0,B|288:242|271:257|216:254|207:223|207:223,1,100,2|0 +256,144,2094682,1,8 +148,44,2095111,6,0,B|72:112|72:112|56:208,1,200 +80,288,2095754,1,0 +168,324,2095968,2,0,B|192:360|232:360|256:344|256:344|276:360|320:360|344:324,1,200,8|0 +456,208,2096825,2,0,B|440:112|440:112|364:44,1,200,2|8 +256,192,2097682,1,0 +301,268,2097897,2,0,B|256:288|256:288|210:268,1,100,8|0 +48,344,2098539,5,8 +192,48,2099182,2,0,B|160:89|184:161|254:190|327:161|351:89|315:40,1,300,2|8 +464,344,2100468,1,0 +256,272,2100897,5,2 +256,360,2101111,1,8 +172,208,2101539,2,0,B|186:176|246:127|319:155|340:212|340:212,1,200,0|0 +123,97,2102397,2,0,B|158:52|257:5|353:49|389:100,1,300,8|8 +388,99,2103254,1,0 +332,296,2103682,1,8 +256,340,2103897,1,0 +180,296,2104111,1,0 +176,208,2104325,1,0 +256,160,2104539,1,0 +336,208,2104754,1,0 +256,248,2104968,5,8 +256,36,2105611,1,0 +256,248,2106039,5,2 +176,304,2106254,2,0,B|200:344|256:360|256:360|312:344|336:304,1,200,8|0 +411,139,2107111,2,0,B|370:42,1,100,2|0 +299,99,2107539,2,0,B|320:127|304:175|257:195|208:175|192:127|216:94,1,200,8|0 +139,46,2108182,2,0,B|101:139,1,100,8|0 +128,288,2108825,2,0,B|256:368|256:368|384:288,1,300,8|8 +300,232,2109682,2,0,B|256:256|256:256|212:232,1,100,8|0 +256,152,2110111,1,4 +108,52,2110539,5,2 +204,204,2110968,2,0,B|210:255|210:255|254:287|302:255|302:255|306:203,1,200,0|8 +412,60,2111825,1,2 +412,60,2112039,1,0 +440,224,2112468,1,0 +440,224,2112682,2,0,B|416:324|416:324|328:368|328:368,1,200,8|2 +184,368,2113539,2,0,B|97:324|97:324|73:224,1,200,0|8 +176,76,2114397,2,0,B|200:116|256:132|256:132|312:116|336:76,1,200,2|8 +256,32,2115039,1,0 +256,32,2115254,5,4 +432,192,2115682,2,0,B|404:171|356:187|336:234|356:283|404:299|437:275,1,200,2|0 +256,360,2116539,1,8 +80,276,2116968,2,0,B|108:297|156:281|176:234|156:185|108:169|75:193,1,200,2|0 +176,40,2117825,5,8 +336,40,2118254,1,0 +256,96,2118468,1,8 +176,40,2118682,1,0 +256,200,2119111,1,8 +208,348,2119539,5,8 +176,260,2119754,1,8 +256,200,2119968,1,8 +336,260,2120182,1,0 +304,348,2120397,1,12 +348,72,2120825,6,0,B|288:72|288:72|256:48|256:48|224:72|224:72|164:72,1,200,2|0 +160,352,2121682,6,0,B|128:338|79:278|107:205|164:184|164:184,1,200,8|2 +256,172,2122325,2,0,B|256:372,1,200,0|2 +352,352,2122968,2,0,B|384:338|433:278|405:205|348:184|348:184,1,200,8|0 +256,32,2123825,5,2 +96,152,2124254,1,8 +164,336,2124682,1,2 +348,336,2125111,1,0 +416,152,2125539,1,0 +256,192,2125647,12,0,2127682 +348,80,2127897,6,0,B|288:80|288:80|256:56|256:56|224:80|224:80|164:80,1,200,2|0 +172,288,2128754,2,0,B|186:256|246:207|319:235|340:292|340:292,1,200,0|0 +256,344,2129397,1,8 +192,152,2129825,2,0,B|156:56,1,100,8|0 +256,68,2130254,1,8 +320,152,2130468,2,0,B|356:56,1,100,0|4 +426,311,2131111,6,0,B|404:351|351:367|298:362|256:307|256:307|213:363|160:371|96:353|83:305|83:305,1,400,0|8 +256,192,2132397,5,0 +340,32,2132825,1,2 +256,64,2133039,1,0 +172,32,2133254,1,8 +256,284,2133897,1,8 +182,323,2134111,2,0,B|256:387|256:387|336:323,1,200,0|8 +320,152,2134968,2,0,B|292:208|292:208|256:188|220:208|220:208|196:152,1,200 +144,76,2135611,2,0,B|196:64|232:20|232:20|256:56|256:56|276:20|276:20|320:64|368:76,1,300 +404,252,2136682,5,0 +256,356,2137111,1,8 +108,252,2137539,1,0 +164,172,2137754,2,0,B|224:140|256:172|304:220|288:268|224:268|208:220|256:172|288:140|348:172,2,300,0|2|0 +256,28,2139468,5,0 +256,28,2139682,1,8 +396,140,2140111,1,8 +344,216,2140325,1,8 +256,240,2140539,1,0 +168,216,2140754,1,8 +116,140,2140968,1,12 +113,306,2141397,2,0,B|128:352|192:368|224:336|224:288|184:272|152:320|204:368|256:368|256:368|308:368|360:320|328:272|288:288|288:336|324:368|400:344|403:296,1,456.000004348755,0|8 +256,144,2142682,1,0 +256,56,2142897,1,0 +344,360,2143539,6,0,B|294:310|319:215|378:210|418:265|378:315|304:290|254:225|254:225|204:290|129:315|89:265|129:210|189:215|214:310|164:360,1,600,0|8 +172,68,2145254,2,0,B|186:36|246:-13|319:15|340:72|340:72,1,200,0|2 +373,163,2145897,2,0,B|346:228|269:286|155:253|136:155|136:155,2,300,0|0|2 +440,224,2147397,5,8 +213,335,2147825,2,0,B|236:352|256:360|256:360|277:351|304:331,1,100,0|2 +76,228,2148682,1,8 +256,192,2148789,12,2,2151254 +60,85,2151682,6,0,B|32:134|31:182,1,100,2|8 +120,156,2152111,2,0,B|98:269|152:358,1,200,0|8 +451,298,2152968,6,0,B|479:249|480:201,1,100,8|8 +391,227,2153397,2,0,B|413:114|359:25,1,200,8|4 +256,192,2154254,5,0 +33,334,2155111,6,0,B|33:287|101:278|122:323|122:323|141:368|193:368|212:323|212:323|229:278|283:278|302:323|302:323|319:368|373:368|390:323|390:323|414:278|480:287|480:336,1,600,8|8 +256,152,2157039,1,8 +176,108,2157254,2,0,B|192:65|256:33|320:65|336:113,2,200,8|8|8 +256,264,2158539,1,8 +80,336,2158968,6,0,B|132:368|160:336|176:320|176:320|192:336|208:352|232:344|232:344|256:368|280:344|280:344|304:352|320:336|336:320|336:320|352:336|380:368|432:336,1,400,8|0 +340,172,2160254,2,0,B|381:116|349:21|256:-18|159:21|127:116|175:181,1,400,8|0 +157,353,2161539,2,0,B|358:353,1,200,8|0 +306,276,2162182,2,0,B|202:276,1,100,8|0 +56,176,2162825,5,8 +157,31,2163254,2,0,B|358:31,1,200,0|8 +306,108,2163897,2,0,B|202:108,1,100,0|4 +256,280,2164539,1,0 +456,176,2164968,5,0 +380,228,2165182,1,0 +464,268,2165397,1,8 +300,364,2165825,2,0,B|321:336|305:288|258:268|209:288|193:336|217:369,1,200 +256,192,2166682,1,8 +56,176,2167111,1,0 +132,228,2167325,1,8 +48,264,2167539,1,0 +256,192,2167968,5,8 +300,20,2168397,2,0,B|321:48|305:96|258:116|209:96|193:48|217:15,1,200 +97,157,2169254,2,0,B|128:223|176:255|256:301|335:255|383:223|415:160,1,400,8|0 +437,320,2170539,2,0,B|368:384|256:416|139:386|72:316,1,400,8|0 +165,176,2171825,2,0,B|195:207|256:237|316:207|346:176,1,200,8|0 +352,88,2172468,2,0,B|296:0,1,100,8|0 +213,5,2172897,2,0,B|159:89,1,100,8|8 +48,232,2173539,5,8 +24,320,2173754,1,0 +104,360,2173968,1,8 +176,304,2174182,1,0 +136,224,2174397,1,4 +76,56,2174825,2,0,B|128:20|112:100|207:51|191:51|191:51|223:83|255:50|255:50|287:83|330:51|314:51|314:51|384:100|384:20|442:59,1,400,0|8 +464,232,2176111,5,8 +488,320,2176325,1,0 +408,360,2176539,1,8 +336,304,2176754,1,0 +376,224,2176968,1,8 +312,56,2177397,2,0,B|416:104|416:104,1,100 +200,56,2178039,2,0,B|96:104|96:104,1,100,0|8 +176,264,2178682,2,0,B|232:272|256:216|256:216|280:272|336:264,1,200 +344,360,2179325,1,0 +344,360,2179539,2,0,B|312:396|276:380|256:360|256:360|236:384|192:396|160:348,2,200,4|0|0 +367,36,2180825,6,0,B|398:21|475:23|512:92|491:149|491:149,1,200,8|2 +456,240,2181468,2,0,B|392:271|296:264|232:164|282:78|282:78,1,300,0|2 +256,336,2182754,1,0 +256,336,2182968,1,0 +256,152,2183397,1,8 +145,36,2183825,2,0,B|114:21|37:23|0:92|21:149|21:149,1,200,8|2 +56,240,2184468,2,0,B|120:271|216:264|280:164|230:78|230:78,1,300,0|2 +388,164,2185539,5,0 +256,360,2185968,1,8 +124,164,2186397,1,2 +180,308,2186825,1,0 +256,272,2187039,1,0 +332,308,2187254,1,8 +256,24,2187897,5,8 +256,216,2188325,1,0 +336,172,2188539,2,0,B|312:132|256:116|256:116|200:132|176:172,2,200,8|0|0 +428,337,2189825,2,0,B|397:384|334:368|272:337|319:306|319:259|303:243|256:228|209:243|194:259|194:306|241:337|178:368|116:384|85:337,1,456.000004348755,4|0 +16,176,2191111,2,0,B|59:186|103:161|113:118|113:118|114:83|115:49|81:30|46:46|28:80|44:116|78:117|113:118|113:118|157:111|184:69|177:25,1,456.000004348755,8|0 +334,24,2192397,2,0,B|327:69|354:111|398:118|398:118|433:117|467:116|483:80|465:46|430:30|396:49|397:83|398:118|398:118|408:161|452:186|496:176,1,456.000004348755,8|0 +464,254,2193468,1,0 +404,328,2193682,5,8 +256,192,2194111,1,0 +176,124,2194325,1,8 +208,36,2194539,1,8 +304,36,2194754,1,0 +336,124,2194968,1,12 +96,226,2195397,2,0,B|112:308|179:376|252:395|355:368|404:296|422:226,1,456.000004348755,0|8 +256,104,2196682,5,0 +44,48,2197111,1,0 +104,116,2197325,1,0 +176,56,2197539,2,0,B|192:13|256:-19|320:13|336:61,1,200,8|0 +336,148,2198182,2,0,B|320:191|256:223|192:191|176:143,1,200,8|0 +256,104,2198825,5,8 +164,300,2199254,1,0 +348,300,2199682,1,0 +256,192,2200111,12,8,2201397 +256,192,2201504,12,8,2202682 +256,192,2202789,12,4,2205254 +384,80,2225825,5,4 +128,80,2226468,1,2 +132,308,2227111,1,8 +384,308,2227754,1,2 +256,192,2228182,5,0 +336,148,2228397,1,8 +336,60,2228611,1,0 +256,24,2228825,1,2 +176,60,2229039,1,0 +176,148,2229254,1,0 +256,192,2229468,1,0 +320,256,2229682,2,0,B|351:297|327:368|257:398|185:368|161:297|196:248,1,300,8|2 +108,228,2230539,5,0 +72,208,2230682,1,0 +52,168,2230825,1,0 +56,128,2230968,2,0,B|64:76|112:28|176:36|212:76|212:76,1,200,8|0 +288,120,2231611,2,0,B|348:140|388:104|388:104,1,100,2|0 +468,96,2232039,1,0 +440,180,2232254,2,0,B|395:224|395:268|395:313|440:357,1,200,8|2 +256,364,2233111,2,0,B|256:156,1,200,0|8 +72,180,2233968,2,0,B|116:224|116:268|116:313|72:357,2,200,2|0|8 +48,136,2234968,1,0 +56,88,2235111,1,0 +88,60,2235254,2,0,B|112:32|148:32|172:68|172:68|196:100|236:104|260:72,1,200,2|0 +344,48,2235897,5,0 +428,80,2236111,2,0,B|496:88|508:160|492:220|440:240|372:224|372:224,1,200,4|0 +452,308,2236754,2,0,B|404:328|344:296|328:244|328:204|356:176|356:176,1,200,2|0 +336,100,2237397,2,0,B|312:60|256:44|256:44|200:60|176:100,1,200,8|0 +164,188,2238039,2,0,B|156:176|184:204|184:244|168:296|108:328|60:308,1,200,2|0 +76,220,2238682,1,8 +88,132,2238897,1,0 +172,100,2239111,5,2 +212,109,2239218,1,0 +245,142,2239325,1,0 +304,208,2239539,1,0 +388,172,2239754,1,0 +468,212,2239968,2,0,B|500:248|500:304|468:364|388:344|388:344,1,200,8|2 +368,328,2240539,1,0 +345,310,2240682,1,0 +314,311,2240825,1,0 +285,326,2240968,1,0 +252,331,2241111,1,0 +230,317,2241254,2,0,B|193:288|193:230|252:200|252:200|310:171|310:113|252:80,1,300,4|2 +176,48,2242111,6,0,B|136:92|80:76|80:76,1,100,0|0 +80,156,2242539,1,8 +168,324,2242968,5,2 +344,324,2243397,1,0 +432,156,2243825,1,4 +256,32,2244254,1,2 +176,76,2244468,2,0,B|192:119|256:151|320:119|336:71,1,200,8|0 +407,131,2245111,5,0 +388,154,2245218,1,0 +366,175,2245325,1,0 +341,192,2245432,1,0 +313,202,2245539,1,0 +283,210,2245647,1,0 +256,212,2245754,1,0 +229,210,2245861,1,0 +199,202,2245968,1,0 +171,192,2246075,1,0 +146,175,2246182,1,0 +124,154,2246289,1,0 +105,131,2246397,1,4 +52,240,2246825,5,2 +140,320,2247254,1,0 +256,348,2247682,1,8 +372,320,2248111,1,2 +460,240,2248539,1,0 +488,156,2248754,1,0 +404,172,2248968,6,0,B|320:128|320:128|304:20,1,200,8|2 +207,23,2249611,1,0 +207,23,2249825,2,0,B|192:128|192:128|108:172,1,200,0|8 +336,292,2250682,6,0,B|322:324|262:373|189:345|168:288|168:288,1,200,2|0 +135,197,2251325,2,0,B|162:132|239:74|353:107|372:205|372:205,1,300,2|0 +152,36,2252397,1,2 +360,36,2252825,1,8 +256,192,2253254,5,0 +192,250,2253468,2,0,B|157:294|184:334|233:346|256:312|256:312|282:348|328:336|364:292|309:241,2,300,2|8|0 +56,324,2255182,5,2 +56,324,2255397,1,8 +176,96,2255825,2,0,B|200:56|256:40|256:40|312:56|336:96,1,200,2|0 +256,144,2256468,1,0 +336,188,2256682,2,0,B|312:228|256:244|256:244|200:228|176:188,1,200,4|0 +456,324,2257539,1,0 +256,364,2257968,1,8 +56,324,2258397,1,2 +92,232,2258611,6,0,B|140:232|152:194|150:166|150:166|182:167|220:143|206:87,1,200,0|0 +303,101,2259254,2,0,B|298:142|329:167|361:166|361:166|359:194|371:232|420:232,1,200,2|0 +376,312,2259897,2,0,B|324:324|324:324|300:372,1,100,8|0 +208,365,2260325,2,0,B|188:324|188:324|136:312,1,100,0|8 +56,128,2260968,5,2 +256,104,2261397,1,0 +304,20,2261611,1,0 +208,20,2261825,1,8 +372,132,2262254,2,0,B|345:197|268:255|154:222|135:124|135:124,1,300,2|0 +56,188,2263111,2,0,B|76:240|144:300|220:308,1,200,8|0 +304,306,2263754,2,0,B|372:294|436:240|456:188,1,200,2|0 +456,187,2264397,1,2 +256,192,2264504,12,8,2265682 +256,192,2265789,12,10,2266968 +184,124,2267397,5,8 +256,76,2267611,1,8 +328,124,2267825,1,8 +304,212,2268039,1,0 +212,212,2268254,1,8 +256,352,2268682,5,8 +80,68,2269111,1,8 +432,64,2269539,1,12 +466,303,2269968,2,0,B|466:182|332:145|256:220|256:220|142:315|199:391|256:411|313:391|370:315|256:220|256:220|180:145|9:172|46:321|46:321,1,1000,0|8 +256,32,2272754,1,8 +176,76,2272968,2,0,B|192:119|256:151|320:119|336:71,1,200,8|8 +256,200,2273825,5,0 +168,360,2274254,1,0 +340,360,2274682,2,0,B|395:298|408:225|358:155|311:129|254:113|210:125|154:141|108:209|110:255|111:328|188:373,2,600,8|8|8 +472,200,2277897,6,0,B|472:100,1,100,8|8 +392,56,2278325,1,8 +336,128,2278539,2,0,B|312:168|256:184|256:184|200:168|176:128,1,200,4|0 +40,200,2279397,2,0,B|40:100,1,100,2|0 +116,52,2279825,1,4 +177,271,2280254,6,0,B|201:231|257:215|257:215|313:231|337:271,1,200 +299,355,2280897,2,0,B|283:372|256:377|256:377|227:372|213:354,1,100,0|8 +144,90,2281539,6,0,B|153:55|199:44|221:77|199:100|209:146|242:133|255:48|255:48|265:133|298:146|310:100|288:77|310:44|364:50|366:88,1,400,0|8 +340,268,2282825,5,0 +256,304,2283039,1,8 +172,268,2283254,1,0 +328,360,2283682,5,8 +256,196,2284111,1,0 +184,360,2284539,1,0 +256,196,2284968,5,4 +256,192,2285075,12,0,2286254 +256,192,2286361,12,0,2287539 +256,192,2287647,12,0,2290111 +192,144,2308992,6,0,B|160:96|96:80,2,100,2|0|0 +256,112,2309663,2,0,B|256:0,2,100,2|0|0 +320,144,2310335,2,0,B|352:96|416:80,2,100,2|0|0 +256,192,2311006,1,0 +176,304,2311678,2,0,B|208:352|256:368|256:368|304:352|336:304,1,200,4|4 +416,128,2312574,6,0,B|416:64|352:48,2,100,2|0|0 +256,144,2313245,2,0,B|256:32,2,100,2|0|0 +96,128,2313917,2,0,B|96:64|160:48,2,100,2|0|0 +256,144,2314589,1,0 +164,336,2315260,2,0,B|208:305|256:345|256:345|304:305|348:335|348:335,1,200,4|4 +256,256,2316156,5,0 +208,176,2316380,1,0 +112,192,2316603,1,2 +64,112,2316827,2,0,B|112:96|144:48,2,100,0|8|0 +192,112,2317499,2,0,B|256:32|384:48,1,200,2|0 +352,128,2318171,1,0 +336,208,2318394,2,0,B|336:272|400:304,1,100,2|0 +464,304,2318842,2,0,B|464:240|400:208,1,100,8|0 +352,128,2319290,5,2 +208,80,2319514,1,0 +112,192,2319738,2,0,B|64:224|80:288,1,100 +256,256,2320186,2,0,B|208:240|144:256,1,100,2|0 +112,192,2320633,1,8 +80,112,2320857,1,0 +160,80,2321081,2,0,B|256:128|352:80,1,200,2|0 +400,160,2321753,5,0 +352,240,2321977,2,0,B|351:240|307:270|259:230|259:230|211:270|167:239,1,200,2|8 +96,176,2322648,1,0 +64,256,2322872,2,0,B|64:304|80:368|176:368|208:352,1,200,2|4 +192,272,2323544,5,0 +256,208,2323768,1,2 +320,272,2323991,1,0 +311,354,2324215,2,0,B|336:368|432:368|448:304|448:256,1,200,8|0 +416,176,2324887,2,0,B|384:112|416:48,2,100,0|0|0 +320,192,2325559,1,2 +224,192,2325783,2,0,B|224:256|176:288,1,100,0|8 +160,128,2326230,2,0,B|160:64|112:32,1,100,0|2 +224,192,2326678,1,0 +160,128,2326902,1,4 +64,128,2327126,5,0 +192,48,2327350,1,2 +224,192,2327574,1,0 +304,112,2327797,2,0,B|400:48|512:80,1,200,8|2 +464,160,2328469,2,0,B|432:208|432:272,2,100,0|0|0 +464,336,2329141,1,2 +384,304,2329365,1,0 +304,256,2329589,2,0,B|256:288|208:256,1,100,8|0 +124,304,2330036,2,0,B|188:96,1,200,2|4 +156,16,2330708,6,0,B|188:128,1,100,0|2 +272,128,2331156,1,0 +352,176,2331380,2,0,B|304:288|368:384,1,200,8|0 +153,368,2332051,2,0,B|208:288|160:176,1,200 +64,176,2332723,2,0,B|96:224|96:272,1,100,2|0 +192,84,2333171,2,0,B|160:132|160:180,1,100,8|0 +256,192,2333618,1,2 +160,336,2334066,6,0,B|256:384|352:336,2,200,4|0|8 +160,256,2335186,1,2 +160,256,2335409,2,0,B|256:320|352:256,1,200,4|0 +344,168,2336081,6,0,B|360:116|332:72,1,100,0|4 +256,144,2336529,1,0 +256,144,2336641,1,0 +256,144,2336753,1,12 +192,80,2336977,1,0 +128,144,2337200,2,0,B|160:192|128:240,1,100,8|0 +240,336,2337648,6,0,B|192:240|256:144,1,200,4|2 +320,96,2338320,2,0,B|368:64|416:64,2,100,0|8|0 +380,164,2338991,1,0 +380,164,2339215,2,0,B|300:248|180:204,1,200,2|2 +284,108,2339887,1,8 +284,108,2340111,2,0,B|304:220|244:304,1,200,8|2 +160,236,2340783,1,2 +64,288,2341006,6,0,B|48:240|64:176,1,100,0|4 +48,16,2341454,2,0,B|64:64|48:128,1,100,0|2 +128,144,2341902,2,0,B|240:112,1,100,0|8 +304,128,2342350,1,0 +256,304,2342797,6,0,B|336:384|464:352,2,200,0|0|0 +381,253,2343917,2,0,B|381:189|333:157,1,100,8|0 +253,205,2344365,2,0,B|269:253|253:317,1,100,2|0 +173,253,2344812,5,4 +32,128,2345036,2,0,B|32:64|80:32,1,100,0|2 +160,80,2345484,2,0,B|144:128|160:192,1,100,0|8 +240,192,2345932,1,0 +288,112,2346156,1,2 +368,272,2346380,6,0,B|384:208|336:176,1,100,0|0 +288,256,2346827,2,0,B|272:320|320:352,1,100,0|2 +288,256,2347275,1,0 +352,176,2347499,1,8 +392,80,2347723,2,0,B|320:80|256:32|256:32|192:80|104:80,1,300,0|4 +80,160,2348618,5,0 +64,256,2348842,2,0,B|192:240,1,100,2|0 +224,320,2349290,2,0,B|96:336,1,100,8|0 +346,300,2349738,2,0,B|273:227|161:243,1,200,2|0 +176,144,2350409,1,0 +224,64,2350633,5,2 +400,48,2350857,1,0 +336,192,2351081,1,0 +256,80,2351305,5,0 +352,64,2351529,1,0 +320,160,2351753,1,0 +240,256,2351977,6,0,B|144:176|208:48,1,224.999993294478,4|2 +140,112,2352872,2,0,B|120:204|27:226,1,149.999995529652,8|0 +48,220,2353544,1,0 +88,300,2353768,1,2 +168,344,2353991,1,8 +256,364,2354215,2,0,B|336:348,1,74.9999977648259,0|0 +420,344,2354663,6,0,B|356:280|404:200,1,149.999995529652,8|2 +464,144,2355335,1,0 +384,108,2355559,2,0,B|316:68|240:120,1,150.000005722046,0|2 +260,208,2356230,1,0 +260,208,2356454,2,0,B|276:112|212:48|132:32|68:80,1,299.999991059304,10|0 +132,144,2357574,6,0,B|168:140|184:188,2,75.0000028610231,8|2|8 +140,328,2358245,2,0,B|140:232|252:200,1,149.999995529652,8|2 +216,212,2358917,1,8 +372,328,2359141,2,0,B|372:232|260:200,1,149.999995529652,4|2 +256,132,2359812,1,0 +184,80,2360036,2,0,B|256:48|256:48|328:80,2,149.999995529652,8|2|0 +108,128,2361156,1,8 +96,216,2361380,6,0,B|224:312,1,149.999995529652,2|8 +256,224,2362051,1,2 +416,216,2362275,2,0,B|288:312,2,149.999995529652,0|4|2 +420,128,2363394,1,8 +336,92,2363618,2,0,B|288:44|224:28|144:44|96:92|80:156,2,299.999991059304,8|0|8 +288,176,2365633,5,0 +160,256,2365857,1,4 +304,320,2366081,1,0 +288,176,2366305,5,6 +224,80,2366529,2,0,B|160:112|96:96,1,100,2|0 +48,48,2366977,2,2,B|16:96|48:160,1,100,2|0 +128,176,2367424,2,2,B|144:240|112:272,1,100,0|2 +160,368,2367872,2,2,B|240:288|224:176,1,200 +128,176,2368544,5,0 +227,187,2368768,2,2,B|259:139|307:123,1,100 +464,48,2369215,2,0,B|432:80|368:96,1,100,2|0 +368,192,2369663,2,2,B|480:224|480:352,1,200,6|2 +384,320,2370335,5,0 +288,336,2370559,1,2 +192,352,2370783,1,0 +96,352,2371006,2,0,B|128:304|96:256,1,100,2|0 +97,258,2371454,2,2,B|0:192|48:64,1,200 +144,80,2372126,1,0 +240,112,2372350,5,0 +336,80,2372574,1,2 +416,128,2372797,1,0 +416,32,2373021,1,2 +416,128,2373245,2,2,B|320:192|304:288,1,200,4|0 +384,320,2373917,2,0,B|288:256|160:304,1,200,2|0 +112,336,2374589,1,0 +80,256,2374812,1,0 +80,256,2375036,2,2,B|48:128|144:80,1,200,2|2 +208,144,2375708,6,2,B|256:160|320:144,1,100,0|2 +432,80,2376156,2,2,B|381:66|333:82,1,100 +240,64,2376603,1,0 +144,64,2376827,2,2,B|64:128|80:272,1,200,6|0 +32,176,2377499,1,0 +32,176,2377723,2,2,B|112:256|32:384,1,200,6|0 +120,296,2378394,2,2,B|196:196|325:267,1,200,0|4 +480,296,2379066,2,0,B|428:304|380:276,1,100 +432,96,2379514,5,0 +336,192,2379738,1,4 +472,220,2379962,1,0 +428,112,2380186,1,4 +352,184,2380409,1,0 +456,208,2380633,1,4 +256,192,2381081,6,2,B|160:112,1,100 +96,112,2381529,1,2 +96,112,2381753,1,0 +48,192,2381977,1,2 +48,192,2382200,2,2,B|204:316,1,200 +288,320,2382872,2,0,B|352:304|352:240,2,100,0|0|2 +288,320,2383544,1,0 +256,144,2383768,6,0,B|256:192|288:256,2,100,2|0|4 +192,64,2384439,2,2,B|184:121|126:149,1,100,2|2 +32,144,2384887,2,0,B|80:192|64:272,1,100,2|0 +128,320,2385335,1,2 +32,320,2385559,1,0 +128,320,2385783,6,2,B|176:224|304:256,1,200,6|0 +416,240,2386454,2,0,B|360:136|240:176,1,200,0|0 +188,92,2387126,1,4 +288,48,2387350,1,0 +368,112,2387574,1,0 +264,168,2387797,5,6 +368,112,2388021,2,2,B|432:96|480:32,2,100,0|2|2 +331,208,2388693,2,2,B|224:272|203:384,1,200,0|2 +208,358,2389365,1,0 +304,360,2389589,2,2,B|277:248|186:198,1,200,6|2 +128,128,2390260,1,0 +164,48,2390484,2,0,B|208:17|256:57|256:57|304:17|348:47|348:47,1,200,2|0 +384,128,2391156,1,2 +128,128,2391603,1,4 +256,192,2391827,12,0,2394066 +256,48,2401678,5,0 +256,48,2401790,1,0 +256,48,2401902,1,0 +256,48,2402014,1,0 +256,48,2402126,1,4 +208,112,2402350,6,0,B|256:144|304:112,1,100,0|2 +344,176,2402797,1,0 +344,176,2403021,2,0,B|312:208|256:224|192:208|152:168,1,200,8|0 +64,160,2403693,1,2 +32,240,2403917,1,0 +160,176,2404141,2,0,B|224:176|256:128,1,100,0|2 +160,96,2404589,2,0,B|128:48|160:-16,1,100,0|8 +240,32,2405036,2,0,B|309:113|453:97,1,200 +432,192,2405708,1,4 +384,272,2405932,5,0 +336,352,2406156,1,2 +256,304,2406380,2,0,B|256:192,1,100,0|8 +176,176,2406827,1,0 +176,176,2407051,2,0,B|192:48|64:32,2,200,2|0|2 +256,144,2408171,1,2 +256,144,2408394,2,0,B|272:32|400:32,1,200,8|2 +448,208,2409066,1,0 +448,208,2409290,6,0,B|432:144|448:96,1,100,4|0 +352,144,2409738,2,0,B|368:192|352:256,1,100,2|0 +272,288,2410186,2,0,B|256:224|272:176,1,100,8|0 +176,224,2410633,2,0,B|192:272|176:336,1,100,2|0 +96,320,2411081,5,0 +64,224,2411305,1,0 +80,128,2411529,2,0,B|176:64,1,100,2|0 +256,96,2411977,1,8 +353,76,2412200,2,0,B|449:12,1,100,2|0 +416,144,2412648,1,2 +416,144,2412872,6,0,B|464:240|416:352,2,200,8|0|8 +336,176,2413991,1,0 +240,176,2414215,1,2 +288,256,2414439,1,0 +240,176,2414663,1,0 +144,176,2414887,1,0 +192,256,2415111,1,10 +240,176,2415335,5,0 +240,176,2415447,1,0 +240,176,2415559,1,12 +144,48,2415783,1,0 +336,48,2416006,1,8 +240,176,2416230,1,0 +240,176,2416454,1,4 +304,240,2416678,5,0 +352,320,2416902,2,0,B|384:272|448:256,1,100,2|0 +432,176,2417350,2,0,B|320:144|320:16,1,200,8|2 +240,48,2418021,2,0,B|208:160|64:160,1,200,0|2 +48,256,2418693,1,2 +96,352,2418917,1,0 +96,352,2419141,2,0,B|144:240|256:240,2,200,8|0|4 +208,352,2420260,5,0 +304,352,2420484,1,2 +400,352,2420708,1,0 +400,352,2420932,2,0,B|352:240|240:240,1,200,8|0 +176,192,2421603,5,0 +144,112,2421827,1,2 +240,128,2422051,1,0 +240,128,2422275,2,0,B|352:128|416:16,1,200,0|8 +464,144,2422947,1,0 +416,272,2423171,1,2 +256,352,2423618,5,4 +160,336,2423842,2,0,B|112:240|176:144,1,200 +112,96,2424514,2,0,B|160:64|208:64,2,100,8|0|2 +288,80,2425186,1,0 +368,128,2425409,1,0 +352,224,2425633,1,0 +432,352,2425857,6,0,B|416:304|448:256,1,100,2|0 +256,208,2426305,2,0,B|256:320,1,100,8|0 +80,352,2426753,2,0,B|96:304|64:256,1,100,2|0 +64,160,2427200,1,4 +144,112,2427424,2,0,B|272:80,2,100,0|2|0 +192,192,2428096,2,0,B|208:320,1,100,8|0 +307,291,2428544,2,0,B|320:192,1,100,2|0 +256,96,2428991,5,0 +208,176,2429215,1,8 +304,176,2429439,1,0 +256,64,2429663,1,0 +176,176,2429887,1,4 +336,176,2430111,1,0 +388,272,2430335,1,0 +256,304,2430559,1,0 +128,272,2430783,6,0,B|128:144|48:64,1,224.999993294478,4|0 +168,88,2431678,2,0,B|252:100|312:40,1,150.000005722046,8|0 +396,40,2432350,1,8 +452,108,2432574,1,0 +416,192,2432797,1,0 +328,176,2433021,1,8 +320,88,2433245,1,0 +416,192,2433469,6,0,B|467:195|491:221,1,74.9999977648259,8|0 +420,280,2433917,2,0,B|384:364|288:344,1,150.000005722046,0|4 +324,260,2434589,1,0 +324,260,2434812,2,0,B|332:180|402:139,2,149.999995529652,0|8|0 +300,348,2435932,1,8 +220,200,2436156,1,0 +400,140,2436380,6,0,B|352:140|320:108,2,74.9999977648259,8|8|0 +348,32,2437051,2,0,B|348:120|260:144,1,150.000005722046,8|0 +260,232,2437723,1,0 +260,60,2437947,2,0,B|163:69|157:170,1,149.999995529652,4|0 +156,244,2438618,1,0 +256,336,2438842,2,0,B|352:336|360:248,2,150.000005722046,8|0|0 +256,336,2439962,1,8 +268,248,2440186,6,0,B|168:124,1,150.000005722046,0|8 +120,204,2440857,1,0 +80,124,2441081,2,0,B|168:112|196:40,2,150.000005722046,0|4|0 +120,204,2442200,1,8 +168,280,2442424,2,0,B|216:328|280:344|360:328|408:280|424:216,2,299.999991059304,8|0|0 +224,208,2444439,5,0 +352,128,2444663,1,0 +224,64,2444887,1,0 +128,80,2445111,6,2,B|80:112|96:192,1,100,6|2 +48,256,2445559,1,0 +96,336,2445783,2,0,B|160:320|224:368,1,100,0|2 +288,352,2446230,1,0 +368,320,2446454,1,2 +288,352,2446678,2,2,B|240:256|288:160,1,200 +288,64,2447350,2,0,B|176:48,1,100 +352,128,2447797,2,2,B|464:144,1,100,2|0 +400,224,2448245,5,2 +400,320,2448469,2,2,B|304:272|192:320,1,200,6|0 +128,352,2449141,2,2,B|144:288|128:240,1,100,2|0 +80,64,2449589,2,2,B|64:128|80:176,1,100,2|2 +176,192,2450036,1,0 +176,192,2450260,2,2,B|64:224|48:352,1,200 +128,256,2450932,6,0,B|240:304,1,100 +288,368,2451380,1,2 +384,320,2451603,2,0,B|475:280,1,100 +464,176,2452051,6,2,B|352:176|288:48,1,200,6|0 +302,73,2452723,1,2 +180,56,2452947,1,0 +152,180,2453171,1,2 +276,204,2453394,1,0 +152,180,2453618,1,2 +32,256,2453842,2,2,B|80:368|224:352,1,200,0|2 +256,288,2454514,1,0 +320,352,2454738,6,2,B|384:336|400:256,2,100,0|2|2 +256,288,2455409,1,0 +320,224,2455633,2,2,B|384:112|512:144,1,200,4|0 +400,128,2456305,1,0 +400,128,2456529,2,2,B|272:112|256:0,1,200,2|0 +176,32,2457200,2,2,B|192:160|64:208,1,200,0|2 +64,288,2457872,1,0 +64,288,2458096,6,0,B|128:288|176:336,1,100 +352,348,2458544,1,4 +240,352,2458768,1,0 +288,272,2458991,1,0 +304,256,2459103,1,0 +320,240,2459215,1,0 +336,224,2459327,1,0 +352,208,2459439,1,4 +256,48,2459887,6,0,B|176:128,2,100,0|2|2 +320,112,2460559,2,2,B|240:192,1,100 +384,176,2461006,2,2,B|240:320,1,200 +160,336,2461678,1,0 +80,288,2461902,1,0 +64,192,2462126,6,0,B|48:144|64:80,1,100,2|0 +128,144,2462574,2,2,B|144:192|128:256,1,100,2|0 +208,208,2463021,2,0,B|224:80|352:64,1,200,2|0 +416,96,2463693,1,0 +384,176,2463917,2,0,B|432:384,2,200,2|2|0 +304,224,2465036,1,0 +304,224,2465260,6,0,B|336:336,1,100 +192,240,2465708,2,0,B|160:128,1,100 +32,112,2466156,1,0 +144,32,2466380,1,0 +144,32,2466603,6,2,B|192:64|240:48,2,100,6|0|0 +144,128,2467275,2,2,B|224:192|208:320,1,200 +304,304,2467947,1,0 +304,304,2468171,2,2,B|289:188|369:124,1,200 +416,48,2468842,5,4 +256,32,2469066,1,0 +96,48,2469290,1,4 +192,128,2469514,1,0 +320,128,2469738,1,0 +256,240,2469962,1,0 +256,240,2470409,1,0 +256,192,2470633,12,0,2472872 +256,64,2487424,5,0 +256,64,2487648,1,0 +432,192,2488096,5,4 +368,256,2488320,2,0,B|368:368,2,100,0|0|8 +336,176,2488991,2,0,B|240:112,2,100,0|0|8 +256,224,2489663,2,0,B|160:160,2,100,0|0|8 +176,272,2490335,2,0,B|112:272|80:336,2,100,0|0|8 +336,272,2491006,2,0,B|400:272|432:336,2,100,0|0|8 +384,192,2491678,5,4 +416,112,2491902,1,0 +384,32,2492126,1,0 +256,32,2492350,5,8 +256,128,2492574,1,0 +256,224,2492797,1,0 +128,192,2493021,5,8 +96,112,2493245,1,0 +128,32,2493469,1,0 +256,32,2493693,6,0,B|256:160,3,100,8|0|0|8 +352,128,2494589,1,0 +432,176,2494812,2,0,B|384:208|400:288,1,100,8|0 +448,336,2495260,6,0,B|456:296|440:264|380:264|380:264|296:264,1,200,4|8 +124,256,2495932,2,0,B|116:216|132:184|192:184|192:184|276:184,1,200,0|8 +416,176,2496603,2,0,B|424:136|408:104|348:104|348:104|264:104,1,200,0|8 +164,104,2497275,2,0,B|100:24,2,100,0|0|8 +100,184,2497947,2,0,B|4:104,2,100,0|0|8 +256,192,2498618,1,0 +256,192,2498842,12,4,2500633 +384,48,2500857,6,0,B|368:112|384:160,1,100 +336,272,2501305,1,0 +336,272,2501529,2,0,B|272:64,1,200,4|0 +176,64,2502200,2,0,B|64:80,1,100,0|4 +64,176,2502648,6,0,B|32:304,1,100,8|0 +144,192,2503096,2,0,B|112:304,1,100,0|8 +224,208,2503544,2,0,B|192:320,1,100,0|8 +384,240,2503991,6,0,B|416:112,1,100 +304,224,2504439,2,0,B|336:112,1,100,8|0 +224,208,2504887,2,0,B|256:95,1,100,0|8 +176,48,2505335,5,0 +80,80,2505559,1,8 +48,176,2505783,1,0 +64,272,2506006,5,4 +256,352,2506454,1,0 +448,272,2506902,1,4 +352,96,2507350,1,0 +160,96,2507797,1,4 +256,272,2508245,2,0,B|292:185|228:158|260:74,2,200,0|4|0 +256,192,2509589,12,4,2513171 +336,48,2526380,5,0 +304,80,2526603,1,0 +272,112,2526827,1,0 +240,144,2527051,1,0 +208,176,2527275,1,0 +128,240,2527499,5,6 +48,304,2527723,2,2,B|144:336,1,100,2|0 +304,320,2528171,2,2,B|208:288,1,100,2|0 +304,224,2528618,1,2 +336,144,2528842,1,0 +304,224,2529066,2,2,B|496:288,1,200 +464,192,2529738,6,0,B|432:144|464:80,1,100 +256,64,2530186,2,2,B|368:80,1,100 +176,32,2530633,1,0 +176,32,2530857,2,2,B|176:160|16:192,1,200,6|0 +32,352,2531529,6,2,B|80:240,1,100,2|0 +208,304,2531977,1,2 +160,208,2532200,1,0 +71,261,2532424,1,0 +208,304,2532648,2,2,B|272:304|272:304|320:296|320:296|408:296,1,200,2|2 +448,204,2533320,6,0,B|320:204,1,100 +256,56,2533768,2,0,B|300:160,1,100,2|0 +220,216,2534215,1,0 +220,216,2534439,2,2,B|132:216|132:216|84:224|84:224|20:224,1,200,6|0 +80,308,2535111,1,2 +80,308,2535335,1,0 +80,308,2535559,2,2,B|132:276|148:216|120:164|72:152,1,200,2|0 +71,152,2536230,2,2,B|84:104|140:76|204:92|228:144,1,200,2|0 +380,136,2536902,5,0 +228,144,2537126,1,0 +308,208,2537350,1,2 +404,304,2537574,2,0,B|280:332,1,100,0|2 +116,256,2538021,2,2,B|192:232|192:232|248:232|248:232|324:204,1,200,6|0 +224,156,2538693,1,0 +224,156,2538917,2,2,B|300:132|300:132|356:132|356:132|432:104,1,200,6|0 +352,36,2539589,6,0,B|256:28|256:28|152:40,2,200 +312,164,2540708,1,0 +176,128,2540932,1,4 +56,156,2541156,5,0 +88,296,2541380,1,4 +220,204,2541603,1,0 +220,204,2541827,1,4 +420,224,2542275,5,0 +420,224,2542499,2,2,B|408:84,1,100,2|0 +328,68,2542947,1,2 +228,72,2543171,1,0 +328,68,2543394,2,2,B|344:272,1,200 +264,332,2544066,2,0,B|208:340|160:308,1,100 +72,336,2544514,6,2,B|192:316|180:184,1,200,2|0 +80,208,2545186,2,2,B|68:86|188:66,1,200,6|0 +284,48,2545857,2,0,B|384:40,2,100,0|2|0 +304,148,2546529,1,2 +208,116,2546753,1,0 +176,212,2546977,6,2,B|-8:316,1,200,2|0 +100,336,2547648,2,2,B|284:232,1,200,2|0 +376,248,2548320,2,0,B|436:248|460:200,2,100 +384,148,2548991,5,6 +364,48,2549215,1,0 +264,56,2549439,1,2 +192,124,2549663,2,2,B|144:228|208:320,1,200,0|2 +315,314,2550335,1,0 +315,314,2550559,2,2,B|376:228|328:124,1,200 +396,52,2551230,6,0,B|508:108,1,100 +328,124,2551678,2,0,B|240:80,1,100 +96,132,2552126,2,0,B|208:188,1,100 +256,292,2552574,1,4 +256,292,2552797,1,0 +256,192,2553021,12,4,2555708 +357,168,2579508,5,4 +232,96,2579732,1,2 +152,234,2579956,1,0 +280,300,2580179,1,0 +280,300,2580851,5,4 +152,234,2581075,1,2 +232,96,2581299,1,0 +357,168,2581523,1,0 +256,192,2582194,5,2 +256,192,2582418,1,0 +256,192,2582642,1,2 +208,364,2583090,6,0,B|224:378|256:394|288:378|304:362,1,100,4|0 +256,296,2583538,1,2 +200,232,2583762,2,0,B|163:179|192:112|255:92|304:104|346:153|336:200|312:232|312:232,1,300,0|4 +368,301,2584657,2,0,B|411:263|413:197,1,100,0|2 +405,123,2585105,1,0 +144,301,2585777,2,0,B|101:263|99:197,1,100,4|0 +107,123,2586224,1,4 +256,192,2586672,5,0 +256,192,2586784,1,0 +256,192,2586896,1,0 +256,192,2587120,1,0 +256,280,2587344,1,0 +451,140,2588015,6,0,B|465:124|481:92|465:60|449:44,1,100 +362,48,2588463,2,0,B|349:59|333:91|349:123|363:139,1,100 +336,328,2589359,1,0 +256,368,2589582,1,0 +176,328,2589806,1,0 +148,140,2590254,2,0,B|162:124|178:92|162:60|146:44,1,100 +59,48,2590702,2,0,B|46:59|30:91|46:123|60:139,1,100 +209,344,2591597,6,0,B|255:362|255:362|302:344,1,100 +301,243,2592045,2,0,B|254:225|254:225|207:244,1,100 +332,76,2592941,5,0 +256,20,2593165,1,0 +180,76,2593388,1,0 +212,156,2593612,1,0 +300,156,2593836,1,0 +300,156,2593948,1,0 +300,156,2594060,1,0 +376,204,2594284,5,0 +468,196,2594508,1,0 +504,276,2594732,1,0 +440,344,2594956,1,0 +360,296,2595179,1,0 +304,373,2595403,2,0,B|288:387|256:403|224:387|208:371,1,100,0|0 +152,296,2595851,1,0 +72,344,2596075,1,0 +8,276,2596299,1,0 +44,196,2596523,1,0 +136,204,2596747,1,0 +176,124,2596971,5,0 +208,92,2597082,1,0 +252,76,2597194,1,0 +296,92,2597306,1,0 +324,124,2597418,1,0 +256,184,2597642,1,0 +340,240,2597866,2,0,B|368:280|368:280|420:296|420:296,2,100 +256,284,2598538,2,0,B|256:388|256:388,2,100 +172,240,2599209,2,0,B|144:280|144:280|92:296|92:296,2,100 +256,180,2599881,1,0 +360,256,2600105,5,0 +360,256,2600217,1,0 +360,256,2600329,1,0 +360,256,2600441,1,0 +360,256,2600553,1,0 +360,256,2600665,1,0 +360,256,2600777,1,0 +360,256,2600888,1,0 +360,257,2601000,6,0,B|347:343|241:378,2,150,4|0|0 +400,240,2601784,1,0 +448,226,2601896,1,0 +277,64,2602344,1,0 +229,78,2602456,1,0 +181,92,2602568,1,0 +157,188,2602791,6,0,B|71:175|36:69,2,150 +145,237,2603575,1,0 +127,283,2603687,1,0 +284,159,2604135,1,0 +302,113,2604247,1,0 +314,65,2604359,1,0 +397,119,2604582,6,0,B|420:34|505:13,2,150 +444,136,2605366,1,0 +491,152,2605478,1,0 +346,290,2605926,1,0 +299,272,2606038,1,0 +252,252,2606150,1,0 +221,156,2606374,6,0,B|246:79|180:5,2,150 +179,163,2607157,1,0 +129,170,2607269,1,0 +17,336,2607717,1,0 +66,331,2607829,1,0 +115,323,2607941,1,0 +196,265,2608165,6,0,B|289:271|311:373,2,150 +205,215,2608948,1,0 +214,165,2609060,1,0 +401,233,2609508,1,0 +413,184,2609620,1,0 +426,135,2609732,1,0 +415,36,2609956,6,0,B|290:22|248:130,2,200 +216,8,2611299,1,0 +35,94,2611747,5,0 +58,137,2611859,1,0 +78,182,2611971,2,0,B|141:186|158:255,1,100 +99,323,2612418,1,0 +183,376,2612642,1,0 +183,376,2612866,1,0 +249,301,2613090,6,0,B|388:315,1,100 +429,251,2613538,1,0 +429,251,2613762,1,0 +375,167,2613985,1,0 +388,67,2614209,1,0 +196,65,2614433,6,0,B|171:20,7,50,0|0|0|0|0|0|0|0 +218,111,2615329,2,0,B|163:231|296:295,1,200,4|0 +347,216,2616000,1,0 +438,257,2616224,1,0 +414,354,2616448,5,0 +414,354,2616560,1,0 +414,354,2616672,2,0,B|175:368,1,200 +214,365,2617232,1,0 +214,365,2617344,1,0 +118,336,2617568,1,0 +161,246,2617791,1,0 +80,186,2618015,2,0,B|68:67|191:31,1,200 +180,34,2618575,1,0 +180,34,2618687,1,0 +230,121,2618911,5,0 +230,121,2619023,1,0 +230,121,2619135,2,0,B|363:104,1,100 +321,208,2619582,1,0 +321,208,2619694,1,0 +321,208,2619806,2,0,B|454:191,1,100 +482,274,2620254,6,0,B|281:299,1,200 +283,299,2620814,1,0 +283,299,2620926,1,0 +208,365,2621150,1,0 +122,312,2621374,1,0 +195,244,2621597,1,0 +195,244,2621709,1,0 +195,244,2621821,2,0,B|209:139,1,100 +148,63,2622269,1,0 +75,131,2622493,5,0 +75,131,2622605,1,0 +75,131,2622717,2,0,B|61:236,1,100 +87,327,2623165,1,0 +175,372,2623388,2,0,B|301:374|320:255,1,200 +409,218,2624060,1,0 +409,218,2624284,5,0 +103,218,2624732,1,0 +409,218,2625179,1,0 +103,218,2625627,1,0 +256,48,2626075,5,0 +256,48,2626187,1,0 +256,48,2626299,2,0,B|281:167,1,100 +186,191,2626747,1,0 +234,278,2626971,2,0,B|218:342|129:338,1,100 +88,268,2627418,1,0 +88,268,2627642,1,0 +43,178,2627866,5,0 +43,178,2627978,1,0 +43,178,2628090,2,0,B|66:72,1,100 +179,45,2628538,2,0,B|156:151,1,100 +240,255,2628985,2,0,B|263:149,1,100 +420,100,2629433,1,0 +420,100,2629545,1,0 +420,100,2629657,1,4 +340,310,2630105,6,0,B|316:352|256:374|204:357|173:311,1,200,0|0 +172,74,2631000,2,0,B|196:32|256:10|308:27|339:73,1,200,0|0 +339,73,2631560,1,0 +339,73,2631672,1,0 +43,206,2632120,6,0,B|66:312,1,100 +179,339,2632568,2,0,B|156:233,1,100 +240,129,2633015,2,0,B|263:235,1,100,0|4 +398,343,2633687,2,0,B|433:355|475:325|475:280|454:238|395:234|371:270,1,200 +393,60,2634582,2,0,B|357:47|315:77|315:122|336:164|395:168|419:132,1,200 +414,138,2635142,1,0 +414,138,2635254,1,0 +242,86,2635702,6,0,B|220:38|220:38|166:28|166:28,1,100 +119,101,2636150,1,0 +36,64,2636374,2,0,B|-24:160|36:252,1,200,0|4 +336,356,2637269,2,0,B|312:312|256:300|256:300|200:312|176:356,1,200 +476,251,2638165,2,0,B|536:160|476:64,1,200 +476,64,2638724,1,0 +476,64,2638836,1,0 +256,116,2639284,5,0 +328,172,2639508,1,0 +300,256,2639732,1,0 +212,256,2639956,1,0 +184,172,2640179,1,0 +108,128,2640403,6,0,B|80:64|80:64,2,50,0|0|0 +176,296,2640851,2,0,B|148:360|148:360,2,50 +256,96,2641299,2,0,B|256:48|256:48,2,50 +336,296,2641747,2,0,B|364:360|364:360,2,50 +404,128,2642194,2,0,B|432:64|432:64,2,50 +296,208,2642642,22,0,B|288:232|256:240|224:232|216:208,1,100 +256,120,2643090,1,0 +140,268,2643314,1,0 +172,296,2643426,1,0 +212,316,2643538,1,0 +256,324,2643650,1,0 +300,316,2643762,1,0 +340,296,2643874,1,0 +372,268,2643985,1,4 +152,152,2644433,6,0,B|130:104|130:104|76:94|76:94,1,100 +256,100,2644881,2,0,B|256:-8,1,100 +360,152,2645329,2,0,B|382:104|382:104|436:94|436:94,1,100 +209,344,2646224,6,0,B|255:362|255:362|302:344,1,100 +303,243,2646672,2,0,B|256:225|256:225|209:244,1,100 +40,50,2647344,22,0,B|61:104|116:123|179:104|196:45,1,200 +296,160,2648015,2,0,B|288:136|256:128|224:136|216:160,1,100 +317,50,2648463,2,0,B|333:104|396:123|451:104|472:50,1,200,0|0 +296,160,2649135,2,0,B|328:216|328:216|280:232|257:262|257:262|232:232|185:214|185:214|222:152,1,300 +304,348,2650030,6,0,B|256:376|204:348,1,100 +424,192,2650478,1,0 +257,53,2650702,1,0 +88,192,2650926,1,0 +88,192,2651038,1,0 +88,192,2651150,1,4 +297,350,2651597,6,0,B|297:302|297:302|337:262,1,100 +217,350,2652045,2,0,B|217:302|217:302|177:262,1,100 +417,115,2652717,22,0,B|367:16|260:-22|138:16|95:118,1,400,0|0 +172,238,2653836,2,0,B|198:283|257:300|314:282|339:237,1,200,0|0 +256,152,2654508,1,0 +256,192,2654732,12,0,2656299 +114,110,2656971,6,0,B|231:63,1,100 +398,274,2657418,2,0,B|281:321,1,100 +257,327,2657754,1,0 +207,329,2657866,1,0 +160,311,2657978,1,0 +136,266,2658090,1,0 +143,216,2658202,1,0 +179,181,2658314,2,0,B|280:89|156:-1,1,200,4|0 +376,255,2658985,6,0,B|367:325|432:349,1,100 +450,247,2659433,2,0,B|459:177|394:153,1,100 +312,172,2659881,2,0,B|259:272|112:233,1,200 +243,9,2660553,6,0,B|234:79|299:103,1,100 +367,44,2661000,1,0 +451,98,2661224,2,0,B|460:168|395:192,1,100 +454,275,2661672,2,0,B|225:269,1,200 +122,144,2662344,6,0,B|222:146,1,100 +220,311,2662791,2,0,B|420:317,1,200 +291,72,2663463,2,0,B|92:67,1,200 +96,233,2664135,5,0 +69,321,2664359,1,0 +155,346,2664582,1,0 +182,261,2664806,1,0 +96,234,2665030,1,0 +182,262,2665254,2,0,B|398:231,1,200 +506,369,2665926,6,0,B|497:299|449:257,1,100 +500,182,2666374,1,0 +427,113,2666597,2,0,B|436:43|492:10,1,100 +372,3,2667045,2,0,B|267:26|234:157,1,200 +385,212,2667717,6,0,B|381:273|324:303,1,100 +399,33,2668165,2,0,B|294:56|261:187,1,200 +79,197,2668836,2,0,B|184:174|217:43,1,200 +302,18,2669508,5,0 +376,85,2669732,2,0,B|427:96|465:165,2,100 +305,156,2670403,2,0,B|347:255|469:286,1,200 +231,107,2671075,6,0,B|145:108|111:163,2,100 +248,8,2671747,2,0,B|132:2|44:73,1,200 +22,95,2672306,1,0 +11,143,2672418,1,0 +35,186,2672530,1,0 +84,195,2672642,6,0,B|148:201|156:271,1,100,4|0 +252,240,2673090,1,0 +235,338,2673314,2,0,B|358:357|418:242,1,200 +175,277,2673985,6,0,B|110:267|73:328,1,100 +0,256,2674433,1,0 +82,199,2674657,2,0,B|47:88|92:-9,1,200 +216,125,2675329,6,0,B|294:191,1,100 +338,100,2675777,2,0,B|494:232,1,200 +463,325,2676448,1,0 +463,325,2676672,2,0,B|422:365|353:347,1,100 +275,25,2677568,5,0 +275,25,2677791,1,0 +275,25,2678015,2,0,B|303:70|297:135,1,100 +44,299,2678911,6,0,B|108:297|155:246,1,100 +47,322,2679359,2,0,B|111:320|158:269,1,100 +237,272,2679806,5,0 +237,272,2679918,1,0 +237,272,2680030,2,0,B|352:306,1,100 +370,207,2680478,1,0 +444,273,2680702,2,0,B|493:237|487:181,2,100 +425,371,2681374,6,0,B|214:354,1,200 +133,262,2682045,2,0,B|332:278,1,200 +358,196,2682717,2,0,B|252:187,1,100 +208,183,2683053,1,0 +158,178,2683165,1,0 +108,173,2683277,1,0 +58,166,2683388,2,0,B|20:124|41:55,1,100 +108,2,2683836,2,0,B|146:44|125:113,1,100 +222,57,2684284,6,0,B|433:40,1,200,0|0 +492,111,2684956,2,0,B|293:127,1,200 +235,209,2685627,2,0,B|341:200,1,100 +263,129,2686075,1,0 +213,134,2686187,1,0 +163,141,2686299,2,0,B|125:183|146:252,1,100 +256,30,2697717,5,0 +110,125,2697941,1,0 +133,290,2698165,1,0 +299,333,2698388,1,0 +421,211,2698612,1,0 +342,54,2698836,5,0 +169,54,2699060,1,0 +90,211,2699284,1,0 +212,333,2699508,1,0 +378,290,2699732,1,0 +401,125,2699956,1,0 +256,30,2700179,1,0 +256,192,2700403,5,0 +256,192,2700515,1,0 +256,192,2700627,1,0 +256,192,2700739,1,0 +256,192,2700851,1,0 +256,192,2700963,1,0 +256,192,2701075,1,0 +256,192,2701187,1,0 +256,192,2701299,1,4 +326,324,2701635,6,0,B|306:358|256:377|213:363|187:324,1,164.999995082617,0|0 +60,280,2702194,1,0 +76,148,2702418,2,0,B|88:92|88:92|132:52,1,109.999996721745 +209,147,2702866,2,0,B|219:123|254:115|289:122|302:146,1,109.999996721745 +256,188,2703202,1,0 +256,240,2703314,1,0 +384,56,2703538,2,0,B|424:92|424:92|436:148,1,109.999996721745 +326,311,2703985,2,0,B|306:345|256:364|213:350|187:311,2,164.999995082617 +256,192,2704881,5,0 +256,144,2704993,1,0 +256,96,2705105,1,0 +130,80,2705329,2,0,B|119:56|84:48|50:55|37:79,1,109.999996721745 +209,259,2705777,2,0,B|219:282|254:290|289:284|302:260,1,109.999996721745 +475,79,2706224,2,0,B|462:55|427:48|392:56|382:80,1,109.999996721745 +408,296,2706672,6,0,B|464:264,2,54.9999983608723 +300,336,2707120,2,0,B|286:360|256:366|256:366|225:360|212:336,1,109.999996721745 +192,228,2707568,2,0,B|211:192|257:182|257:182|303:192|322:228,2,164.999995082617 +104,296,2708463,2,0,B|48:264,2,54.9999983608723 +136,108,2708911,5,0 +256,32,2709135,1,0 +376,108,2709359,1,0 +336,260,2709582,1,0 +176,260,2709806,1,0 +256,152,2710030,1,0 +256,152,2710254,5,0 +404,348,2710702,1,0 +108,348,2711150,1,0 +175,131,2711597,6,0,B|192:71|257:48|320:72|338:134,1,219.999993443489,0|0 +256,156,2712157,2,0,B|256:232,1,54.9999983608723 +356,280,2712493,2,0,B|372:332|372:332|436:360|436:360,2,109.999996721745 +256,364,2713165,1,0 +156,280,2713388,2,0,B|140:332|140:332|76:360|76:360,2,109.999996721745 +156,280,2713948,1,0 +156,280,2714060,1,0 +209,160,2714284,2,0,B|219:136|254:128|289:135|302:159,1,109.999996721745 +95,106,2714732,5,0 +124,65,2714844,1,0 +161,33,2714956,1,0 +206,15,2715068,1,0 +256,8,2715179,1,0 +306,15,2715291,1,0 +351,33,2715403,1,0 +388,65,2715515,1,0 +417,106,2715627,1,4 +340,310,2716075,6,0,B|316:352|256:374|204:357|173:311,1,200,0|0 +340,129,2716971,2,0,B|316:87|256:65|204:82|173:128,1,200,0|0 +256,176,2717530,2,0,B|256:232|256:232,1,50 +400,336,2718090,6,0,B|440:224,1,100 +256,176,2718538,2,0,B|256:276,1,100 +112,336,2718985,2,0,B|72:224,1,100,0|4 +176,88,2719657,6,0,B|200:44|256:32|256:32|312:44|336:88,1,200 +336,296,2720553,2,0,B|312:340|256:352|256:352|200:340|176:296,1,200 +256,252,2721112,2,0,B|256:184|256:184,1,50 +160,48,2721672,5,0 +216,120,2721896,2,0,B|224:96|256:88|288:96|296:120,1,100 +352,48,2722344,1,0 +256,204,2722791,5,4 +160,356,2723239,1,0 +352,356,2723687,1,0 +340,157,2724135,2,0,B|316:115|256:93|204:110|173:156,1,200,0|0 +256,204,2724694,2,0,B|256:260|256:260,1,50 +416,92,2725254,5,0 +348,28,2725478,1,0 +256,8,2725702,1,0 +164,28,2725926,1,0 +96,92,2726150,1,0 +160,164,2726374,5,0 +144,212,2726485,1,0 +104,244,2726597,1,0 +208,316,2726821,2,0,B|190:336|190:336|180:360,2,50 +304,316,2727269,2,0,B|322:336|322:336|332:360,2,50 +408,244,2727717,1,0 +368,212,2727829,1,0 +352,164,2727941,1,0 +184,80,2728165,5,0 +80,104,2728388,1,0 +328,80,2728612,5,0 +432,104,2728836,1,0 +256,32,2729060,5,0 +256,104,2729284,1,0 +270,190,2729508,1,0 +270,190,2729620,1,0 +270,190,2729732,1,0 +270,190,2729844,1,0 +270,190,2729956,6,0,B|271:312|126:334,1,200,4|0 +344,248,2730627,2,0,B|402:270|419:337,2,100 +409,169,2731299,1,0 +310,151,2731523,2,0,B|325:32|457:27,1,200 +297,226,2732194,6,0,B|286:279|226:297,2,100 +204,186,2732866,1,0 +131,117,2733090,1,0 +208,53,2733314,2,0,B|432:48,1,200 +226,288,2733985,6,0,B|160:280|141:204,1,100 +276,358,2734433,2,0,B|393:349|414:209,1,200 +236,26,2735105,2,0,B|119:35|98:175,1,200 +166,229,2735777,5,0 +84,285,2736000,2,0,B|65:344|-6:353,2,100 +48,190,2736672,1,0 +118,118,2736896,1,0 +118,118,2737008,1,0 +118,118,2737120,2,0,B|326:103,1,200,4|0 +455,238,2737791,6,0,B|393:233|379:150,2,100 +476,139,2738463,1,0 +458,301,2738687,2,0,B|338:294|324:163,1,200 +476,139,2739359,1,0 +476,139,2739582,1,0 +416,356,2739806,6,0,B|295:340|272:206,1,200 +96,28,2740478,2,0,B|216:43|240:178,1,200 +145,200,2741150,1,0 +218,268,2741374,2,0,B|330:261,2,100 +169,355,2742045,6,0,B|389:363,1,200 +419,366,2742605,1,0 +468,367,2742717,1,0 +500,272,2742941,1,0 +448,186,2743165,1,0 +397,184,2743277,1,0 +347,181,2743388,1,0 +369,83,2743612,5,0 +318,82,2743724,1,0 +268,78,2743836,1,0 +217,77,2743948,1,0 +166,75,2744060,1,0 +116,71,2744172,1,0 +66,68,2744284,6,0,B|22:166|85:277,1,200,4|0 +174,237,2744956,1,0 +152,334,2745179,1,0 +152,334,2745403,2,0,B|259:358,1,100 +316,280,2745851,2,0,B|435:312|488:212,1,200 +194,111,2746523,5,0 +274,50,2746747,1,0 +194,111,2746971,1,0 +194,111,2747194,1,0 +261,185,2747418,1,0 +206,269,2747642,2,0,B|236:385|393:359,1,200 +438,298,2748314,6,0,B|440:198,1,100 +330,214,2748762,2,0,B|336:14,1,200 +209,285,2749433,2,0,B|214:86,1,200 +445,24,2750105,5,0 +345,30,2750329,1,0 +245,36,2750553,1,0 +145,41,2750777,1,0 +150,140,2751000,1,0 +50,153,2751224,2,0,B|21:275|129:337,1,200 +444,267,2751896,5,0 +345,254,2752120,1,0 +345,254,2752344,1,0 +444,267,2752568,1,0 +345,254,2752791,1,0 +285,335,2753015,2,0,B|161:325|165:199,1,200 +80,150,2753687,6,0,B|40:183|33:247,1,100 +139,102,2754135,2,0,B|234:49|359:83,1,200 +373,282,2754806,2,0,B|278:335|153:301,1,200 +147,211,2755478,5,0 +78,284,2755702,2,0,B|4:294|-11:364,2,100 +20,201,2756374,2,0,B|62:102|17:-2,1,200 +207,276,2757045,6,0,B|272:283|323:217,2,100 +156,362,2757717,2,0,B|249:393|381:350,1,200 +401,346,2758277,1,0 +443,319,2758388,1,0 +467,275,2758500,1,4 +480,228,2758612,5,4 +304,57,2759060,2,0,B|335:101|310:159|256:176|213:166|177:124|184:84|207:56|207:56,1,259.999995350838,0|0 +32,228,2759956,1,0 +298,356,2760403,2,0,B|312:296|312:296|256:256|256:256|199:296|199:296|215:357|215:357,1,259.999995350838 +368,112,2761299,5,0 +256,48,2761523,1,0 +144,112,2761747,1,0 +223,233,2761971,2,0,B|232:217|256:211|282:220|290:232,1,74.9999977648259 +256,304,2762194,1,0 +474,73,2762642,6,0,B|430:90|431:171|489:190|489:190|430:215|427:293|489:319,1,299.999991059304 +37,311,2763538,6,0,B|80:288|82:215|23:190|23:190|81:171|82:90|38:73,1,299.999991059304 +256,296,2764433,5,0 +256,24,2764881,1,0 +308,169,2765105,1,0 +184,88,2765329,1,0 +336,88,2765553,1,0 +336,88,2765665,1,0 +336,88,2765777,1,0 +212,168,2766000,1,0 +80,224,2766224,5,0 +144,328,2766448,1,0 +256,368,2766672,1,0 +368,328,2766896,1,0 +432,224,2767120,1,0 +298,192,2767344,2,0,B|302:215|288:248|259:253|233:252|210:230|206:209|215:190|215:190,1,149.999995529652,0|0 +192,82,2767791,2,0,B|256:42|256:42|320:82,1,149.999995529652 +397,162,2768239,6,0,B|465:191|465:191|484:274,1,149.999995529652 +440,362,2768687,1,0 +344,312,2768911,1,0 +308,348,2769023,1,0 +256,360,2769135,1,0 +204,348,2769247,1,0 +168,312,2769359,1,0 +72,364,2769582,1,0 +30,265,2769807,2,0,B|47:191|47:191|115:162,1,149.999995529652 +96,60,2770254,5,0 +196,96,2770478,2,0,B|205:120|244:118|256:89|256:89|265:118|306:118|315:97,1,149.999995529652 +420,60,2770926,1,0 +404,164,2771150,1,0 +318,240,2771374,2,0,B|299:208|254:191|215:204|191:239,1,149.999995529652,0|0 +208,340,2771821,1,0 +256,316,2771933,1,0 +304,340,2772045,1,0 +388,168,2772269,5,0 +356,128,2772381,1,0 +308,100,2772493,1,0 +256,88,2772605,1,0 +204,100,2772717,1,0 +156,128,2772829,1,0 +124,168,2772941,1,4 +256,296,2773165,5,0 +256,296,2773277,1,0 +256,296,2773388,1,0 +120,360,2773612,1,0 +28,240,2773836,1,0 +112,116,2774060,1,0 +256,152,2774284,1,0 +256,296,2774508,5,0 +256,296,2774620,1,0 +256,296,2774731,1,0 +392,360,2774955,1,0 +484,240,2775179,1,0 +400,116,2775403,1,0 +320,16,2775627,6,0,B|301:48|256:65|217:52|193:17,1,149.999995529652,0|0 +112,120,2776075,1,0 +204,240,2776299,1,0 +256,220,2776411,1,0 +304,240,2776523,1,4 +196,344,2776747,2,0,B|213:376|255:385|255:385|297:376|315:344,1,149.999995529652 +436,252,2777194,5,0 +396,108,2777418,1,0 +256,56,2777642,1,0 +116,108,2777866,1,0 +76,252,2778090,1,0 +76,252,2778202,1,0 +76,252,2778314,1,0 +208,348,2778538,5,0 +256,208,2778762,1,0 +304,348,2778985,1,0 +180,264,2779209,1,0 +332,264,2779433,1,0 +348,124,2779657,1,0 +312,80,2779769,1,0 +256,60,2779881,1,0 +200,80,2779993,1,0 +164,124,2780105,1,4 +76,240,2780329,5,0 +256,160,2780553,1,0 +436,244,2780777,1,0 +192,368,2781000,2,0,B|211:336|256:319|295:332|319:367,2,149.999995529652 +192,192,2781672,1,0 +256,192,2781784,1,0 +320,192,2781896,1,0 +320,16,2782120,6,0,B|301:48|256:65|217:52|193:17,2,149.999995529652 +424,120,2782791,1,0 +256,192,2783015,1,0 +96,120,2783239,1,0 +256,352,2783463,1,0 +256,192,2783687,6,0,B|252:203|238:234|285:241|301:236|315:230|336:212|323:182|328:171|332:161|326:145|310:125|295:132|283:107|277:112|244:105|219:108|204:131|189:147|180:169|180:169|169:197|172:220|178:254|191:280|206:310|255:317|286:330|322:338|357:307|381:282|403:250|408:216|414:197|422:176|425:150|406:108|386:57|353:37|305:22|251:26|214:22|174:41|139:77|121:113|107:146|107:146|97:176|87:233|95:284|124:325|166:371|200:383|250:410|330:406|384:383|417:348|445:311|483:242|479:162,1,1799.99994635582 +478,147,2786485,1,0 +472,126,2786597,1,0 +466,104,2786709,1,0 +457,83,2786821,1,0 +444,64,2786933,1,0 +432,44,2787045,1,0 +415,28,2787157,1,0 +397,14,2787269,1,4 +256,80,2801597,5,4 +256,192,2801709,12,0,2804284 +366,72,2804508,5,0 +422,120,2804732,1,0 +422,192,2804956,1,0 +366,239,2805179,2,0,B|253:192|145:240,1,224.999993294478,4|0 +113,306,2806075,2,0,B|258:390|408:299,1,300 +494,192,2807418,1,0 +398,79,2807866,2,0,B|258:-6|114:78,1,299.999991059304,4|4 +256,192,2808874,12,0,2811896 +64,152,2812120,5,0 +64,232,2812344,1,4 +256,192,2812456,12,0,2814135 +424,116,2814359,5,0 +426,153,2814471,1,0 +411,187,2814582,1,0 +380,208,2814694,1,0 +342,205,2814806,1,0 +309,186,2814918,1,0 +272,177,2815030,1,0 +234,178,2815142,1,0 +199,192,2815254,1,0 +177,222,2815366,1,0 +167,258,2815478,1,0 +171,293,2815590,1,0 +195,321,2815702,1,0 +227,339,2815814,1,0 +264,339,2815926,6,0,B|306:298|375:323,1,100 +441,263,2816374,1,0 +362,201,2816597,2,0,B|347:124|383:92,2,100 +272,248,2817269,1,0 +201,177,2817493,5,0 +201,177,2817605,1,0 +201,177,2817717,2,0,B|93:194,2,100 +289,129,2818388,1,0 +312,32,2818612,5,0 +410,30,2818836,1,0 +204,32,2819060,1,0 +106,30,2819284,1,0 +29,94,2819508,6,0,B|14:141|65:193,1,100 +57,184,2819844,1,0 +57,184,2819956,1,0 +52,284,2820179,5,0 +52,284,2820291,1,0 +52,284,2820403,2,0,B|170:273,1,100 +151,274,2820739,1,0 +151,274,2820851,1,0 +232,333,2821075,5,0 +232,333,2821187,1,0 +232,333,2821299,2,0,B|298:328|314:249,1,100 +306,275,2821635,1,0 +306,275,2821747,1,0 +401,305,2821971,5,0 +401,305,2822082,1,0 +401,305,2822194,2,0,B|382:81,1,200 +349,69,2822754,1,0 +303,47,2822866,1,0 +254,60,2822978,1,0 +232,105,2823090,6,0,B|234:166|174:188,1,100 +147,220,2823426,1,0 +111,255,2823538,1,0 +204,291,2823762,5,0 +239,326,2823874,1,0 +274,361,2823985,2,0,B|398:345,1,100 +421,363,2824321,1,0 +468,379,2824433,1,0 +462,279,2824657,5,0 +437,235,2824769,1,0 +392,211,2824881,2,0,B|379:150|437:120,1,100 +471,99,2825217,1,0 +480,49,2825329,1,0 +387,10,2825553,5,0 +337,16,2825665,1,0 +288,27,2825777,2,0,B|282:137|177:182,1,200 +235,263,2826448,1,0 +134,263,2826672,2,0,B|109:321|132:372,1,100,4|0 +127,360,2827008,1,0 +127,360,2827120,1,0 +225,343,2827344,5,0 +321,367,2827568,1,0 +338,268,2827791,1,0 +338,268,2827903,1,0 +338,268,2828015,2,0,B|479:251,1,100 +373,124,2828463,6,0,B|273:135,1,100 +189,80,2828911,1,0 +149,111,2829023,1,0 +139,160,2829135,1,0 +157,206,2829247,1,0 +196,236,2829359,1,0 +245,243,2829471,1,0 +290,262,2829582,1,0 +316,304,2829694,1,0 +307,353,2829806,1,0 +263,377,2829918,1,0 +213,381,2830030,1,0 +166,361,2830142,1,0 +116,366,2830254,1,4 +48,296,2830478,5,0 +17,256,2830590,1,0 +19,206,2830702,1,0 +54,168,2830814,1,0 +100,160,2830926,1,0 +145,180,2831038,1,0 +194,188,2831150,1,0 +243,181,2831262,1,0 +285,154,2831374,1,0 +302,107,2831485,1,0 +291,58,2831597,1,0 +255,22,2831709,1,0 +205,14,2831821,1,0 +160,36,2831933,1,0 +136,80,2832045,1,0 +194,188,2832269,5,0 +226,222,2832381,1,0 +232,273,2832493,1,0 +204,314,2832605,1,0 +155,328,2832717,1,0 +101,243,2832941,1,0 +53,227,2833053,1,0 +28,183,2833165,1,0 +112,132,2833388,1,0 +130,87,2833500,1,0 +176,62,2833612,1,0 +224,71,2833724,1,0 +255,111,2833836,1,0 +340,144,2834060,5,0 +390,145,2834172,1,0 +434,121,2834284,1,0 +492,202,2834508,2,0,B|456:227|438:286|453:314,1,100 +360,348,2834956,1,0 +360,348,2835068,1,0 +360,348,2835180,1,0 +296,288,2835403,2,0,B|288:312|256:320|224:312|216:288,1,100 +152,348,2835851,1,0 +152,348,2835963,1,0 +152,348,2836075,1,0 +256,208,2836299,5,0 +170,101,2836523,2,0,B|203:58|256:44|312:56|344:104,3,200,0|0|4|0 +404,192,2838090,5,0 +384,200,2838202,1,0 +368,212,2838314,1,0 +356,228,2838426,1,0 +348,248,2838538,1,0 +344,268,2838650,1,0 +348,288,2838762,1,0 +356,308,2838874,1,0 +368,324,2838985,1,0 +384,336,2839097,1,0 +404,344,2839209,1,0 +256,196,2839657,5,0 +108,192,2839881,5,0 +128,200,2839993,1,0 +144,212,2840105,1,0 +156,228,2840217,1,0 +164,248,2840329,1,0 +168,268,2840441,1,0 +164,288,2840553,1,0 +156,308,2840665,1,0 +144,324,2840776,1,0 +128,336,2840888,1,0 +108,344,2841000,1,0 +216,124,2841448,6,0,B|224:148|256:156|288:148|296:124,1,100 +296,123,2841784,1,0 +296,123,2841896,1,0 +256,36,2842120,1,0 +216,260,2842344,6,0,B|224:236|256:228|288:236|296:260,1,100 +256,348,2842791,1,0 +256,192,2842847,12,4,2843687 +256,192,2843799,12,0,2844582 +176,104,2845030,5,0 +256,48,2845254,1,0 +336,104,2845478,1,0 +308,200,2845702,1,0 +204,200,2845926,1,0 +183,297,2846150,2,0,B|198:352|258:373|316:351|333:294,1,200,0|0 +424,344,2846821,6,0,B|448:296|448:296|496:280,1,100 +424,208,2847269,1,0 +456,112,2847493,1,0 +372,48,2847717,1,0 +293,106,2847941,2,0,B|316:139|297:184|256:197|223:189|195:157|201:126|218:105|218:105,1,200,0|0 +140,48,2848612,1,0 +56,112,2848836,1,0 +88,208,2849060,1,0 +256,352,2849508,1,0 +338,300,2849732,2,0,B|310:254|257:240|202:251|172:305,1,200 +120,216,2850403,5,0 +208,168,2850627,1,0 +256,156,2850739,1,0 +304,168,2850851,1,0 +392,216,2851075,1,0 +396,116,2851299,1,0 +304,56,2851523,1,0 +256,44,2851635,1,0 +208,56,2851747,1,4 +116,116,2851971,1,0 +120,216,2852194,5,0 +208,268,2852418,2,0,B|184:276|176:308|184:340|208:348,1,100 +303,348,2852866,2,0,B|328:340|336:308|328:276|304:268,1,100 +392,216,2853314,6,0,B|336:188|332:132|332:132|384:121|408:60,1,200 +308,32,2853985,1,0 +204,32,2854209,1,0 +111,75,2854433,2,0,B|134:120|180:132|180:132|176:188|120:216,1,200 +216,280,2855105,2,0,B|227:258|255:252|255:252|283:258|295:280,1,100 +256,192,2855385,12,0,2856224 +256,192,2856280,12,0,2857120 +333,71,2857568,6,0,B|328:134|394:164,1,100 +179,313,2858015,2,0,B|184:250|118:220,1,100 +96,197,2858351,1,0 +74,151,2858463,1,0 +87,102,2858575,1,0 +129,75,2858687,1,0 +178,79,2858799,1,0 +218,108,2858911,2,0,B|298:184|412:151,1,200,4|0 +163,154,2859582,6,0,B|192:206|164:266,2,100 +93,81,2860254,1,0 +23,153,2860478,2,0,B|-22:249|34:367,1,200 +308,324,2861150,6,0,B|210:318,2,100 +353,234,2861821,2,0,B|362:164|297:140,1,100 +247,222,2862269,2,0,B|41:209,1,200 +126,25,2862941,6,0,B|102:71|96:129,1,100 +278,101,2863388,2,0,B|293:199|248:304,1,200 +428,259,2864060,2,0,B|404:145|420:45,1,200 +243,29,2864732,6,0,B|369:47,2,100 +143,15,2865403,1,0 +107,50,2865515,1,0 +105,100,2865627,1,0 +138,137,2865739,1,0 +184,155,2865851,1,0 +216,193,2865963,1,0 +227,241,2866075,2,0,B|261:337|415:331,1,200,4|0 +359,235,2866747,2,0,B|395:120,1,100 +474,190,2867194,2,0,B|510:75,1,100 +269,128,2867642,2,0,B|202:341,1,200 +460,196,2868314,6,0,B|496:81,1,100 +255,134,2868762,2,0,B|188:347,1,200 +195,324,2869433,1,0 +256,192,2869545,12,0,2873239 +256,96,2873463,5,0 +256,96,2873575,1,0 +256,96,2873687,1,0 +256,96,2873911,1,0 +256,96,2874135,1,0 +256,288,2874359,5,0 +256,288,2874471,1,0 +256,288,2874582,1,0 +256,288,2874806,1,0 +256,288,2875030,1,0 +352,192,2875254,5,0 +352,192,2875366,1,0 +352,192,2875478,1,0 +352,192,2875702,1,0 +352,192,2875926,1,0 +160,192,2876150,5,0 +160,192,2876262,1,0 +160,192,2876374,1,0 +160,192,2876597,1,0 +160,192,2876821,1,0 +296,56,2877045,5,0 +304,56,2877157,1,0 +312,56,2877269,1,0 +216,56,2877493,1,0 +200,56,2877717,1,0 +216,328,2877941,5,0 +208,328,2878053,1,0 +200,328,2878165,1,0 +296,328,2878389,1,0 +312,328,2878613,1,0 +256,192,2878724,12,0,2880403 +392,128,2880627,5,0 +392,128,2880739,1,0 +392,128,2880851,1,0 +392,248,2881075,1,0 +392,128,2881299,1,0 +120,256,2881523,5,0 +120,256,2881635,1,0 +120,256,2881747,1,0 +120,136,2881971,1,0 +120,256,2882194,1,0 +320,328,2882418,5,0 +320,328,2882530,1,0 +320,328,2882642,1,0 +200,328,2882866,1,0 +320,328,2883090,1,0 +192,56,2883314,5,0 +192,56,2883426,1,0 +192,56,2883538,1,0 +312,56,2883762,1,0 +192,56,2883985,1,0 +256,192,2884209,5,0 +256,192,2884321,1,0 +256,192,2884433,1,0 +256,192,2884657,1,0 +256,192,2884881,1,0 +256,192,2885105,5,0 +256,192,2885217,1,0 +256,192,2885329,1,0 +256,192,2885553,1,0 +256,192,2885777,1,0 +64,344,2886224,5,0 +96,344,2886336,1,0 +128,344,2886448,1,0 +160,344,2886560,1,0 +192,344,2886672,1,0 +224,344,2886784,1,0 +256,344,2886896,1,0 +288,344,2887008,1,0 +320,344,2887120,1,0 +352,344,2887232,1,0 +384,344,2887344,1,0 +416,344,2887456,1,0 +448,344,2887568,1,0 +256,192,2887679,12,0,2889359 +176,192,2901769,6,0,B|196:288|272:340|364:360,1,240.000005722046 +332,84,2903251,1,0 +428,136,2903621,1,2 +336,192,2903991,1,0 +428,252,2904362,1,2 +336,192,2904732,6,0,B|316:288|240:340|148:360,1,240.000005722046 +180,84,2906214,1,0 +84,136,2906584,1,2 +176,192,2906954,1,0 +84,252,2907325,1,2 +176,192,2907695,6,0,B|196:96|272:44|364:24,1,240.000005722046 +332,300,2909176,1,0 +428,248,2909547,1,2 +336,192,2909917,1,0 +428,132,2910288,1,2 +336,192,2910658,6,0,B|316:96|240:44|148:24,1,240.000005722046 +180,324,2912139,1,0 +332,324,2912510,1,2 +176,192,2912880,1,0 +204,236,2913065,1,0 +256,248,2913251,1,2 +308,236,2913436,1,0 +336,192,2913621,5,0 +76,324,2914362,1,0 +236,96,2915102,1,0 +340,128,2915473,2,0,B|308:164|304:200|352:248,1,120.000002861023,2|0 +240,276,2916214,1,2 +176,192,2916584,5,0 +436,324,2917325,1,0 +276,96,2918065,1,0 +172,128,2918436,2,0,B|204:164|208:200|160:248,1,120.000002861023,2|0 +272,276,2919176,1,2 +336,192,2919547,5,0 +76,60,2920288,1,0 +236,288,2921028,1,0 +340,256,2921399,2,0,B|308:220|304:184|352:136,1,120.000002861023,2|0 +240,108,2922139,1,2 +176,192,2922510,5,0 +436,60,2923251,1,0 +316,312,2923991,1,0 +228,296,2924362,2,0,B|228:280|228:280|228:208|280:208|280:120|280:120|280:104,1,200 +196,88,2925473,1,0 +174,95,2940288,6,0,B|194:52|255:27|316:48|338:97,2,200,2|8|2 +256,144,2942139,1,0 +208,224,2942510,1,8 +304,224,2942880,1,0 +347,302,2943251,2,0,B|308:333|270:317|255:302|255:302|240:317|199:333|164:302,1,200,2|8 +88,155,2944732,5,2 +68,67,2945102,1,0 +149,28,2945473,1,8 +210,93,2945843,1,0 +181,184,2946214,2,0,B|191:220|255:244|255:244|319:220|333:183,1,200,2|8 +424,229,2947695,5,2 +444,317,2948065,1,0 +363,356,2948436,1,8 +302,291,2948806,1,0 +331,200,2949176,2,0,B|321:164|257:140|257:140|194:160|181:202,1,200,2|8 +352,88,2950658,1,2 +256,48,2951028,1,0 +152,88,2951399,1,8 +44,56,2951769,21,8 +44,56,2951954,1,0 +44,56,2952139,1,4 +160,348,2952880,1,8 +36,172,2953436,1,0 +36,172,2953621,1,0 +192,140,2953991,1,0 +320,244,2954362,1,8 +476,212,2954732,1,0 +476,212,2954917,2,0,B|424:8|112:76|168:296,1,500,0|8 +320,244,2956214,1,0 +476,212,2956584,5,0 +320,244,2956954,1,0 +192,140,2957325,1,8 +36,172,2957695,1,0 +36,172,2957880,2,0,B|88:376|400:308|344:88,1,500,0|8 +192,140,2959176,1,0 +112,276,2959547,5,0 +256,344,2959917,1,0 +400,276,2960288,1,8 +340,136,2960658,2,0,B|364:66|291:34|256:69|256:69|221:34|148:66|172:136,2,299.999991059304 +460,270,2962510,1,0 +421,325,2962695,1,0 +360,300,2962880,1,0 +256,256,2963251,1,8 +152,300,2963621,1,0 +91,325,2963806,1,0 +52,270,2963991,5,0 +352,56,2964732,1,8 +116,92,2965288,1,0 +116,92,2965473,1,0 +176,240,2965843,1,0 +336,240,2966214,1,8 +396,92,2966584,1,0 +396,92,2966769,2,0,B|492:128|532:196|532:304|448:392|348:384|260:320,1,500,0|8 +396,92,2968065,5,0 +396,92,2968251,1,0 +396,92,2968436,1,0 +336,240,2968806,1,0 +176,240,2969176,1,8 +116,92,2969547,1,0 +116,92,2969732,2,0,B|20:128|-20:196|-20:304|64:392|164:384|252:320,1,500,0|8 +256,36,2971399,5,0 +176,172,2971769,1,0 +336,172,2972139,1,8 +432,300,2972510,2,0,B|356:399|257:327|257:327|158:399|83:300,1,400 +256,236,2973621,1,8 +432,172,2973991,2,0,B|356:73|257:144|257:144|158:73|83:172,1,400 +256,236,2975102,1,8 +256,60,2975473,1,8 +256,60,2975658,1,0 +256,60,2975843,5,4 +378,372,2976584,2,0,B|348:306|260:264|172:297|136:371,1,300,8|0 +156,204,2977325,2,0,B|372:204,1,200,8|8 +296,32,2978065,2,0,B|314:70|296:101|268:115|240:115|212:101|194:70|212:32,1,200,8|0 +126,127,2978621,2,0,B|174:192|256:213|337:192|385:127,1,300 +432,304,2979547,5,8 +256,352,2979917,1,0 +80,304,2980288,1,8 +181,159,2980658,2,0,B|191:195|255:219|255:219|319:195|333:158,1,200,0|8 +127,79,2981399,2,0,B|143:14|207:-1|272:14|304:46|288:95|256:111|223:95|207:46|239:14|304:-1|368:14|384:79,1,400 +480,224,2982510,5,8 +360,322,2982880,2,0,B|325:339|290:339|256:322|221:304|204:252|221:201|256:183|290:201|308:252|290:304|256:322|221:339|187:339|152:322,1,400 +32,224,2983991,1,8 +173,146,2984362,2,0,B|181:115|213:90|254:84|254:84|281:74|303:41|281:9|257:3|229:9|201:41|227:74|254:84|254:84|297:90|324:111|338:152,1,400 +448,288,2985473,1,8 +256,368,2985843,5,8 +184,312,2986028,1,8 +208,224,2986214,1,0 +304,224,2986399,1,8 +328,312,2986584,1,8 +334,99,2986954,2,0,B|317:52|255:28|199:50|176:101,1,200,4|0 +256,136,2987510,1,0 +256,136,2987695,5,4 +256,352,2988436,1,8 +412,108,2988991,1,0 +412,194,2989176,2,0,B|362:199|313:154|308:94|344:51,1,200,8|0 +167,51,2989917,2,0,B|203:94|198:154|149:199|100:194,1,200,8|0 +100,108,2990473,1,0 +256,204,2990843,1,0 +330,254,2991028,2,0,B|320:290|256:314|256:314|192:290|178:253,1,200,0|8 +364,344,2991769,5,0 +420,140,2992139,1,0 +256,48,2992510,1,0 +92,140,2992880,1,8 +148,344,2993251,1,0 +256,48,2993991,5,0 +432,336,2994732,1,0 +80,332,2995473,1,0 +256,304,2995843,1,8 +172,152,2996214,2,0,B|136:76|172:14|227:-12|283:-12|338:14|374:76|338:152,1,400 +472,276,2997325,1,8 +311,381,2997695,2,0,B|311:325|255:307|255:307|200:288|200:233,1,200,0|8 +256,168,2998251,1,8 +311,232,2998436,2,0,B|311:288|256:307|256:307|201:325|201:381,1,200,8|8 +39,276,2999176,1,8 +39,276,2999362,1,8 +39,276,2999547,1,4 +256,192,2999639,12,8,3001769 +256,192,3002510,5,4 +468,56,3008436,5,4 +352,348,3009176,1,8 +476,172,3009732,1,0 +476,172,3009917,1,0 +320,140,3010288,1,0 +192,244,3010658,1,8 +36,212,3011028,1,0 +36,212,3011214,2,0,B|88:8|400:76|344:296,1,500,0|8 +36,212,3012880,5,0 +192,244,3013251,1,0 +320,140,3013621,1,8 +476,172,3013991,1,0 +476,172,3014176,2,0,B|424:376|112:308|168:88,1,500,0|8 +320,140,3015473,1,0 +400,276,3015843,5,0 +256,344,3016214,1,0 +112,276,3016584,1,8 +172,136,3016954,2,0,B|148:66|221:34|256:69|256:69|291:34|364:66|340:136,2,299.999991059304 +256,272,3018806,1,0 +412,300,3019176,1,0 +256,272,3019547,1,8 +100,300,3019917,1,0 +100,300,3020102,1,0 +100,300,3020288,5,0 +376,99,3021028,2,0,B|349:30|255:-8|170:25|135:103,1,300,8|0 +176,180,3021769,2,0,B|193:227|255:251|311:229|334:178,1,200,0|0 +488,264,3022510,1,8 +336,348,3022880,1,0 +256,372,3023065,1,0 +176,348,3023251,1,0 +256,52,3023991,5,8 +24,264,3024547,1,0 +24,264,3024732,1,0 +120,112,3025102,1,0 +256,228,3025473,1,8 +392,112,3025843,1,0 +336,180,3026028,2,0,B|308:136|239:99|166:123|80:208|151:317|208:352|279:356|344:292|344:245,1,500,0|8 +448,352,3027325,5,0 +255,229,3027695,1,0 +64,352,3028065,1,0 +32,128,3028436,5,8 +173,13,3028806,2,0,B|137:89|173:151|228:177|284:177|339:151|375:89|339:13,1,400 +480,128,3029917,1,8 +352,288,3030288,5,0 +160,288,3030658,1,0 +336,184,3031028,1,8 +256,216,3031214,1,0 +176,184,3031399,1,8 +304,96,3031769,21,8 +208,96,3031954,1,0 +256,32,3032139,1,4 +256,32,3032880,37,8 +48,280,3033436,1,0 +48,280,3033621,1,0 +192,212,3033991,1,0 +328,292,3034362,1,8 +464,216,3034732,1,0 +464,216,3034917,2,0,B|397:316|291:313|235:285|185:249|132:157|191:49,1,500,0|8 +320,48,3036214,1,0 +256,196,3036584,5,0 +320,336,3036954,1,0 +416,208,3037325,1,8 +256,195,3037695,2,0,B|122:154|122:154|189:218|155:337|25:311,2,400 +256,196,3039917,1,0 +256,36,3040288,1,8 +256,195,3040658,2,0,B|389:154|389:154|322:218|356:337|486:311,1,400 +476,152,3041769,1,8 +352,52,3042139,5,0 +316,108,3042325,1,0 +256,136,3042510,1,0 +196,108,3042695,1,8 +160,52,3042880,1,0 +256,232,3043251,1,8 +256,40,3043621,1,0 +256,40,3043806,1,0 +256,40,3043991,5,4 +256,40,3044732,1,8 +464,280,3045288,1,0 +464,280,3045473,1,0 +320,212,3045843,1,0 +184,292,3046214,1,8 +48,216,3046584,1,0 +48,216,3046769,2,0,B|114:316|220:313|276:285|326:249|379:157|320:49,1,500,0|8 +192,48,3048065,1,0 +256,196,3048436,5,0 +192,336,3048806,1,0 +96,208,3049176,1,8 +256,196,3049547,2,0,B|389:236|389:236|322:173|356:54|486:80,2,400 +256,196,3051769,1,0 +256,355,3052139,1,8 +256,196,3052510,2,0,B|122:236|122:236|189:173|155:54|25:80,1,400 +36,239,3053621,1,8 +160,339,3053991,5,0 +196,283,3054176,1,0 +256,255,3054362,1,0 +316,283,3054547,1,8 +352,339,3054732,1,0 +256,159,3055102,1,8 +256,351,3055473,1,0 +256,351,3055658,1,0 +256,351,3055843,5,4 +256,36,3056584,1,8 +256,260,3057139,1,0 +256,260,3057325,2,0,B|256:112,1,132.00000125885 +312,96,3057880,1,0 +316,160,3058065,1,0 +256,188,3058251,1,0 +196,160,3058436,1,0 +200,96,3058621,1,0 +256,64,3058806,5,4 +332,348,3059547,2,0,B|289:366|256:333|256:308|256:308|256:333|222:366|181:348,1,200,8|0 +80,192,3060288,1,8 +256,88,3060658,1,0 +432,192,3061028,1,8 +340,39,3061399,2,0,B|376:115|340:177|285:203|229:203|174:177|138:115|174:39,2,400 +432,192,3063251,5,8 +256,296,3063621,1,0 +80,192,3063991,1,8 +172,345,3064362,2,0,B|136:269|172:207|227:181|283:181|338:207|374:269|338:345,2,400 +356,48,3066584,6,0,B|156:48,1,200,0|8 +52,196,3067325,1,0 +174,338,3067695,2,0,B|321:183|321:183|256:126|256:126|190:183|190:183|337:338,1,600,8|0 +464,192,3069176,1,8 +464,192,3069362,1,8 +464,192,3069547,1,0 +341,61,3069917,6,0,B|309:102|257:126|200:102|170:59,1,200,8|0 +19,154,3070658,2,0,B|49:231|123:301|196:315|255:330|314:315|392:300|461:231|492:152,1,600,4|0 +480,336,3072139,5,8 +384,80,3072510,1,0 +256,288,3072880,1,8 +128,80,3073251,1,0 +32,336,3073621,1,8 +97,274,3073806,1,0 +171,219,3073991,2,0,B|202:175|257:161|311:175|342:219,1,200,0|8 +344,39,3074732,6,0,B|311:82|258:96|203:82|171:39,1,200,0|8 +160,224,3075473,1,0 +352,224,3075843,1,8 +256,352,3076214,1,0 +256,352,3076954,5,0 +256,192,3077325,1,8 +120,76,3077695,2,0,B|147:143|226:120|256:102|256:37|256:37|256:102|292:120|368:144|400:65,1,400 +376,248,3078806,5,8 +256,344,3079176,1,0 +256,140,3079547,1,0 +136,248,3079917,1,0 +256,44,3080288,1,8 +376,248,3080658,1,0 +340,172,3080843,1,8 +256,140,3081028,1,8 +172,172,3081214,1,8 +136,248,3081399,1,8 +256,344,3081769,21,8 +488,104,3082510,2,0,B|500:97|521:64|516:9|478:-2|456:-23|369:-26|316:12|357:88|357:122|390:190|423:190|491:190|495:255|495:288|495:288|496:323|465:380|423:389|358:386|328:339|323:322|323:289|325:257|345:218|345:218|56:190|256:-177|457:190|157:221|157:221|189:250|198:289|190:322|190:338|156:389|90:389|51:373|21:337|19:289|19:289|20:240|33:208|90:190|123:190|156:122|156:88|169:17|140:-9|52:-29|18:-4|-9:55|4:88|27:110,1,2200,4|0 +72,192,3103251,5,0 +168,332,3103621,1,0 +344,332,3103991,1,0 +440,192,3104362,1,0 +256,148,3104732,1,0 +256,148,3105102,1,0 +168,296,3105473,5,0 +200,348,3105658,1,0 +256,364,3105843,1,0 +312,348,3106028,1,0 +344,296,3106214,2,0,B|272:208|348:88|472:128,1,264.0000025177 +500,268,3107510,1,0 +500,268,3107695,1,0 +400,236,3108065,1,0 +300,204,3108436,1,0 +300,100,3108806,1,0 +168,88,3109176,6,0,B|240:176|164:296|40:256,1,264.0000025177 +12,116,3110473,1,0 +12,116,3110658,1,0 +112,148,3111028,1,0 +212,180,3111399,1,0 +212,284,3111769,1,0 +344,288,3112139,6,0,B|410:277|421:196|403:192|403:192|421:189|410:108|344:97,2,264.0000025177 +256,192,3113991,1,0 +168,96,3114362,2,0,B|101:106|90:187|108:191|108:191|90:194|101:275|168:286,1,264.0000025177 +344,284,3115473,1,0 +256,192,3115843,1,0 +168,100,3116214,1,0 +196,44,3116399,1,0 +256,20,3116584,1,0 +316,44,3116769,1,0 +344,100,3116954,1,0 +256,192,3117325,1,0 +168,300,3117695,1,0 +168,300,3117880,1,0 +168,300,3118065,6,0,B|208:344|312:364|448:344|444:164|432:108|372:56|288:-28|144:4|64:152|140:248,2,800,4|8|8 +256,192,3121121,12,8,3122510 +256,192,3122602,12,8,3123991 +256,192,3124084,12,8,3126214 +108,240,3126584,5,0 +168,72,3126954,1,0 +348,72,3127325,1,0 +404,240,3127695,1,8 +256,360,3128065,1,8 +192,312,3128251,1,8 +212,232,3128436,1,8 +300,232,3128621,1,0 +320,312,3128806,1,8 +344,56,3129176,6,0,B|340:104|340:104|288:136,1,100,8|0 +216,131,3129547,2,0,B|171:103|171:103|167:55,1,100,8|0 +256,44,3129917,5,4 +256,44,3130658,1,8 +464,280,3131214,1,0 +464,280,3131399,1,0 +320,212,3131769,1,0 +184,292,3132139,1,8 +48,216,3132510,1,0 +48,216,3132695,2,0,B|115:316|221:313|277:285|327:249|380:157|321:49,1,500,0|8 +192,48,3133991,1,0 +256,196,3134362,5,0 +192,336,3134732,1,0 +96,208,3135102,1,8 +256,195,3135473,2,0,B|390:154|390:154|323:218|357:337|487:311,2,400 +256,196,3137695,5,0 +256,36,3138065,1,8 +256,195,3138436,2,0,B|123:154|123:154|190:218|156:337|26:311,1,400 +36,152,3139547,1,8 +160,52,3139917,1,0 +196,108,3140102,1,0 +256,136,3140288,1,0 +316,108,3140473,1,8 +352,52,3140658,1,0 +256,232,3141028,5,8 +256,40,3141399,1,0 +256,40,3141584,1,0 +256,40,3141769,1,4 +378,372,3142510,6,0,B|348:306|260:264|172:297|136:371,1,300,8|0 +156,204,3143251,2,0,B|372:204,1,200,8|8 +296,32,3143991,2,0,B|314:70|296:101|268:115|240:115|212:101|194:70|212:32,1,200,8|0 +126,127,3144547,2,0,B|174:192|256:213|337:192|385:127,1,300 +428,304,3145473,5,8 +256,364,3145843,1,0 +84,304,3146214,1,0 +332,212,3146584,6,0,B|322:176|258:152|258:152|192:172|180:213,1,200,0|8 +104,72,3147325,2,0,B|189:109|256:42|256:-7|256:-7|256:42|322:109|406:72,1,400 +480,224,3148436,1,8 +360,322,3148806,2,0,B|325:339|290:339|256:322|221:304|204:252|221:201|256:183|290:201|308:252|290:304|256:322|221:339|187:339|152:322,1,400 +32,224,3149917,5,8 +173,146,3150288,2,0,B|181:115|213:90|254:84|254:84|281:74|303:41|281:9|257:3|229:9|201:41|227:74|254:84|254:84|297:90|324:111|338:152,1,400 +492,232,3151399,1,8 +352,344,3151769,2,0,B|312:312|312:312|304:264,1,100,8|0 +392,256,3152139,2,0,B|384:208|384:208|344:176,1,100,8|0 +328,88,3152510,1,8 +256,144,3152695,1,0 +184,88,3152880,1,8 +168,176,3153065,2,0,B|128:208|128:208|120:256,1,100,0|8 +208,264,3153436,2,0,B|200:312|200:312|160:344,1,100,0|4 +160,56,3154362,5,8 +396,92,3154917,1,0 +396,92,3155102,1,0 +336,240,3155473,1,0 +176,240,3155843,1,8 +116,91,3156214,2,0,B|51:116|28:160|20:208|72:284|148:287|204:212,1,300 +176,240,3156954,1,0 +288,116,3157325,1,8 +116,92,3157695,5,0 +116,92,3157880,1,0 +116,92,3158065,1,0 +176,240,3158436,1,0 +336,240,3158806,1,8 +396,91,3159176,2,0,B|460:116|484:160|492:208|440:284|363:287|308:212,1,300 +336,240,3159917,2,0,B|372:288|288:332|256:256|256:256|224:332|140:288|176:240,1,300 +175,240,3160658,1,0 +256,80,3161028,5,0 +388,320,3161399,1,0 +124,320,3161769,1,8 +96,104,3162139,2,0,B|132:10|259:-40|374:4|421:109,1,400 +256,192,3163251,1,8 +96,280,3163621,2,0,B|132:373|259:424|374:379|421:274,1,400 +366,112,3164732,6,0,B|320:92|320:92|300:48,1,100,8|0 +212,46,3165102,2,0,B|192:92|192:92|146:112,1,100,8|0 +207,184,3165473,2,0,B|146:207|146:207|67:176|67:176|99:348|99:348|177:317|177:317|224:348|224:348|256:333|256:333|287:348|287:348|334:317|334:317|412:348|412:348|444:176|444:176|365:207|365:207|303:184,1,1000,4|0 +256,24,3167695,5,8 +396,91,3168065,2,0,B|461:116|484:160|492:208|440:284|364:287|308:212,1,300 +336,240,3168806,1,0 +224,116,3169176,1,8 +396,92,3169547,5,0 +396,92,3169732,1,0 +396,92,3169917,1,0 +336,240,3170288,1,0 +176,240,3170658,1,8 +116,91,3171028,2,0,B|52:116|28:160|20:208|72:284|149:287|204:212,1,300 +176,240,3171769,2,0,B|140:288|224:332|256:256|256:256|288:332|372:288|336:240,1,300 +337,240,3172510,1,0 +256,80,3172880,5,0 +124,320,3173251,1,0 +388,320,3173621,1,8 +416,104,3173991,2,0,B|380:10|253:-40|138:4|91:109,1,400 +256,192,3175102,1,8 +104,304,3175473,2,0,B|189:267|256:334|256:383|256:383|256:334|322:267|406:304,1,400,8|0 +386,195,3176460,5,0 +309,126,3176583,2,0,B|341:62,2,66.6666666666667,8|0|8 +203,126,3176955,2,0,B|171:62,2,66.6666666666667,8|0|8 +126,195,3177325,1,4 diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 60ce67c7f6..3a15b5e6b1 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -30,6 +30,9 @@ false + + $(SolutionDir)\packages\DeepEqual.1.6.0.0\lib\net40\DeepEqual.dll + $(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll True @@ -147,6 +150,7 @@ + \ No newline at end of file diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index ecc44f0c70..e09f2a07ba 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -4,6 +4,7 @@ Copyright (c) 2007-2017 ppy Pty Ltd . Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE --> + From 851c20aff0be3c55fc84c47d8b137de3cbc59222 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 11:17:32 +0900 Subject: [PATCH 042/239] Add a few comments --- osu.Game/Audio/SampleInfo.cs | 4 ++++ osu.Game/IO/Serialization/IJsonSerializable.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index f8b5bf33d9..3c3a64dbb2 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -15,6 +15,10 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; + /// + /// The that is used for and + /// if the values have not already been provided by the hitobject. + /// [JsonIgnore] public SoundControlPoint ControlPoint; diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 8d10f0b291..38e7f47656 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -20,6 +20,10 @@ namespace osu.Game.IO.Serialization public static T DeepClone(this T obj) where T : IJsonSerializable => Deserialize(Serialize(obj)); + /// + /// Creates the default that should be used for all s. + /// + /// public static JsonSerializerSettings CreateGlobalSettings() => new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore, From b0684cb194c11a24e6293e38bee98d3f4fc94191 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:02:34 +0900 Subject: [PATCH 043/239] Add storyboard test case but disable for now --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 23 +- ...ow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu | 1231 +++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + 3 files changed, 1245 insertions(+), 10 deletions(-) create mode 100644 osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index c3ebcb9e7c..5a66a7047d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -19,13 +19,14 @@ namespace osu.Game.Tests.Beatmaps.Formats [TestFixture] public class OsuJsonDecoderTest { - private const string beatmap_1 = "Soleily - Renatus (Gamu) [Insane].osu"; - private const string beatmap_2 = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; + private const string normal = "Soleily - Renatus (Gamu) [Insane].osu"; + private const string marathon = "Within Temptation - The Unforgiving (Armin) [Marathon].osu"; + private const string with_sb = "Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu"; [Test] public void TestDecodeMetadata() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); var meta = beatmap.BeatmapInfo.Metadata; Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual("Soleily", meta.Artist); @@ -43,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeGeneral() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmapInfo.AudioLeadIn); Assert.AreEqual(false, beatmapInfo.Countdown); @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeEditor() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; int[] expectedBookmarks = @@ -78,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeDifficulty() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); @@ -91,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeColors() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); Color4[] expected = { new Color4(142, 199, 255, 255), @@ -109,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeHitObjects() { - var beatmap = decodeAsJson(beatmap_1); + var beatmap = decodeAsJson(normal); var curveData = beatmap.HitObjects[0] as IHasCurve; var positionData = beatmap.HitObjects[0] as IHasPosition; @@ -128,8 +129,10 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); } - [TestCase(beatmap_1)] - [TestCase(beatmap_2)] + [TestCase(normal)] + [TestCase(marathon)] + // Currently fails: + // [TestCase(with_sb)] public void TestParity(string beatmap) { var beatmaps = decode(beatmap); diff --git a/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu b/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu new file mode 100644 index 0000000000..38db42fddc --- /dev/null +++ b/osu.Game.Tests/Resources/Kozato snow - Rengetsu Ouka (_Kiva) [Yuki YukI].osu @@ -0,0 +1,1231 @@ +osu file format v9 + +[General] +AudioFilename: Kozato snow.mp3 +AudioLeadIn: 1000 +PreviewTime: 173263 +Countdown: 0 +SampleSet: Soft +StackLeniency: 0.5 +Mode: 0 +LetterboxInBreaks: 0 + +[Editor] +Bookmarks: 14893,27933,40766,54427,67053,80300,94167,107414,120247,133919,147166,160424,173673,187336,200172 +DistanceSpacing: 0.5 +BeatDivisor: 4 +GridSize: 8 + +[Metadata] +Title:Rengetsu Ouka +Artist:Kozato snow +Creator:_Kiva +Version:Yuki YukI +Source: +Tags:Kiva Snowy Dream Yumeko Yuki HakuNoKaemi wmfchris aabc271 + +[Difficulty] +HPDrainRate:8 +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8 +SliderMultiplier:1.8 +SliderTickRate:0.5 + +[Events] +//Background and Video events +0,0,"BG_example.JPG" +//Break Periods +2,54644,66322 +2,67893,79572 +2,107641,119526 +2,160627,172513 +//Storyboard Layer 0 (Background) +Sprite,Background,Centre,"SB\bg2.png",320,240 + S,0,26288,26495,1,1.04096 + F,0,26288,27530,1,0 + S,0,26495,27530,1.04096,1.26624 +Sprite,Background,Centre,"SB\C32.png",320,240 + M,0,41193,54436,259,283,320,240 + F,0,41193,54436,1 + S,0,41193,54436,1.2048,1.13312 + R,0,41193,54436,0,0.1023999 + F,0,54436,55264,1,0 + S,0,54436,55264,1.13312,1.29696 +Sprite,Background,Centre,"SB\bg1.png",320,240 + F,0,27944,,1 + S,0,27944,41193,1,1.327681 + S,0,41193,,1.327681 +Sprite,Background,Centre,"SB\bg3.png",320,240 + F,0,160427,162083,0,1 + S,0,160427,162083,1 + S,0,162083,166829,1,1.035869 + F,0,162083,173677,1,0.9987923 + S,0,166829,173677,1.035869,1.11264 + F,0,173677,173780,0.9987923,0.8605523 +//Storyboard Layer 1 (Fail) +//Storyboard Layer 2 (Pass) +//Storyboard Layer 3 (Foreground) +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,1445,2687,0.8,0 + S,0,1445,2687,1,1.16 +Sprite,Foreground,Centre,"SB\Mini Light.png",320,240 + M,0,24631,,140,147 + S,0,24631,24838,0.19104,0.488 + F,0,24631,25045,1,0 + S,0,24838,25045,0.488,0.5903998 +Sprite,Foreground,Centre,"SB\MINI SNOW.png",320,240 + M,0,25045,,472,224 + S,0,25045,25252,0.32416,0.5392001 + F,0,25045,25459,1,0 + S,0,25252,25459,0.5392001,0.6518402 +Sprite,Foreground,Centre,"SB\black.png",320,240 + F,0,26702,27944,0,1 + F,0,27944,28358,1,0.55296 + F,0,28358,28772,0.55296,0.005119934 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25459,,193,293 + F,0,25459,25666,1,0 + S,0,25459,25666,0.61088,1.24576 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25666,,387,373 + P,0,25666,,H + V,0,25666,,1.06144,1 + V,0,25666,,1.06144,1 + F,0,25666,25873,1,0 + S,0,25666,25873,0.6313599,1.4096 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + M,0,25873,,417,134 + M,0,25873,,417,134 + P,0,25873,,V + F,0,25873,26080,1,0 + S,0,25873,26080,1,1.52224 +Sprite,Foreground,Centre,"SB\OP effect.png",320,240 + F,0,26080,26288,1,0 + S,0,26080,26288,1,1.54272 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + S,0,26288,26391,0.3,0.7 + F,0,26288,27116,1 + S,0,26391,27530,0.7,0.9 + F,0,27116,27530,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,26288,,529,377 + P,0,26288,,H + S,0,26288,26391,0.4,0.55 + F,0,26288,26702,1,0 + S,0,26391,26702,0.55,0.9 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,26288,,202,210 + P,0,26288,,H + P,0,26288,,V + S,0,26288,26391,0.4,0.64 + F,0,26288,26702,1,0 + S,0,26391,26702,0.64,0.7 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,40779,41193,0,1 + F,0,41193,41814,1,0 +Sprite,Foreground,Centre,"SB\sakura_b",320,240 + F,0,54436,55264,0,1 + R,0,54436,61060,0.2252804,0 + S,0,54443,,1.5625 + M,0,54443,61060,378,177,304,342 + F,0,55264,59404,1 + F,0,59404,61060,1,0 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,61060,61267,0,0.7122133 + S,0,61060,62302,1,1.1024 + F,0,61267,62302,0.7122133,0 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,67685,67892,0,0.8333333 + S,0,67685,68927,1,1.13312 + F,0,67892,68927,0.8333333,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,80935,,305,116 + F,0,80935,81038,1,0.9206253 + S,0,80935,81038,0.3548797,0.7181275 + S,0,81038,,0.7181275 + F,0,81038,81349,0.9206253,0.6809599 + S,0,81038,81556,0.7181275,0.8000475 + F,0,81349,81556,0.6809599,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + F,0,14694,14901,0,0.8333333 + S,0,14694,14901,0.7337599,1.41984 + S,0,14901,15108,1.41984,1.6144 + F,0,14901,15936,0.8333333,0 + S,0,15108,15522,1.6144,1.80896 + S,0,15522,15936,1.80896,1.94208 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,102,25,93,434 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,411,130,387,308 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,102,25,93,434 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,179,179,203,467 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,1 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,237,361,274,534 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,0.63136 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,227,175,267,525 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,67685,,0.41632 + R,0,67685,,0 + F,0,67685,68513,0,0.7696 + M,0,67685,74310,564,-67,619,531 + F,0,68513,69341,0.7696,1 + F,0,69341,70169,1,0.7696 + F,0,70169,70998,0.7696,1 + F,0,70998,71826,1,0.8771199 + F,0,71826,72654,0.8771199,1 + F,0,72654,73482,1,0.73888 + F,0,73482,74310,0.73888,0 +Sprite,Foreground,Centre,"SB\SNOW.png",320,240 + S,0,67685,,2.35168 + F,0,67685,70998,0,1 + M,0,67685,80314,320,-211,317,693 + F,0,68513,80314,1,0 + F,0,70998,79279,1 + F,0,79279,80314,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + F,0,70998,71826,0,1 + M,0,70998,74299,257,66,263,208 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,70998,,0.7030401 + F,0,70998,71826,0,1 + M,0,70998,74310,431,109,422,223 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + F,0,70998,71826,0,1 + M,0,70998,74299,73,-34,66,270 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Animation,Foreground,Centre,"SB\am2\am2.png",320,240,5,500,LoopForever + S,0,70998,,0.6825599 + F,0,70998,71826,0,1 + M,0,70998,74299,390,75,370,115 + F,0,71826,72654,1,0.7132799 + F,0,72654,73689,0.7132799,1 + F,0,73689,74310,1,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,81763,,249,251 + S,0,81763,81866,0.39584,0.6620799 + F,0,81763,82384,1,0 + S,0,81866,82384,0.6620799,0.8975999 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,82591,,152,394 + S,0,82591,82694,0.3651201,0.7952 + F,0,82591,83212,1,0 + S,0,82694,83212,0.7952,1.01024 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,83419,,456,324 + S,0,83419,83523,0.3856,0.744 + F,0,83419,84040,1,0 + S,0,83523,84040,0.744,1.02048 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,85075,,321,197 + S,0,85075,85179,0.37536,0.80544 + F,0,85075,85696,0.9948799,0 + S,0,85179,85696,0.80544,1.12288 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,85903,,248,284 + S,0,85903,86007,0.3344,0.8464 + F,0,85903,86524,1,0 + S,0,86007,86524,0.8464,1.2048 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,86731,,207,101 + S,0,86731,86835,0.37536,0.8 + F,0,86731,87352,1,0 + S,0,86835,87352,0.8463999,1.22528 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,87560,,472,229 + S,0,87560,87663,0.3,0.8 + F,0,87560,88180,1,0 + S,0,87663,88180,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,88388,,408,373 + S,0,88388,88491,0.3,0.8 + F,0,88388,88905,1,0 + S,0,88491,88905,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,89216,,321,136 + S,0,89216,89319,0.3,0.8 + F,0,89216,89733,1,0 + S,0,89319,89733,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,90044,,257,252 + S,0,90044,90147,0.3,0.8 + F,0,90044,90561,1,0 + S,0,90147,90561,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,90872,,265,292 + S,0,90872,90975,0.3,0.8 + F,0,90872,91389,1,0 + S,0,90975,91389,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,91700,,265,132 + S,0,91700,91804,0.3,0.8 + F,0,91700,92217,1,0 + S,0,91804,92217,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,92528,,521,252 + S,0,92528,92632,0.3,0.8 + F,0,92528,93045,1,0 + S,0,92632,93045,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,93356,,304,309 + S,0,93356,93460,0.3,0.8 + F,0,93356,93873,1,0 + S,0,93460,93873,0.8,1.12 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,84247,,495,122 + S,0,84247,84351,0.3855998,0.7849598 + F,0,84247,84868,1,0 + S,0,84351,84868,0.7849598,1.13312 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + M,0,94184,,320,240 + S,0,94184,94391,1,1.05632 + F,0,94184,95012,1,0 + S,0,94391,95012,1.05632,1.19456 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,100809,102051,0.38048,0 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + R,0,120276,,0 + F,0,120276,120690,0,1 + F,0,120690,121518,1,0 +Sprite,Foreground,Centre,"SB\bg4.png",320,240 + F,0,107441,133112,1 + S,0,107441,133112,1.3,1 + R,0,107441,133112,0.2,0 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + F,0,107020,107434,0,1 + F,0,107434,108269,1,0 +Sprite,Foreground,Centre,"SB\C32.png",320,240 + F,0,173682,190322,1 + M,0,173682,200181,228,268,320,240 + S,0,173682,200181,1.70656,1 + R,0,173682,200181,0.3072,0 + F,0,190322,192412,1 + F,0,192412,200181,1 + F,0,200181,201009,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + F,0,173677,173682,0.69792,0.3702399 + S,0,173682,180306,1,1.43008 + R,0,173682,180306,0.04096008,2.7648 + F,0,173682,186103,0.3702399 + S,0,180306,186931,1.43008,1.64512 + R,0,180306,186931,2.7648,4.68992 + F,0,186103,186931,0.3702399,0 +Sprite,Foreground,Centre,"SB\MINI SNOW.png",320,240 + F,0,186931,187759,0,1 + S,0,186931,200181,1,1.53248 + R,0,186931,200181,1.92512,0 + F,0,187759,199353,1 + F,0,199353,200181,1,0 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + F,0,186931,188173,0,0.3503307 + S,0,186931,199353,1,1.28672 + R,0,186931,199353,-3.23584,-1.76128 + F,0,188173,198110,0.3503307,0.19104 + F,0,198110,199353,0.19104,0.005120086 + F,0,199353,200724,0.005120086,0 +Animation,Foreground,Centre,"SB\am1\am1.jpg",320,240,5,500,LoopForever + M,0,74310,,313,234 + F,0,74310,74724,0.05792008,1 + F,0,74724,75345,1,0.8813094 + F,0,75345,75966,0.8813094,0.9981386 + F,0,75966,76794,0.9981386,0.8279376 + F,0,76794,77622,0.8279376,0.9956568 + F,0,77622,78450,0.9956568,0.7588959 + F,0,78450,79279,0.7588959,0.9931734 + F,0,79279,80107,0.9931734,0.01013343 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + M,0,74310,,313,232 + S,0,74310,74413,0.3139198,0.6415999 + F,0,74310,75138,1,0 + S,0,74413,75138,0.6415999,0.7961942 +Sprite,Foreground,Centre,"SB\bg2.png",320,240 + F,0,133112,133940,0,1 + F,0,133940,147178,1 + S,0,133940,147178,1,1.16384 + F,0,147178,147592,1,0 + S,0,147178,147592,1.16384,1.33792 +Sprite,Foreground,Centre,"SB\sakura_b.png",320,240 + M,0,120690,,320,240 + S,0,120690,120794,1,1.29696 + F,0,120690,121104,1 + S,0,120794,121932,1.29696,1.44032 + F,0,121104,121932,1,0.002415478 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + S,0,160427,160531,0.7337601,1.4608 + F,0,160427,162083,1,0 + S,0,160531,161255,1.4608,1.77824 + S,0,161255,162083,1.77824,2.03615 +Sprite,Foreground,Centre,"SB\azure.png",320,240 + R,0,132284,,0 + F,0,132284,133112,0,1 + F,0,133112,133940,1,0 +Sprite,Foreground,Centre,"SB\wakka.png",320,240 + S,0,133940,134043,1,1.38912 + F,0,133940,134457,1,0.80544 + S,0,134043,134975,1.38912,1.89088 + F,0,134457,134975,0.80544,0 +Sprite,Foreground,Centre,"SB\SNOW.png",320,240 + S,0,108269,,2.37216 + F,0,108269,109097,0,1 + M,0,108269,120173,314,-250,315,229 + F,0,109097,112409,1 + F,0,112409,112823,1,0.80032 + F,0,112823,113237,0.80032,1 + F,0,113237,119344,1 + F,0,119344,120173,1,0 +Sprite,Foreground,Centre,"SB\black.png",320,240 + S,0,173263,,1 + F,0,173263,173682,0,1 + F,0,173682,174510,1,0 + F,0,173682,174510,1,0.004830897 +Sprite,Foreground,Centre,"SB\White Effect.png",320,240 + M,0,173677,,217,128 + F,0,173677,174510,1,0 + S,0,173677,174510,0.2115199,1.09216 +//Storyboard Sound Samples +//Background Colour Transformations +3,100,163,162,255 + +[TimingPoints] +1445,414.050100062108,4,2,1,70,1,0 +13659,-100,4,2,1,40,0,0 +13762,-100,4,2,1,45,0,0 +13866,-100,4,2,1,50,0,0 +13970,-100,4,2,1,55,0,0 +14073,-100,4,2,1,50,0,0 +14177,-100,4,2,1,55,0,0 +14280,-100,4,2,1,60,0,0 +14384,-100,4,2,1,75,0,0 +14487,-100,4,2,1,80,0,0 +14591,-100,4,2,1,85,0,0 +14694,-100,4,2,1,60,0,0 +14901,-100,4,2,0,45,0,0 +15005,-100,4,2,1,60,0,0 +15315,-100,4,2,0,45,0,0 +15419,-100,4,2,1,60,0,0 +15626,-100,4,2,0,45,0,0 +15833,-100,4,2,1,60,0,0 +16040,-100,4,2,0,45,0,0 +16247,-100,4,2,1,60,0,0 +16454,-100,4,2,0,45,0,0 +16764,-100,4,2,1,60,0,0 +16868,-100,4,2,0,45,0,0 +17178,-100,4,2,1,60,0,0 +17282,-100,4,2,0,45,0,0 +17489,-100,4,2,1,60,0,0 +17696,-100,4,2,0,45,0,0 +17903,-100,4,2,1,60,0,0 +18110,-100,4,2,0,45,0,0 +18317,-100,4,2,1,60,0,0 +18628,-100,4,2,0,45,0,0 +18731,-100,4,2,1,60,0,0 +18938,-100,4,2,0,45,0,0 +19145,-100,4,2,1,60,0,0 +19352,-100,4,2,0,45,0,0 +19559,-100,4,2,1,60,0,0 +19766,-100,4,2,0,45,0,0 +19973,-100,4,2,1,60,0,0 +20180,-100,4,2,0,45,0,0 +20387,-100,4,2,1,60,0,0 +20594,-100,4,2,0,45,0,0 +20801,-100,4,2,1,60,0,0 +21112,-100,4,2,0,45,0,0 +21215,-100,4,2,1,60,0,0 +21422,-100,4,2,0,45,0,0 +21629,-100,4,2,1,60,0,0 +21836,-100,4,2,0,45,0,0 +22043,-100,4,2,1,60,0,0 +22354,-100,4,2,0,45,0,0 +22458,-100,4,2,1,60,0,0 +22768,-100,4,2,0,45,0,0 +22872,-100,4,2,1,60,0,0 +23079,-100,4,2,0,45,0,0 +23286,-100,4,2,1,60,0,0 +23596,-100,4,2,0,45,0,0 +23700,-100,4,2,1,60,0,0 +23907,-100,4,2,0,45,0,0 +24114,-100,4,2,1,60,0,0 +24321,-100,4,2,0,45,0,0 +24528,-100,4,1,1,60,0,0 +27323,-100,4,2,1,60,0,0 +27530,-100,4,2,1,60,0,0 +27944,-100,4,2,1,60,0,0 +29082,-100,4,2,1,65,0,0 +39951,-100,4,2,1,80,0,0 +40158,-100,4,2,1,85,0,0 +40766,-100,4,2,1,30,0,0 +41193,-100,4,2,1,85,0,0 +41814,-100,4,2,1,85,0,0 +43471,-100,4,2,1,75,0,0 +44092,-100,4,2,1,65,0,0 +44299,-100,4,2,1,55,0,0 +44506,-100,4,2,1,45,0,0 +44920,-100,4,2,1,50,0,0 +45437,-100,4,2,1,55,0,0 +46162,-100,4,2,1,60,0,0 +47094,-100,4,2,1,65,0,0 +47818,-100,4,2,1,55,0,0 +48750,-100,4,2,1,60,0,0 +50095,-100,4,2,1,65,0,0 +54444,414.050100062108,4,2,1,70,1,0 +67383,-100,4,2,1,50,0,0 +67590,-100,4,2,1,70,0,0 +102680,-200,4,2,1,70,0,0 +105993,-100,4,2,1,70,0,0 +107441,414.050100062108,4,2,1,70,1,0 +107648,-100,4,2,1,70,0,0 +120380,-100,4,2,1,75,0,0 +132905,-100,4,2,1,35,0,0 +133008,-100,4,2,1,40,0,0 +133112,-100,4,2,1,45,0,0 +133215,-100,4,2,1,45,0,0 +133319,-100,4,2,1,50,0,0 +133422,-100,4,2,1,55,0,0 +133526,-100,4,2,1,60,0,0 +133629,-100,4,2,1,65,0,0 +133733,-100,4,2,1,70,0,0 +133836,-100,4,2,1,75,0,0 +133940,-100,4,2,1,80,0,1 +136217,-100,4,2,1,60,0,1 +137045,-100,4,2,1,80,0,1 +147174,-100,4,2,1,80,0,0 +147178,414.050100062108,4,2,1,70,1,0 +173263,-100,4,2,1,60,0,0 +173682,414.050100062108,4,2,1,70,1,0 +174303,-100,4,2,1,40,0,0 +174406,-100,4,2,1,45,0,0 +174510,-100,4,2,1,50,0,0 +174613,-100,4,2,1,55,0,0 +174717,-100,4,2,1,60,0,0 +174820,-100,4,2,1,65,0,0 +174924,-100,4,2,1,70,0,0 +175027,-100,4,2,1,75,0,0 +175131,-100,4,2,1,80,0,0 +186517,-100,4,2,1,30,0,0 +186931,-100,4,2,1,80,0,1 +187035,-100,4,2,1,80,0,1 +196247,-100,4,2,1,70,0,1 +196454,-100,4,2,1,60,0,1 +196661,-100,4,2,1,50,0,1 +196868,-100,4,2,1,80,0,1 +200181,-100,4,2,1,80,0,0 + +[Colours] +Combo1 : 152,48,207 +Combo2 : 254,75,169 +Combo3 : 226,5,221 +SliderBorder : 224,224,224 + +[HitObjects] +112,96,1030,1,0 +144,108,1134,1,0 +176,92,1237,1,0 +208,108,1341,1,0 +240,96,1445,1,0 +408,272,2894,5,0 +408,272,2997,1,0 +408,272,3101,2,0,B|360:280|312:272,1,90 +104,276,3515,2,0,B|152:268|200:276,1,90 +216,184,3929,1,0 +168,88,4136,1,0 +256,144,4343,1,0 +208,48,4550,1,0 +312,64,4757,5,0 +416,64,4964,1,0 +416,64,5067,2,0,B|448:104|464:144|464:200,1,135 +463,187,5481,1,0 +463,187,5585,1,0 +437,289,5792,1,0 +337,262,5999,1,0 +364,162,6206,1,0 +464,188,6413,1,0 +464,88,6620,6,0,B|424:64|368:56,1,90 +280,56,7034,2,0,B|184:56,1,90 +190,56,7448,1,0 +128,136,7655,1,0 +120,168,7759,1,0 +128,200,7862,1,0 +136,232,7966,1,0 +128,264,8069,1,0 +192,336,8276,5,0 +192,336,8380,2,0,B|240:336|288:328|336:312,1,135 +324,315,8794,1,0 +324,315,8897,1,0 +336,216,9104,1,0 +440,200,9311,1,0 +480,104,9518,1,0 +392,40,9726,6,0,B|288:40,1,90 +224,88,10140,1,0 +216,104,10243,1,0 +208,120,10347,1,0 +200,136,10450,1,0 +192,152,10554,1,0 +256,296,10761,1,0 +256,152,10968,1,0 +256,296,11175,1,0 +168,344,11382,5,0 +168,248,11589,1,0 +168,248,11692,1,0 +128,112,12003,1,0 +128,112,12106,1,0 +128,112,12210,2,0,B|144:160|128:208,1,90 +48,248,12624,2,0,B|32:200|48:152,1,90 +48,64,13038,5,0 +48,64,13245,2,0,B|96:64,2,45 +208,64,13659,1,0 +240,64,13762,1,0 +272,72,13866,1,0 +304,88,13970,1,0 +328,112,14073,1,8 +344,144,14177,1,8 +352,176,14280,1,8 +352,208,14384,5,0 +344,240,14487,1,0 +328,272,14591,1,0 +304,296,14694,2,0,B|264:320|216:328,1,90,0|2 +104,280,15108,2,0,B|144:256|192:248,1,90,0|2 +280,224,15522,1,0 +296,120,15729,1,2 +192,96,15936,1,0 +168,192,16143,5,2 +168,192,16247,1,0 +168,192,16350,1,0 +144,288,16557,2,0,B|208:288|208:288|232:264,1,90,2|0 +168,192,16971,2,0,B|104:192|104:192|80:216,1,90,2|0 +144,288,17385,1,2 +256,352,17592,1,0 +368,288,17799,1,2 +256,104,18007,5,0 +256,104,18214,1,2 +256,104,18317,2,0,B|256:240,1,135,0|2 +256,239,18731,1,0 +256,239,18835,1,0 +168,296,19042,1,2 +256,352,19249,1,0 +168,296,19456,1,2 +256,239,19663,1,0 +344,296,19870,1,2 +256,239,20077,1,0 +136,152,20284,5,2 +256,96,20491,1,0 +376,152,20698,1,2 +424,264,20905,1,0 +416,264,21008,1,0 +408,264,21112,1,2 +400,264,21215,1,0 +392,264,21319,1,0 +288,264,21526,1,2 +288,264,21629,1,0 +136,208,21940,5,2 +136,208,22043,1,0 +136,208,22147,2,0,B|136:144|136:144|160:128,1,90,0|2 +248,104,22561,2,0,B|248:168|248:168|224:184,1,90,0|2 +136,104,22975,1,0 +248,56,23182,1,2 +360,104,23389,1,0 +376,120,23493,1,0 +392,136,23596,1,2 +408,152,23700,1,0 +424,168,23803,1,0 +432,280,24010,5,2 +256,328,24217,1,0 +80,280,24424,1,2 +256,192,24631,12,0,27116 +256,192,27323,5,0 +256,192,27426,1,0 +256,192,27530,1,4 +72,136,27944,5,0 +161,84,28151,1,0 +158,182,28358,1,0 +96,80,28565,1,0 +192,32,28772,5,0 +224,32,28875,1,0 +256,32,28979,1,0 +288,32,29082,1,0 +320,32,29186,2,0,B|368:32,4,45 +272,112,29807,6,0,B|248:160|272:200,1,90 +360,160,30221,1,0 +368,168,30324,1,0 +376,176,30428,1,0 +384,184,30532,1,0 +392,192,30635,2,0,B|448:192,4,45 +328,280,31256,5,0 +224,296,31463,1,0 +128,248,31670,2,0,B|96:216|104:160,1,90 +176,88,32084,2,0,B|208:120|200:176,1,90 +128,248,32498,2,0,B|96:280,2,45 +216,192,32912,2,0,B|256:168|304:192,1,90 +264,288,33326,2,0,B|304:312|352:288,1,90 +408,208,33740,1,0 +424,104,33947,1,0 +336,40,34154,6,0,B|288:40,4,45,0|0|0|0|8 +336,136,34776,1,0 +176,136,34983,1,0 +256,72,35190,1,0 +256,112,35293,1,0 +256,160,35397,1,0 +88,264,35604,2,0,B|128:240|176:240,1,90 +424,264,36018,2,0,B|384:240|336:240,1,90 +208,320,36432,6,0,B|208:232,1,90 +304,320,36846,2,0,B|304:232,1,90 +256,144,37260,1,0 +256,144,37467,1,0 +256,144,37570,1,0 +256,144,37674,1,0 +336,80,37881,1,0 +424,136,38088,1,0 +424,136,38191,1,0 +424,136,38295,2,0,B|424:232,1,90 +344,272,38709,5,0 +344,176,38916,1,0 +256,328,39123,1,0 +256,120,39330,1,0 +176,224,39537,5,0 +176,224,39744,1,0 +176,224,39848,1,0 +176,224,39951,1,0 +120,128,40158,1,0 +120,128,40262,1,0 +120,128,40365,1,0 +224,128,40572,1,0 +224,128,40676,1,0 +224,128,40779,6,0,B|224:96,7,22.5 +224,105,41193,6,0,B|312:104,1,90,4|0 +400,152,41607,1,0 +400,256,41814,2,0,B|432:288,4,45 +304,312,42435,1,0 +208,256,42642,1,0 +208,256,42746,1,0 +208,256,42850,2,0,B|208:168,1,90 +168,80,43264,6,0,B|208:56|256:80,1,90 +272,112,43574,1,0 +288,144,43678,2,0,B|312:184|368:192,1,90 +368,96,44092,1,0 +368,288,44299,1,0 +280,336,44506,6,0,B|240:368,4,45 +232,248,45127,1,0 +232,248,45230,1,0 +128,248,45437,1,0 +120,248,45541,1,0 +112,248,45644,1,0 +104,248,45748,1,0 +48,168,45955,1,0 +104,80,46162,5,0 +120,80,46265,1,0 +136,80,46369,1,0 +152,80,46472,1,0 +168,80,46576,1,0 +272,80,46783,1,0 +272,80,46886,1,0 +376,80,47094,1,0 +400,80,47197,1,0 +424,80,47301,1,0 +448,80,47404,1,0 +456,176,47611,5,0 +400,256,47818,1,8 +368,256,47922,1,0 +336,256,48025,1,0 +304,256,48129,1,0 +272,256,48232,1,0 +176,256,48439,1,0 +176,256,48543,1,0 +80,256,48750,5,0 +80,256,48853,1,0 +80,256,48957,1,0 +80,256,49060,1,0 +128,168,49267,1,0 +32,168,49474,1,0 +32,168,49578,1,0 +32,168,49681,1,0 +32,168,49785,1,0 +32,168,49888,1,0 +80,80,50095,5,0 +96,72,50199,1,0 +200,40,50406,1,0 +224,40,50509,1,0 +248,40,50613,1,0 +272,40,50716,1,0 +360,96,50923,1,0 +415,175,51131,5,0 +336,230,51338,1,0 +281,151,51545,1,0 +360,96,51752,1,0 +360,96,51855,1,0 +360,96,51959,1,0 +442,214,52166,1,0 +323,297,52373,1,0 +241,178,52580,1,0 +241,178,52683,1,0 +241,178,52787,1,0 +256,192,52890,12,4,54444 +168,112,67072,5,0 +168,112,67176,1,0 +168,112,67279,1,0 +168,112,67486,1,8 +168,112,67693,1,4 +144,96,80322,5,0 +144,96,80425,1,0 +144,96,80529,1,0 +144,96,80736,1,0 +240,56,80943,1,12 +280,152,81150,1,0 +280,152,81253,1,0 +280,152,81357,1,0 +184,192,81564,1,0 +184,192,81771,1,4 +88,232,81978,5,0 +88,232,82185,1,0 +88,232,82392,1,0 +88,336,82599,2,8,B|184:336,1,90,8|0 +208,336,82909,1,0 +240,336,83013,2,0,B|328:336,1,90 +392,264,83427,1,4 +464,168,83634,1,0 +392,264,83841,1,0 +464,168,84048,1,0 +432,64,84255,6,8,B|336:64,1,90,8|0 +80,64,84669,2,8,B|176:64,1,90,0|0 +256,136,85083,1,4 +256,304,85290,1,0 +256,136,85497,1,0 +256,304,85704,1,0 +184,224,85911,5,8 +88,224,86118,1,0 +80,216,86222,1,0 +72,208,86325,1,0 +72,112,86532,1,0 +144,40,86739,2,0,B|240:40,1,90,4|0 +224,128,87153,2,0,B|312:128,1,90 +408,168,87568,6,0,B|432:208|416:256,1,90,8|0 +416,254,87878,1,0 +416,254,87982,1,0 +344,312,88189,1,0 +344,312,88396,1,4 +168,256,88603,1,0 +344,200,88810,1,0 +168,144,89017,1,0 +256,76,89224,5,8 +256,160,89431,1,0 +256,192,89534,1,0 +256,224,89638,1,0 +256,307,89845,1,0 +192,192,90052,1,4 +256,76,90259,1,0 +320,192,90466,1,0 +256,307,90673,1,0 +200,232,90880,5,8 +312,232,91087,1,0 +152,152,91294,1,0 +360,152,91501,1,0 +200,72,91708,5,4 +312,72,91915,1,0 +152,152,92122,1,0 +360,152,92329,1,0 +456,192,92536,5,8 +416,288,92743,1,0 +384,288,92847,1,0 +352,288,92950,1,0 +264,336,93157,2,0,B|240:296|240:240,1,90,0|4 +240,160,93571,1,0 +208,64,93778,1,0 +176,72,93882,1,0 +152,96,93985,1,0 +136,128,94089,1,0 +136,160,94192,5,8 +72,240,94399,2,0,B|16:256,2,45 +136,312,94813,1,0 +192,232,95020,2,0,B|240:152,1,90 +376,112,95434,2,0,B|328:192,1,90 +464,112,96056,5,0 +496,192,96263,1,0 +464,272,96470,1,0 +48,272,96884,1,0 +16,192,97091,1,0 +48,112,97298,1,0 +152,64,97505,5,0 +224,136,97712,1,0 +152,64,97919,1,0 +248,40,98126,1,0 +344,72,98333,2,0,B|392:88|416:144,1,90 +392,224,98747,2,0,B|344:208|320:152,1,90 +224,136,99161,5,0 +152,208,99368,1,0 +144,240,99471,1,0 +152,272,99575,1,0 +248,222,99782,1,0 +248,312,99989,2,0,B|248:216,1,90 +328,112,100403,2,0,B|328:216,1,90 +360,288,100817,5,2 +432,216,101024,1,0 +432,184,101128,1,0 +432,152,101231,1,0 +400,64,101438,1,0 +304,40,101645,2,0,B|256:40|216:72,1,90 +200,168,102059,2,0,B|248:168|288:136,1,90 +176,280,102680,6,0,B|176:336,2,45 +336,104,103508,2,0,B|336:48,2,45 +336,104,104130,5,0 +416,192,104337,1,0 +336,280,104544,1,0 +256,192,104751,1,0 +176,280,104958,1,0 +96,192,105165,1,0 +176,104,105372,1,0 +256,192,105579,1,0 +256,192,105786,5,0 +256,192,105993,2,0,B|256:136,2,45 +256,192,106407,1,0 +256,288,106614,1,0 +256,288,106821,2,0,B|160:288,1,90 +144,288,107131,1,0 +128,288,107235,1,0 +112,288,107338,1,0 +96,288,107441,1,4 +256,56,120276,5,0 +248,88,120380,1,0 +256,120,120483,1,0 +264,152,120587,1,0 +256,184,120690,5,4 +160,208,120897,2,0,B|176:256|224:280,1,90 +296,208,121311,1,0 +312,192,121415,1,0 +336,184,121518,5,0 +360,184,121622,1,0 +376,192,121725,1,0 +392,208,121829,1,0 +400,232,121932,1,0 +376,336,122139,1,0 +376,336,122243,1,0 +376,336,122346,6,0,B|336:360|288:360,1,90 +192,336,122760,2,0,B|168:296|168:248,1,90 +272,208,123174,1,0 +216,104,123381,1,0 +336,88,123588,2,0,B|304:120|304:176,2,90 +416,168,124210,1,0 +416,168,124313,2,0,B|472:192|472:256|424:280,1,135 +432,275,124727,1,0 +432,275,124831,1,0 +344,224,125038,1,0 +240,208,125245,2,0,B|248:160|304:136,1,90 +360,80,125659,6,0,B|328:40|272:56,1,90 +216,144,126073,1,0 +104,136,126280,1,0 +104,136,126383,1,0 +104,136,126487,2,0,B|64:168|64:224,1,90 +136,296,126901,5,0 +160,320,127004,1,0 +192,328,127108,1,0 +224,320,127211,1,0 +248,296,127315,2,0,B|280:264|296:216,1,90 +294,219,127625,1,0 +408,80,127936,5,0 +368,80,128039,1,0 +328,80,128143,1,0 +224,80,128350,1,0 +72,40,128557,1,0 +104,192,128764,1,0 +272,144,128971,5,0 +120,104,129178,1,0 +152,256,129385,1,0 +152,256,129489,1,0 +152,256,129592,1,0 +152,256,129696,1,0 +152,256,129799,2,0,B|200:280|240:256,1,90 +328,128,130213,2,0,B|280:104|240:128,1,90 +240,224,130627,5,0 +336,256,130834,1,0 +320,288,130938,1,0 +296,312,131041,2,0,B|256:344|208:352,1,90 +184,352,131352,1,0 +152,352,131455,1,0 +64,264,131662,1,0 +160,264,131869,1,0 +32,248,132076,1,0 +192,248,132284,1,0 +96,160,132491,1,0 +120,136,132594,1,0 +152,128,132698,1,0 +248,128,132905,5,0 +280,120,133008,1,0 +312,120,133112,1,0 +344,128,133215,1,0 +372,144,133319,1,0 +392,176,133422,1,0 +400,208,133526,1,0 +400,240,133629,1,0 +392,272,133733,1,0 +376,304,133836,1,0 +344,320,133940,6,0,B|304:344|256:344,1,90,4|0 +192,280,134354,1,0 +92,264,134561,1,0 +80,232,134664,1,0 +76,196,134768,1,0 +80,160,134871,1,0 +88,128,134975,2,0,B|104:88|144:56,1,90 +232,104,135389,6,0,B|256:152,2,45,0|0|8 +232,104,135803,1,0 +380,49,136010,1,0 +289,185,136217,1,0 +283,14,136424,1,0 +389,149,136631,1,0 +232,104,136838,1,0 +380,49,137045,1,0 +289,185,137252,5,4 +289,185,137459,1,0 +289,185,137563,1,0 +112,256,137873,1,0 +112,256,137977,1,0 +112,256,138080,2,0,B|160:272|208:256,1,90 +400,256,138494,2,0,B|352:240|304:256,1,90 +256,184,138908,5,8 +288,72,139115,1,0 +232,152,139322,1,0 +264,40,139529,1,0 +168,72,139736,1,0 +168,72,139943,1,0 +100,160,140150,1,0 +88,192,140254,1,0 +84,224,140357,1,0 +88,256,140461,1,0 +96,288,140565,6,0,B|128:328|184:336,1,90,4|0 +208,336,140875,1,0 +248,336,140979,2,0,B|336:336,1,90 +376,336,141289,1,0 +408,336,141393,1,0 +455,255,141600,1,0 +460,82,141807,1,0 +351,217,142014,1,0 +512,172,142221,5,8 +361,117,142428,1,0 +256,116,142635,2,0,B|208:116,3,45 +184,132,143049,2,0,B|144:160|144:216,1,90 +256,280,143463,2,0,B|296:252|296:196,1,90 +256,192,143877,12,4,147178 +152,120,148627,6,0,B|184:88,2,45 +152,232,149041,1,0 +264,232,149248,2,0,B|264:280|224:320,1,90 +152,232,149662,2,0,B|152:184|192:144,1,90 +288,128,150076,5,0 +416,64,150283,1,0 +288,128,150490,1,8 +392,184,150697,1,0 +392,184,150800,2,0,B|408:232|408:280|352:320,1,135 +367,307,151214,1,0 +367,307,151318,5,0 +256,344,151525,1,0 +98,316,151732,1,0 +126,158,151939,1,0 +283,186,152146,1,0 +194,290,152353,1,0 +36,262,152560,1,0 +64,104,152767,1,0 +221,132,152974,5,0 +194,290,153181,1,0 +128,192,153388,1,0 +144,192,153492,1,0 +160,192,153595,1,0 +176,192,153699,1,0 +192,192,153802,2,0,B|320:192,1,90,4|0 +280,192,154113,1,0 +472,152,154423,5,0 +480,192,154527,1,0 +472,232,154630,1,0 +336,192,154837,1,0 +456,112,155044,1,0 +456,272,155251,1,0 +360,328,155459,5,0 +152,328,155666,1,0 +256,272,155873,1,0 +256,240,155976,1,0 +256,208,156080,1,0 +256,176,156183,1,0 +256,144,156287,1,0 +320,64,156494,2,0,B|376:64|392:112,2,90,0|0|0 +216,64,157115,5,8 +216,64,157322,1,0 +216,64,157425,1,0 +88,104,157736,1,0 +88,104,157839,1,0 +88,104,157943,2,0,B|88:200,1,90 +184,256,158357,2,0,B|184:160,1,90 +88,228,158771,2,0,B|88:324,1,90 +88,318,159081,1,0 +88,318,159185,1,0 +192,320,159392,5,0 +208,320,159495,1,0 +224,320,159599,1,0 +240,320,159703,1,0 +256,320,159806,1,0 +272,320,159910,1,0 +288,320,160013,1,0 +304,320,160117,1,0 +320,320,160220,1,0 +336,320,160324,1,0 +352,320,160427,1,4 +152,72,173263,6,0,B|152:128,4,45,0|0|0|0|8 +248,48,173889,2,0,B|336:48,1,90,0|0 +408,120,174303,2,0,B|456:120,2,45,0|0|0 +376,152,174613,2,0,B|328:152,2,45 +408,184,174924,2,0,B|464:184,2,45 +376,216,175234,1,0 +344,240,175338,6,0,B|240:240,1,90,0|0 +184,184,175752,1,0 +144,296,175959,1,0 +152,160,176166,1,0 +112,272,176373,1,0 +32,216,176580,1,0 +32,112,176787,2,0,B|48:72|96:48,1,90,0|0 +200,48,177201,5,0 +200,48,177304,2,0,B|344:48,1,135 +368,48,177718,1,0 +400,48,177822,1,0 +448,144,178029,1,0 +424,256,178236,2,0,B|376:240|352:192,1,90,0|0 +344,160,178547,1,0 +336,120,178650,2,0,B|288:128|248:168,1,90,0|0 +160,224,179064,6,0,B|256:248,1,90 +352,160,179478,2,0,B|256:136,1,90 +176,88,179892,1,0 +144,72,179996,1,0 +112,88,180099,1,0 +80,72,180203,1,0 +56,88,180306,5,0 +56,176,180513,1,0 +56,176,180617,2,0,B|56:312,1,135 +56,311,181031,1,0 +56,311,181134,1,0 +168,312,181341,1,0 +256,256,181548,1,0 +344,312,181755,1,0 +440,280,181963,2,0,B|472:240|464:192,1,90,0|0 +440,96,182377,5,0 +432,80,182480,1,0 +424,64,182584,1,0 +416,48,182687,1,0 +408,32,182791,2,0,B|344:32|344:32|328:56,1,90 +288,136,183205,2,0,B|200:136,1,90 +128,64,183619,2,0,B|120:32|120:32|56:32,1,90 +32,32,183929,2,0,B|32:168,1,135 +32,200,184343,1,0 +32,232,184447,5,0 +128,272,184654,2,0,B|216:320,1,90 +328,272,185068,2,0,B|240:224,1,90 +168,160,185482,1,0 +176,120,185585,1,0 +208,96,185689,1,0 +320,80,185896,5,0 +352,88,185999,1,0 +376,112,186103,1,0 +384,152,186207,1,0 +376,184,186310,1,0 +344,208,186414,1,0 +312,208,186517,2,0,B|280:192,8,22.5,0|0|0|0|0|0|0|0|4 +256,192,187035,12,0,188173 +256,96,188380,5,0 +256,96,188484,1,0 +256,96,188587,1,8 +376,96,188794,1,0 +448,184,189001,2,0,B|360:216,1,90 +424,304,189415,2,0,B|336:336,1,90 +216,336,189829,1,0 +144,232,190036,1,0 +216,336,190244,1,4 +280,232,190451,5,0 +280,232,190554,2,0,B|280:96,1,135 +280,64,190968,1,0 +280,32,191072,1,0 +176,64,191279,1,0 +121,179,191486,1,0 +237,233,191693,1,0 +291,117,191900,2,0,B|360:184,1,90,8|0 +400,264,192314,6,0,B|352:256|304:264,1,90 +112,264,192728,2,0,B|160:256|208:264,1,90 +256,168,193142,2,0,B|280:128,4,45,0|0|0|0|4 +160,112,193763,1,0 +160,112,193866,2,0,B|104:112|64:144|64:208,1,135 +64,224,194280,1,0 +64,256,194384,1,0 +128,352,194591,5,0 +192,256,194798,1,0 +256,352,195005,1,0 +368,352,195212,2,0,B|416:340|420:280,1,90,8|0 +416,184,195626,1,0 +408,168,195730,1,0 +400,152,195833,1,0 +392,136,195937,1,0 +384,120,196040,5,0 +184,64,196247,1,0 +128,264,196454,1,0 +328,320,196661,1,0 +256,192,196868,12,8,199767 +256,192,200181,5,4 diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 3a15b5e6b1..df22aede95 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -151,6 +151,7 @@ + \ No newline at end of file From fcfdbc8e07cf19ee234e2bff03eb0105272c9410 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:02:48 +0900 Subject: [PATCH 044/239] Don't show the migrate button on deployed builds for now --- .../Sections/Maintenance/GeneralSettings.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index f0f5b434cd..2b40ade895 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(OsuGameBase osuGame, BeatmapManager beatmaps) { Children = new Drawable[] { @@ -55,8 +55,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance beatmaps.Restore(b); }).ContinueWith(t => Schedule(() => restoreButton.Enabled.Value = true)); } - }, - migrateButton = new SettingsButton + } + }; + + if (!osuGame.IsDeployedBuild) + { + Add(migrateButton = new SettingsButton { Text = "Migrate all beatmaps to the new format", Action = () => @@ -64,8 +68,8 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance migrateButton.Enabled.Value = false; Task.Factory.StartNew(beatmaps.MigrateAllToNewFormat).ContinueWith(t => Schedule(() => migrateButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); } - } - }; + }); + } } } } From feef4b1890daa1bb609c995d3bd6a90d8c84b580 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:18:00 +0900 Subject: [PATCH 045/239] Add license header --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index f8897a4e9d..36b53e1137 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -1,3 +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 Newtonsoft.Json; From f1dbcc4f1aab9d0b5540a9dfabeabea29858bc00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:23:53 +0900 Subject: [PATCH 046/239] Remove misleading comments --- 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 6b547afe5d..8d20a3dcfb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -548,12 +548,10 @@ namespace osu.Game.Beatmaps public void UpdateContent(BeatmapInfo beatmapInfo, Stream newData) { - // let's only allow one concurrent update at a time for now. var context = createContext(); using (var transaction = context.BeginTransaction()) { - // create local stores so we can isolate and thread safely, and share a context/transaction. var setInfo = beatmapInfo.BeatmapSet; var existingSetFileInfo = setInfo.Files.First(f => f.FileInfo.Hash == beatmapInfo.Hash); var existingFileInfo = existingSetFileInfo.FileInfo; From 9e51480aa392935b9d06b29869cd9b24439d9d6a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:27:30 +0900 Subject: [PATCH 047/239] Cleanup TypedListConverter a bit --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 36b53e1137..c3d5869035 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -69,10 +69,7 @@ namespace osu.Game.IO.Serialization.Converters serializer.Serialize(writer, lookupTable); writer.WritePropertyName("Items"); - writer.WriteStartArray(); - foreach (var item in objects) - item.WriteTo(writer); - writer.WriteEndArray(); + serializer.Serialize(writer, objects); writer.WriteEndObject(); } From d026587a915ad17d1ab9b3871fa0144ef1b6e095 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 12:49:16 +0900 Subject: [PATCH 048/239] Add flag to explicitly serialize the type version --- .../Converters/TypedListConverter.cs | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index c3d5869035..32b7772092 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -16,6 +16,26 @@ namespace osu.Game.IO.Serialization.Converters /// The type of objects contained in the this attribute is attached to. public class TypedListConverter : JsonConverter { + private readonly bool requiresTypeVersion; + + /// + /// Constructs a new . + /// + // ReSharper disable once UnusedMember.Global + public TypedListConverter() + { + } + + /// + /// Constructs a new . + /// + /// Whether the version of the type should be serialized. + // ReSharper disable once UnusedMember.Global (Used in Beatmap) + public TypedListConverter(bool requiresTypeVersion) + { + this.requiresTypeVersion = requiresTypeVersion; + } + public override bool CanConvert(Type objectType) => objectType == typeof(List); public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) @@ -49,12 +69,17 @@ namespace osu.Game.IO.Serialization.Converters var objects = new List(); foreach (var item in list) { - var type = item.GetType().AssemblyQualifiedName; + var type = item.GetType(); + var assemblyName = type.Assembly.GetName(); - int typeId = lookupTable.IndexOf(type); + var typeString = $"{type.FullName}, {assemblyName.Name}"; + if (requiresTypeVersion) + typeString += $", {assemblyName.Version}"; + + int typeId = lookupTable.IndexOf(typeString); if (typeId == -1) { - lookupTable.Add(type); + lookupTable.Add(typeString); typeId = lookupTable.Count - 1; } From 245b5f759ff4a2cc00e4ca89a5b553bccbca0ade Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 14:36:32 +0900 Subject: [PATCH 049/239] Underscore + lowercase all keys --- .../Converters/TypedListConverter.cs | 12 ++++++------ .../Serialization/Converters/Vector2Converter.cs | 6 +++--- osu.Game/IO/Serialization/IJsonSerializable.cs | 3 ++- osu.Game/IO/Serialization/KeyContractResolver.cs | 16 ++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 5 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 osu.Game/IO/Serialization/KeyContractResolver.cs diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 32b7772092..7147f9110e 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -45,13 +45,13 @@ namespace osu.Game.IO.Serialization.Converters var obj = JObject.Load(reader); var lookupTable = new List(); - serializer.Populate(obj["LookupTable"].CreateReader(), lookupTable); + serializer.Populate(obj["lookup_table"].CreateReader(), lookupTable); - foreach (var tok in obj["Items"]) + foreach (var tok in obj["items"]) { var itemReader = tok.CreateReader(); - var typeName = lookupTable[(int)tok["Type"]]; + var typeName = lookupTable[(int)tok["type"]]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); serializer.Populate(itemReader, instance); @@ -84,16 +84,16 @@ namespace osu.Game.IO.Serialization.Converters } var itemObject = JObject.FromObject(item, serializer); - itemObject.AddFirst(new JProperty("Type", typeId)); + itemObject.AddFirst(new JProperty("type", typeId)); objects.Add(itemObject); } writer.WriteStartObject(); - writer.WritePropertyName("LookupTable"); + writer.WritePropertyName("lookup_table"); serializer.Serialize(writer, lookupTable); - writer.WritePropertyName("Items"); + writer.WritePropertyName("items"); serializer.Serialize(writer, objects); writer.WriteEndObject(); diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs index 5f21018fd5..87fbd31d21 100644 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -18,7 +18,7 @@ namespace osu.Game.IO.Serialization.Converters public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var obj = JObject.Load(reader); - return new Vector2((float)obj["X"], (float)obj["Y"]); + return new Vector2((float)obj["x"], (float)obj["y"]); } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) @@ -27,9 +27,9 @@ namespace osu.Game.IO.Serialization.Converters writer.WriteStartObject(); - writer.WritePropertyName("X"); + writer.WritePropertyName("x"); writer.WriteValue(vector.X); - writer.WritePropertyName("Y"); + writer.WritePropertyName("y"); writer.WriteValue(vector.Y); writer.WriteEndObject(); diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index 38e7f47656..892c266fe3 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -30,7 +30,8 @@ namespace osu.Game.IO.Serialization Formatting = Formatting.Indented, ObjectCreationHandling = ObjectCreationHandling.Replace, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, - Converters = new JsonConverter[] { new Vector2Converter() } + Converters = new JsonConverter[] { new Vector2Converter() }, + ContractResolver = new KeyContractResolver() }; } } diff --git a/osu.Game/IO/Serialization/KeyContractResolver.cs b/osu.Game/IO/Serialization/KeyContractResolver.cs new file mode 100644 index 0000000000..10375646ce --- /dev/null +++ b/osu.Game/IO/Serialization/KeyContractResolver.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using Humanizer; +using Newtonsoft.Json.Serialization; + +namespace osu.Game.IO.Serialization +{ + public class KeyContractResolver : DefaultContractResolver + { + protected override string ResolvePropertyName(string propertyName) + { + return propertyName.Underscore(); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 9e5d4291ce..a2d4a00110 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -272,6 +272,7 @@ + 20171019041408_InitialCreate.cs From f5f7658e906125fca54bc57532971d8d3cb74b36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 14:40:28 +0900 Subject: [PATCH 050/239] Don't serialize TotalBreakTime --- osu.Game/Beatmaps/Beatmap.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index c331872dda..e9342584cc 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -34,15 +34,16 @@ namespace osu.Game.Beatmaps [JsonIgnore] public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; - [JsonConverter(typeof(TypedListConverter))] /// /// The HitObjects this Beatmap contains. /// + [JsonConverter(typeof(TypedListConverter))] public List HitObjects = new List(); /// /// Total amount of break time in the beatmap. /// + [JsonIgnore] public double TotalBreakTime => Breaks.Sum(b => b.Duration); /// From e573db04d43361caf21e14b5c7a601ec0df4ebd5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 14:42:36 +0900 Subject: [PATCH 051/239] Don't serialize HitObject.Kiai --- osu.Game/Rulesets/Objects/HitObject.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 92220ff8bd..0772e7707e 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.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 Newtonsoft.Json; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Objects /// /// Whether this is in Kiai time. /// + [JsonIgnore] public bool Kiai { get; private set; } /// @@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Objects SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - Kiai |= effectPoint.KiaiMode; + Kiai = effectPoint.KiaiMode; // Initialize first sample Samples.ForEach(s => s.ControlPoint = soundPoint); From 0ba8988580a266343c1ab9ad42f0f5a85a7662d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 15:32:39 +0900 Subject: [PATCH 052/239] Don't serialize Author + add SerializableAttributes --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 + osu.Game/Beatmaps/BeatmapMetadata.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 9450022a01..dd66812ea5 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets; namespace osu.Game.Beatmaps { + [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 2efbcd4f15..67d6de49cd 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; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -9,6 +10,7 @@ using osu.Game.Users; namespace osu.Game.Beatmaps { + [Serializable] public class BeatmapMetadata { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] @@ -51,6 +53,7 @@ namespace osu.Game.Beatmaps /// The author of the beatmaps in this set. /// public User Author; + public bool ShouldSerializeAuthor() => false; public string Source { get; set; } From 09f54b06ace0c82a3e500d18acd81e3737b475ff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 15:55:22 +0900 Subject: [PATCH 053/239] Just don't serialize Author altogether for now --- osu.Game/Beatmaps/BeatmapMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 67d6de49cd..a9488ce0e2 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -52,8 +52,8 @@ namespace osu.Game.Beatmaps /// /// The author of the beatmaps in this set. /// + [JsonIgnore] public User Author; - public bool ShouldSerializeAuthor() => false; public string Source { get; set; } From bdf283a4e1b3d3a913beb6dfeb54e78d050d13de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Dec 2017 15:55:38 +0900 Subject: [PATCH 054/239] A bit more cleanup --- osu.Game/IO/Serialization/Converters/TypedListConverter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 7147f9110e..b218f76b53 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -43,9 +43,7 @@ namespace osu.Game.IO.Serialization.Converters var list = new List(); var obj = JObject.Load(reader); - - var lookupTable = new List(); - serializer.Populate(obj["lookup_table"].CreateReader(), lookupTable); + var lookupTable = serializer.Deserialize>(obj["lookup_table"].CreateReader()); foreach (var tok in obj["items"]) { From 76c09ae59ef513cf899f0ca0310252d3d0e2e8b5 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 7 Dec 2017 13:44:47 +0100 Subject: [PATCH 055/239] added comments for local context checking --- osu.Game/Beatmaps/BeatmapStore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 352f793aac..b23f093786 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -34,11 +34,12 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null)) { + // check local context for metadata so we can reuse duplicates from the same set var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); if (contextMetadata != null) - beatmap.Metadata = contextMetadata; + beatmap.Metadata = contextMetadata; // reuse existing else - context.BeatmapMetadata.Attach(beatmap.Metadata); + context.BeatmapMetadata.Attach(beatmap.Metadata); // adding new to context } context.BeatmapSetInfo.Attach(beatmapSet); From 1dcbfab18e97a3c39a01ceeeb42bb1096519269e Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 7 Dec 2017 13:56:37 +0100 Subject: [PATCH 056/239] removed redundant comment --- osu.Game/Beatmaps/BeatmapStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index b23f093786..ac40b3378a 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps // check local context for metadata so we can reuse duplicates from the same set var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); if (contextMetadata != null) - beatmap.Metadata = contextMetadata; // reuse existing + beatmap.Metadata = contextMetadata; else context.BeatmapMetadata.Attach(beatmap.Metadata); // adding new to context } From 95955d68eff35ed128d90aab1ec1ca532bb4e3a1 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 7 Dec 2017 14:14:50 +0100 Subject: [PATCH 057/239] rephrased description of local context checking --- osu.Game/Beatmaps/BeatmapStore.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index ac40b3378a..fb45c17454 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -34,12 +34,14 @@ namespace osu.Game.Beatmaps foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null)) { - // check local context for metadata so we can reuse duplicates from the same set + // If we detect a new metadata object it'll be attached to the current context so it can be reused + // to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local) + // of the corresponding table (.Set()) for matching entries to our criteria. var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); if (contextMetadata != null) beatmap.Metadata = contextMetadata; else - context.BeatmapMetadata.Attach(beatmap.Metadata); // adding new to context + context.BeatmapMetadata.Attach(beatmap.Metadata); } context.BeatmapSetInfo.Attach(beatmapSet); From faa921ba05790c42ee9d6df15f360bb032023bab Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Dec 2017 17:41:10 +0900 Subject: [PATCH 058/239] Fix up post-merge issues --- osu-framework | 2 +- ...layfieldOverlay.cs => TestCaseEditorSelectionLayer.cs} | 8 ++++---- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../Edit/{PlayfieldOverlay.cs => SelectionLayer.cs} | 4 ++-- osu.Game/osu.Game.csproj | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) rename osu.Game.Tests/Visual/{TestCaseEditorPlayfieldOverlay.cs => TestCaseEditorSelectionLayer.cs} (87%) rename osu.Game/Rulesets/Edit/{PlayfieldOverlay.cs => SelectionLayer.cs} (94%) diff --git a/osu-framework b/osu-framework index d231ca9f79..797a351db2 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit d231ca9f79936f3a7f3cff0c7721587755ae168c +Subproject commit 797a351db2e852fef5296453641ffbf6b2f6dc11 diff --git a/osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs similarity index 87% rename from osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs rename to osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index f0da23955d..79f3e4f1d3 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorPlayfieldOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -16,11 +16,11 @@ using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Tests.Visual { - public class TestCaseEditorPlayfieldOverlay : OsuTestCase + public class TestCaseEditorSelectionLayer : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(PlayfieldOverlay) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) }; - public TestCaseEditorPlayfieldOverlay() + public TestCaseEditorSelectionLayer() { var playfield = new OsuEditPlayfield(); playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual Clock = new FramedClock(new StopwatchClock()), Child = playfield }, - new PlayfieldOverlay(playfield) + new SelectionLayer(playfield) }; } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b7536112e3..1596e7e961 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -108,7 +108,7 @@ - + diff --git a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs similarity index 94% rename from osu.Game/Rulesets/Edit/PlayfieldOverlay.cs rename to osu.Game/Rulesets/Edit/SelectionLayer.cs index 98b3bce265..cfe5f8ae5e 100644 --- a/osu.Game/Rulesets/Edit/PlayfieldOverlay.cs +++ b/osu.Game/Rulesets/Edit/SelectionLayer.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit { - public class PlayfieldOverlay : CompositeDrawable + public class SelectionLayer : CompositeDrawable { private readonly static Color4 selection_normal_colour = Color4.White; private readonly static Color4 selection_attached_colour = OsuColour.FromHex("eeaa00"); @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit private readonly Playfield playfield; - public PlayfieldOverlay(Playfield playfield) + public SelectionLayer(Playfield playfield) { this.playfield = playfield; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 660d7cb5e6..d74774e403 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -565,6 +565,7 @@ + From 5341e791029201976bbf401f8cf4615c1086e4c6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Dec 2017 17:51:15 +0900 Subject: [PATCH 059/239] Remove SelectionDragger for now --- osu.Game/Rulesets/Edit/SelectionDragger.cs | 12 ------------ osu.Game/osu.Game.csproj | 1 - 2 files changed, 13 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/SelectionDragger.cs diff --git a/osu.Game/Rulesets/Edit/SelectionDragger.cs b/osu.Game/Rulesets/Edit/SelectionDragger.cs deleted file mode 100644 index 35ea3a375e..0000000000 --- a/osu.Game/Rulesets/Edit/SelectionDragger.cs +++ /dev/null @@ -1,12 +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; - -namespace osu.Game.Rulesets.Edit -{ - public class SelectionDragger : CompositeDrawable - { - - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d74774e403..99156e6658 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -564,7 +564,6 @@ - From e1c04a1f445933c1deb21b26a4bc39e190eb130c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 8 Dec 2017 12:50:04 +0100 Subject: [PATCH 060/239] Added check for "menu music beatmap hash" before undeleting so circles.osu doesn't get imported on Undelete. Also moved the const property to BeatmapManager. --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++++ osu.Game/Screens/Menu/Intro.cs | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e00505d9b3..bc9a3bbacb 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -34,6 +34,11 @@ namespace osu.Game.Beatmaps /// public class BeatmapManager { + /// + /// The hash of the supplied menu music's beatmap set. + /// + public const string MENU_MUSIC_BEATMAP_HASH = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; + /// /// Fired when a new becomes available in the database. /// @@ -341,6 +346,10 @@ namespace osu.Game.Beatmaps public void Undelete(BeatmapSetInfo beatmapSet) { + // So circles.osz doesn't get added as a map + if (beatmapSet.Hash == MENU_MUSIC_BEATMAP_HASH) + return; + lock (importContext) { var context = importContext.Value; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index d7beb34a2f..6a6351305b 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Menu { public class Intro : OsuScreen { - private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; - /// /// Whether we have loaded the menu previously. /// @@ -58,7 +56,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash); + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapManager.MENU_MUSIC_BEATMAP_HASH); if (setInfo == null) { From c97646bea6f8a5e422d433ddae9494e02a9274b0 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Fri, 8 Dec 2017 14:27:07 +0100 Subject: [PATCH 061/239] added confirmation dialog for `Delete ALL beatmaps` --- .../Maintenance/DeleteAllBeatmapsDialog.cs | 46 +++++++++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 15 ++++-- osu.Game/osu.Game.csproj | 1 + 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs new file mode 100644 index 0000000000..7eb2ade562 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.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 System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public class DeleteAllBeatmapsDialog : PopupDialog + { + private BeatmapManager manager; + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmapManager) + { + manager = beatmapManager; + } + + public DeleteAllBeatmapsDialog(Action deleteAction) + { + BodyText = "Everything?"; + + Icon = FontAwesome.fa_trash_o; + HeaderText = @"Confirm deletion of"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Go for it.", + Action = deleteAction + }, + new PopupDialogCancelButton + { + Text = @"No! Abort mission!", + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 4f4f381ae1..18776b4c4c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.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 System.Threading.Tasks; using osu.Framework.Allocation; @@ -16,11 +17,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton deleteButton; private TriangleButton restoreButton; + private DialogOverlay dialogOverlay; + protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps) + private void load(BeatmapManager beatmaps, DialogOverlay dialog) { + dialogOverlay = dialog; + Children = new Drawable[] { importButton = new SettingsButton @@ -38,8 +43,12 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL beatmaps", Action = () => { - deleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + Action deletion = delegate + { + deleteButton.Enabled.Value = false; + Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + }; + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(deletion)); } }, restoreButton = new SettingsButton diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 59f6682569..3f75aab04c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -300,6 +300,7 @@ + From 114604a64205bd108966c6ead5c7c69aa3bc8eb1 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Fri, 8 Dec 2017 14:45:40 +0100 Subject: [PATCH 062/239] removed unused DI --- .../Maintenance/DeleteAllBeatmapsDialog.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index 7eb2ade562..8ae520651a 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -2,12 +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 System.Text; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Overlays.Dialog; @@ -15,14 +9,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { public class DeleteAllBeatmapsDialog : PopupDialog { - private BeatmapManager manager; - - [BackgroundDependencyLoader] - private void load(BeatmapManager beatmapManager) - { - manager = beatmapManager; - } - public DeleteAllBeatmapsDialog(Action deleteAction) { BodyText = "Everything?"; From cdf9ea0d01ddcf43d4728c10dd114faeedd483a0 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Sat, 9 Dec 2017 13:39:11 +0100 Subject: [PATCH 063/239] removed unnecessary variable and fixed the test because of new DI letting it fail --- osu.Game.Tests/Visual/TestCaseSettings.cs | 24 +++++++++++++++---- .../Sections/Maintenance/GeneralSettings.cs | 6 +---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 63d798cd53..d7855a22bf 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -1,6 +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.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -8,16 +10,30 @@ namespace osu.Game.Tests.Visual internal class TestCaseSettings : OsuTestCase { private readonly SettingsOverlay settings; + private readonly DialogOverlay dialogOverlay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); public TestCaseSettings() { - Children = new[] { settings = new MainSettings() }; + settings = new MainSettings + { + State = Visibility.Visible + }; + Add(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - settings.ToggleVisibility(); + dependencies.Cache(dialogOverlay); + + Add(settings); } } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 18776b4c4c..66be5bc0ac 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -17,15 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private TriangleButton deleteButton; private TriangleButton restoreButton; - private DialogOverlay dialogOverlay; - protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, DialogOverlay dialog) + private void load(BeatmapManager beatmaps, DialogOverlay dialogOverlay) { - dialogOverlay = dialog; - Children = new Drawable[] { importButton = new SettingsButton From 8cbd6f32cbe8d9bbb82dd7cae8ccb6748c23b614 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 10 Dec 2017 11:31:37 +0100 Subject: [PATCH 064/239] Moved menu music hash property back to intro and changed check (before undeleting) to "Protected" field. --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +------- osu.Game/Screens/Menu/Intro.cs | 4 +++- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 6dba8106ae..b70f11185f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -34,11 +34,6 @@ namespace osu.Game.Beatmaps /// public class BeatmapManager { - /// - /// The hash of the supplied menu music's beatmap set. - /// - public const string MENU_MUSIC_BEATMAP_HASH = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; - /// /// Fired when a new becomes available in the database. /// @@ -346,8 +341,7 @@ namespace osu.Game.Beatmaps public void Undelete(BeatmapSetInfo beatmapSet) { - // So circles.osz doesn't get added as a map - if (beatmapSet.Hash == MENU_MUSIC_BEATMAP_HASH) + if (beatmapSet.Protected) return; lock (importContext) diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 6a6351305b..d7beb34a2f 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Menu { public class Intro : OsuScreen { + private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; + /// /// Whether we have loaded the menu previously. /// @@ -56,7 +58,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapManager.MENU_MUSIC_BEATMAP_HASH); + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash); if (setInfo == null) { From a8c91a89b57ecfaff38f2fe7f164a25e19107ff5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 16:31:17 +0900 Subject: [PATCH 065/239] Update tasks.json to remove warnings --- .vscode/tasks.json | 77 +++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 35bf9e7a0e..f67d7a8c4e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,63 +2,70 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", - "command": "msbuild", - "type": "shell", - "suppressTaskName": true, - "args": [ - "/property:GenerateFullPaths=true", - "/property:DebugType=portable", - "/verbosity:minimal", - "/m" //parallel compiling support. - ], "tasks": [{ - "taskName": "Build (Debug)", + "label": "Build (Debug)", + "type": "shell", + "command": "msbuild", + "args": [ + "/p:GenerateFullPaths=true", + "/p:DebugType=portable", + "/m", + "/v:m" + ], "group": { "kind": "build", "isDefault": true }, - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Build (Release)", + "label": "Build (Release)", + "type": "shell", + "command": "msbuild", + "args": [ + "/p:Configuration=Release", + "/p:DebugType=portable", + "/p:GenerateFullPaths=true", + "/m", + "/v:m" + ], "group": "build", - "args": [ - "/property:Configuration=Release" - ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean (Debug)", + "label": "Clean (Debug)", + "type": "shell", + "command": "msbuild", "args": [ - "/target:Clean" + "/p:DebugType=portable", + "/p:GenerateFullPaths=true", + "/m", + "/t:Clean", + "/v:m" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean (Release)", + "label": "Clean (Release)", + "type": "shell", + "command": "msbuild", "args": [ - "/target:Clean", - "/property:Configuration=Release" + "/p:Configuration=Release", + "/p:GenerateFullPaths=true", + "/p:DebugType=portable", + "/m", + "/t:Clean", + "/v:m" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" }, { - "taskName": "Clean All", + "label": "Clean All", "dependsOn": [ "Clean (Debug)", "Clean (Release)" ], - "problemMatcher": [ - "$msCompile" - ] + "problemMatcher": "$msCompile" } ] } \ No newline at end of file From 4573cc3322289861742caf7ae6e3c4d06ec8c45b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 17:45:03 +0900 Subject: [PATCH 066/239] Refactor into a new DragBox class, representing a single drag --- osu.Game/Rulesets/Edit/SelectionLayer.cs | 142 ++++++++++-------- .../Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 81 insertions(+), 63 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionLayer.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs index cfe5f8ae5e..21f7a9d796 100644 --- a/osu.Game/Rulesets/Edit/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/SelectionLayer.cs @@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Edit { public class SelectionLayer : CompositeDrawable { - private readonly static Color4 selection_normal_colour = Color4.White; - private readonly static Color4 selection_attached_colour = OsuColour.FromHex("eeaa00"); - - private readonly Container dragBox; - private readonly Playfield playfield; public SelectionLayer(Playfield playfield) @@ -31,56 +26,26 @@ namespace osu.Game.Rulesets.Edit this.playfield = playfield; RelativeSizeAxes = Axes.Both; - - InternalChildren = new[] - { - dragBox = new Container - { - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - MaskingSmoothness = 1, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f - } - } - }; } - private Vector2 dragStartPos; - private RectangleF dragRectangle; - private List capturedHitObjects = new List(); + private DragBox dragBox; protected override bool OnDragStart(InputState state) { - dragStartPos = ToLocalSpace(state.Mouse.NativeState.Position); - dragBox.Position = dragStartPos; - dragBox.Size = Vector2.Zero; - dragBox.FadeTo(1); - dragBox.FadeColour(selection_normal_colour); - dragBox.BorderThickness = 2; + dragBox?.Hide(); + AddInternal(dragBox = new DragBox(ToLocalSpace(state.Mouse.NativeState.Position))); return true; } protected override bool OnDrag(InputState state) { - var dragPos = ToLocalSpace(state.Mouse.NativeState.Position); - dragRectangle = RectangleF.FromLTRB( - Math.Min(dragStartPos.X, dragPos.X), - Math.Min(dragStartPos.Y, dragPos.Y), - Math.Max(dragStartPos.X, dragPos.X), - Math.Max(dragStartPos.Y, dragPos.Y)); - - dragBox.Position = dragRectangle.Location; - dragBox.Size = dragRectangle.Size; + dragBox.ExpandTo(ToLocalSpace(state.Mouse.NativeState.Position)); updateCapturedHitObjects(); - return true; } + private List capturedHitObjects = new List(); private void updateCapturedHitObjects() { capturedHitObjects.Clear(); @@ -90,8 +55,8 @@ namespace osu.Game.Rulesets.Edit if (!obj.IsAlive || !obj.IsPresent) continue; - var objectPosition = obj.Parent.ToScreenSpace(obj.SelectionPoint); - if (dragRectangle.Contains(ToLocalSpace(objectPosition))) + var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); + if (dragBox.ScreenSpaceDrawQuad.Contains(objectPosition)) capturedHitObjects.Add(obj); } } @@ -99,29 +64,82 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDragEnd(InputState state) { if (capturedHitObjects.Count == 0) - dragBox.FadeOut(400, Easing.OutQuint); + dragBox.Hide(); else - { - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - foreach (var obj in capturedHitObjects) - { - topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(obj.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(obj.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - dragBox.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint) - .FadeColour(selection_attached_colour, 200, Easing.OutQuint); - dragBox.BorderThickness = 3; - } + dragBox.Capture(capturedHitObjects); + return true; + } + protected override bool OnClick(InputState state) + { + dragBox?.Hide(); return true; } } + + public class DragBox : CompositeDrawable + { + private readonly Drawable background; + private readonly Vector2 startPos; + + public DragBox(Vector2 startPos) + { + this.startPos = startPos; + + Masking = true; + BorderColour = Color4.White; + BorderThickness = 2; + MaskingSmoothness = 1; + InternalChild = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + AlwaysPresent = true + }; + } + + public void ExpandTo(Vector2 position) + { + var trackingRectangle = RectangleF.FromLTRB( + Math.Min(startPos.X, position.X), + Math.Min(startPos.Y, position.Y), + Math.Max(startPos.X, position.X), + Math.Max(startPos.Y, position.Y)); + + Position = trackingRectangle.Location; + Size = trackingRectangle.Size; + } + + public void Capture(IEnumerable hitObjects) + { + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + foreach (var obj in hitObjects) + { + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + this.MoveTo(topLeft, 200, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + + background.FadeOut(200); + + BorderThickness = 3; + } + + private bool isActive = true; + public override bool HandleInput => isActive; + + public override void Hide() + { + isActive = false; + this.FadeOut(400, Easing.OutQuint).Expire(); + } + } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9eecb9b4f5..c81039c25d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The local point that causes this to be selected in the Editor. /// - public virtual Vector2 SelectionPoint => DrawPosition; + public virtual Vector2 SelectionPoint => Vector2.Zero; /// /// The local rectangle that outlines this for selections in the Editor. From b28306d3c0fcd72ced0f9a9753e70dd6ff47636a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 18:06:05 +0900 Subject: [PATCH 067/239] Fix incorrect SelectionPoint --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index c81039c25d..834bfd53a2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// The local point that causes this to be selected in the Editor. /// - public virtual Vector2 SelectionPoint => Vector2.Zero; + public virtual Vector2 SelectionPoint => DrawSize / 2f; /// /// The local rectangle that outlines this for selections in the Editor. From 47bd97363e625ccdf63bc337d158c00149d997d6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 11 Dec 2017 15:05:12 +0530 Subject: [PATCH 068/239] Use ShortName for mode. - Also set the ruleset when fetching scores in BeatmapSetOverlay --- .../Online/API/Requests/GetScoresRequest.cs | 20 +------------------ osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- .../Select/Leaderboards/Leaderboard.cs | 2 +- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 54d656eeca..4379daa47b 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -69,25 +69,7 @@ namespace osu.Game.Online.API.Requests break; } - switch (ruleset?.Name) - { - default: - case @"osu!": - req.AddParameter(@"mode", @"osu"); - break; - - case @"osu!taiko": - req.AddParameter(@"mode", @"taiko"); - break; - - case @"osu!catch": - req.AddParameter(@"mode", @"catch"); - break; - - case @"osu!mania": - req.AddParameter(@"mode", @"mania"); - break; - } + req.AddParameter(@"mode", ruleset?.ShortName ?? @"osu"); return req; } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 0d658b346e..0a88f586b5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -104,7 +104,7 @@ namespace osu.Game.Overlays scores.IsLoading = true; - getScoresRequest = new GetScoresRequest(beatmap); + getScoresRequest = new GetScoresRequest(beatmap, beatmap.Ruleset); getScoresRequest.Success += r => { scores.Scores = r.Scores; diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index e80f502e73..36dc254792 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Select.Leaderboards { Height = 26; Width = 26; - Child = new ClickableContainer + Child = new OsuClickableContainer { AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, From 5147b342dceb700dd585eaa9944f62f610642f2b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 19:07:40 +0900 Subject: [PATCH 069/239] More refactorings + add markers Move --- osu.Game/Rulesets/Edit/SelectionLayer.cs | 197 ++++++++++++++++++----- 1 file changed, 158 insertions(+), 39 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionLayer.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs index 21f7a9d796..a8c67e8ce8 100644 --- a/osu.Game/Rulesets/Edit/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/SelectionLayer.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Edit { @@ -28,45 +30,32 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; } - private DragBox dragBox; + private DragContainer dragBox; protected override bool OnDragStart(InputState state) { dragBox?.Hide(); - AddInternal(dragBox = new DragBox(ToLocalSpace(state.Mouse.NativeState.Position))); + AddInternal(dragBox = new DragContainer(ToLocalSpace(state.Mouse.NativeState.Position)) + { + CapturableObjects = playfield.HitObjects.Objects + }); + return true; } protected override bool OnDrag(InputState state) { - dragBox.ExpandTo(ToLocalSpace(state.Mouse.NativeState.Position)); - - updateCapturedHitObjects(); + dragBox.Track(ToLocalSpace(state.Mouse.NativeState.Position)); + dragBox.UpdateCapture(); return true; } - private List capturedHitObjects = new List(); - private void updateCapturedHitObjects() - { - capturedHitObjects.Clear(); - - foreach (var obj in playfield.HitObjects.Objects) - { - if (!obj.IsAlive || !obj.IsPresent) - continue; - - var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); - if (dragBox.ScreenSpaceDrawQuad.Contains(objectPosition)) - capturedHitObjects.Add(obj); - } - } - protected override bool OnDragEnd(InputState state) { - if (capturedHitObjects.Count == 0) + if (dragBox.CapturedHitObjects.Count == 0) dragBox.Hide(); else - dragBox.Capture(capturedHitObjects); + dragBox.FinishCapture(); return true; } @@ -77,28 +66,53 @@ namespace osu.Game.Rulesets.Edit } } - public class DragBox : CompositeDrawable + public class DragContainer : CompositeDrawable { + public IEnumerable CapturableObjects; + + private readonly Container borderMask; private readonly Drawable background; + private readonly MarkerContainer markers; + + private Color4 captureFinishedColour; + private readonly Vector2 startPos; - public DragBox(Vector2 startPos) + public DragContainer(Vector2 startPos) { this.startPos = startPos; - Masking = true; - BorderColour = Color4.White; - BorderThickness = 2; - MaskingSmoothness = 1; - InternalChild = background = new Box + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - AlwaysPresent = true + borderMask = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + MaskingSmoothness = 1, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + AlwaysPresent = true + }, + }, + markers = new MarkerContainer + { + RelativeSizeAxes = Axes.Both, + Alpha = 0 + } }; } - public void ExpandTo(Vector2 position) + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + captureFinishedColour = colours.Yellow; + } + + public void Track(Vector2 position) { var trackingRectangle = RectangleF.FromLTRB( Math.Min(startPos.X, position.X), @@ -110,13 +124,31 @@ namespace osu.Game.Rulesets.Edit Size = trackingRectangle.Size; } - public void Capture(IEnumerable hitObjects) + private List capturedHitObjects = new List(); + public IReadOnlyList CapturedHitObjects => capturedHitObjects; + + public void UpdateCapture() + { + capturedHitObjects.Clear(); + + foreach (var obj in CapturableObjects) + { + if (!obj.IsAlive || !obj.IsPresent) + continue; + + var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); + if (ScreenSpaceDrawQuad.Contains(objectPosition)) + capturedHitObjects.Add(obj); + } + } + + public void FinishCapture() { // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - foreach (var obj in hitObjects) + foreach (var obj in capturedHitObjects) { topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); @@ -126,11 +158,13 @@ namespace osu.Game.Rulesets.Edit bottomRight += new Vector2(5); this.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint) + .FadeColour(captureFinishedColour, 200); - background.FadeOut(200); + borderMask.BorderThickness = 3; - BorderThickness = 3; + background.Delay(50).FadeOut(200); + markers.FadeIn(200); } private bool isActive = true; @@ -142,4 +176,89 @@ namespace osu.Game.Rulesets.Edit this.FadeOut(400, Easing.OutQuint).Expire(); } } + + public class MarkerContainer : CompositeDrawable + { + public Action ResizeRequested; + + public MarkerContainer() + { + Padding = new MarginPadding(1); + + InternalChildren = new Drawable[] + { + new Marker + { + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.TopRight, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.BottomRight, + Origin = Anchor.Centre + }, + new CentreMarker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }; + } + } + + public class Marker : CompositeDrawable + { + private float marker_size = 10; + + public Marker() + { + Size = new Vector2(marker_size); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + } + + public class CentreMarker : CompositeDrawable + { + private float marker_size = 10; + private float line_width = 2; + + public CentreMarker() + { + Size = new Vector2(marker_size); + + InternalChildren = new[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = line_width + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = line_width + }, + }; + } + } } From f58c554d19751c6ed7587799f6e7f1d790042711 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Dec 2017 20:22:10 +0900 Subject: [PATCH 070/239] Add per-difficulty filtering support Filters based on selected ruleset --- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 6 +- osu.Game/Beatmaps/Drawables/BeatmapPanel.cs | 2 + osu.Game/Screens/Select/BeatmapCarousel.cs | 66 ++++++++++++++------- osu.Game/Screens/Select/FilterCriteria.cs | 10 +++- osu.sln.DotSettings | 1 + 5 files changed, 60 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 163dd7fbe9..59ff7b06e5 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -40,6 +40,7 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapSetInfo BeatmapSet; private BeatmapGroupState state; + public BeatmapGroupState State { get { return state; } @@ -52,7 +53,8 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapGroupState.Expanded: Header.State = PanelSelectedState.Selected; foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected; + panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : + !panel.Filtered ? PanelSelectedState.NotSelected : PanelSelectedState.Hidden; break; case BeatmapGroupState.Collapsed: Header.State = PanelSelectedState.NotSelected; @@ -108,7 +110,7 @@ namespace osu.Game.Beatmaps.Drawables //we want to make sure one of our children is selected in the case none have been selected yet. if (SelectedPanel == null) - BeatmapPanels.First().State = PanelSelectedState.Selected; + BeatmapPanels.First(p => !p.Filtered).State = PanelSelectedState.Selected; } private void panelGainedSelection(BeatmapPanel panel) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index e6bf08eb9f..c94f4fdc57 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -61,6 +61,8 @@ namespace osu.Game.Beatmaps.Drawables return base.OnClick(state); } + public bool Filtered { get; set; } + protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) { if (!IsLoaded) return; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 578b02d90a..fb6761eb8d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Select var newSelection = newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID); - if(newSelection == null && oldGroup != null && selectedPanel != null) + if (newSelection == null && oldGroup != null && selectedPanel != null) newSelection = newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, oldGroup.BeatmapPanels.IndexOf(selectedPanel))]; selectGroup(newGroup, newSelection); @@ -178,42 +178,62 @@ namespace osu.Game.Screens.Select SelectionChanged?.Invoke(null); } + /// + /// Increment selection in the carousel in a chosen direction. + /// + /// The direction to increment. Negative is backwards. + /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { + // todo: we may want to refactor and remove this as an optimisation in the future. if (groups.All(g => g.State == BeatmapGroupState.Hidden)) { selectNullBeatmap(); return; } - if (!skipDifficulties && selectedGroup != null) - { - int i = selectedGroup.BeatmapPanels.IndexOf(selectedPanel) + direction; + int originalIndex = Math.Max(0, groups.IndexOf(selectedGroup)); + int currentIndex = originalIndex; - if (i >= 0 && i < selectedGroup.BeatmapPanels.Count) - { - //changing difficulty panel, not set. - selectGroup(selectedGroup, selectedGroup.BeatmapPanels[i]); - return; - } - } + // local function to increment the index in the required direction, wrapping over extremities. + int incrementIndex() => currentIndex = (currentIndex + direction + groups.Count) % groups.Count; - int startIndex = Math.Max(0, groups.IndexOf(selectedGroup)); - int index = startIndex; + // in the case we are skipping difficulties, we want to increment the index once before starting to find out new target + // (we don't care about the currently selected group). + if (skipDifficulties) + incrementIndex(); do { - index = (index + direction + groups.Count) % groups.Count; - if (groups[index].State != BeatmapGroupState.Hidden) - { - if (skipDifficulties) - SelectBeatmap(groups[index].SelectedPanel != null ? groups[index].SelectedPanel.Beatmap : groups[index].BeatmapPanels.First().Beatmap); - else - SelectBeatmap(direction == 1 ? groups[index].BeatmapPanels.First().Beatmap : groups[index].BeatmapPanels.Last().Beatmap); + var group = groups[currentIndex]; + if (group.State == BeatmapGroupState.Hidden) continue; + + // we are only interested in non-filtered panels. + IEnumerable validPanels = group.BeatmapPanels.Where(p => !p.Filtered); + + // if we are considering difficulties, we need to do a few extrea steps. + if (!skipDifficulties) + { + // we want to reverse the panel order if we are searching backwards. + if (direction < 0) + validPanels = validPanels.Reverse(); + + // if we are currently on the selected panel, let's try to find a valid difficulty before leaving to the next group. + // the first valid difficulty is found by skipping to the selected panel and then one further. + if (currentIndex == originalIndex) + validPanels = validPanels.SkipWhile(p => p != selectedPanel).Skip(1); + } + + var next = validPanels.FirstOrDefault(); + + // at this point, we can perform the selection change if we have a valid new target, else continue to increment in the specified direction. + if (next != null) + { + selectGroup(group, next); return; } - } while (index != startIndex); + } while (incrementIndex() != originalIndex); } private IEnumerable getVisibleGroups() => groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden); @@ -416,6 +436,8 @@ namespace osu.Game.Screens.Select foreach (BeatmapPanel panel in group.BeatmapPanels) { + if (panel.Filtered) continue; + if (panel == selectedPanel) selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; @@ -460,7 +482,7 @@ namespace osu.Game.Screens.Select try { if (panel == null) - panel = group.BeatmapPanels.First(); + panel = group.BeatmapPanels.First(p => !p.Filtered); if (selectedPanel == panel) return; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c1355bfa63..96778291a1 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -18,19 +19,26 @@ namespace osu.Game.Screens.Select public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; + private bool canConvert(BeatmapInfo beatmapInfo) => beatmapInfo.RulesetID == Ruleset.ID || beatmapInfo.RulesetID == 0 && Ruleset.ID > 0 && AllowConvertedBeatmaps; + public void Filter(List groups) { foreach (var g in groups) { var set = g.BeatmapSet; - bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); + // we only support converts from osu! mode to other modes for now. + // in the future this will have to change, at which point this condition will become a touch more complicated. + bool hasCurrentMode = set.Beatmaps.Any(canConvert); bool match = hasCurrentMode; if (!string.IsNullOrEmpty(SearchText)) match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); + foreach (var panel in g.BeatmapPanels) + panel.Filtered = !canConvert(panel.Beatmap); + switch (g.State) { case BeatmapGroupState.Hidden: diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 76929dcbb3..2f52881d6d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -602,6 +602,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> From 970e55fc4a07064437f0cf878de2a160d62520f3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Dec 2017 20:49:27 +0900 Subject: [PATCH 071/239] Update difficulty icons on headers to match filtered difficulties --- osu.Game/Beatmaps/Drawables/BeatmapPanel.cs | 3 ++- osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs | 17 +++++++++++++++-- osu.Game/Screens/Select/FilterCriteria.cs | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index c94f4fdc57..d8ba1e9195 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -61,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables return base.OnClick(state); } - public bool Filtered { get; set; } + public BindableBool Filtered = new BindableBool(); protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) { diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index 85daefea57..9bb7b5c737 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -7,6 +7,7 @@ using System.Linq; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -157,8 +158,7 @@ namespace osu.Game.Beatmaps.Drawables if (panels == null) throw new ArgumentNullException(nameof(panels)); - foreach (var p in panels) - difficultyIcons.Add(new DifficultyIcon(p.Beatmap)); + difficultyIcons.AddRange(panels.Select(p => new FilterableDifficultyIcon(p))); } public MenuItem[] ContextMenuItems @@ -178,5 +178,18 @@ namespace osu.Game.Beatmaps.Drawables return items.ToArray(); } } + + public class FilterableDifficultyIcon : DifficultyIcon + { + private readonly BindableBool filtered = new BindableBool(); + + public FilterableDifficultyIcon(BeatmapPanel panel) + : base(panel.Beatmap) + { + filtered.BindTo(panel.Filtered); + filtered.ValueChanged += v => this.FadeTo(v ? 0.1f : 1, 100); + filtered.TriggerChange(); + } + } } } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index 96778291a1..f410c69212 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); foreach (var panel in g.BeatmapPanels) - panel.Filtered = !canConvert(panel.Beatmap); + panel.Filtered.Value = !canConvert(panel.Beatmap); switch (g.State) { From 25d80a36be257ad4b7b467c1d40431069b7912fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 21:03:34 +0900 Subject: [PATCH 072/239] Finish up logical implementation of markers --- osu.Game/Rulesets/Edit/SelectionLayer.cs | 147 +++++++++++++++++++---- 1 file changed, 123 insertions(+), 24 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionLayer.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs index a8c67e8ce8..4e8fba11b0 100644 --- a/osu.Game/Rulesets/Edit/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/SelectionLayer.cs @@ -84,24 +84,36 @@ namespace osu.Game.Rulesets.Edit InternalChildren = new Drawable[] { - borderMask = new Container + new Container { RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - MaskingSmoothness = 1, - Child = background = new Box + Padding = new MarginPadding(-1), + Child = borderMask = new Container { RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - AlwaysPresent = true - }, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + MaskingSmoothness = 1, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + AlwaysPresent = true + }, + } }, markers = new MarkerContainer { RelativeSizeAxes = Axes.Both, - Alpha = 0 + Alpha = 0, + GetCaptureRectangle = () => trackingRectangle, + UpdateCapture = r => + { + trackRectangle(r); + UpdateCapture(); + }, + FinishCapture = FinishCapture } }; } @@ -112,16 +124,20 @@ namespace osu.Game.Rulesets.Edit captureFinishedColour = colours.Yellow; } - public void Track(Vector2 position) - { - var trackingRectangle = RectangleF.FromLTRB( - Math.Min(startPos.X, position.X), - Math.Min(startPos.Y, position.Y), - Math.Max(startPos.X, position.X), - Math.Max(startPos.Y, position.Y)); + public void Track(Vector2 position) => trackRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, position.X, position.Y)); - Position = trackingRectangle.Location; - Size = trackingRectangle.Size; + private RectangleF trackingRectangle; + private void trackRectangle(RectangleF rectangle) + { + trackingRectangle = rectangle; + + Position = new Vector2( + Math.Min(rectangle.Left, rectangle.Right), + Math.Min(rectangle.Top, rectangle.Bottom)); + + Size = new Vector2( + Math.Max(rectangle.Left, rectangle.Right) - Position.X, + Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); } private List capturedHitObjects = new List(); @@ -144,6 +160,12 @@ namespace osu.Game.Rulesets.Edit public void FinishCapture() { + if (CapturedHitObjects.Count == 0) + { + Hide(); + return; + } + // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); @@ -158,10 +180,12 @@ namespace osu.Game.Rulesets.Edit bottomRight += new Vector2(5); this.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint) - .FadeColour(captureFinishedColour, 200); + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + + trackingRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); borderMask.BorderThickness = 3; + borderMask.FadeColour(captureFinishedColour, 200); background.Delay(50).FadeOut(200); markers.FadeIn(200); @@ -179,12 +203,12 @@ namespace osu.Game.Rulesets.Edit public class MarkerContainer : CompositeDrawable { - public Action ResizeRequested; + public Func GetCaptureRectangle; + public Action UpdateCapture; + public Action FinishCapture; public MarkerContainer() { - Padding = new MarginPadding(1); - InternalChildren = new Drawable[] { new Marker @@ -193,31 +217,62 @@ namespace osu.Game.Rulesets.Edit Origin = Anchor.Centre }, new Marker + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre + }, + new Marker { Anchor = Anchor.TopRight, Origin = Anchor.Centre }, new Marker + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre + }, + new Marker { Anchor = Anchor.BottomLeft, Origin = Anchor.Centre }, new Marker + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre + }, + new Marker { Anchor = Anchor.BottomRight, Origin = Anchor.Centre }, + new Marker + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre + }, new CentreMarker { Anchor = Anchor.Centre, Origin = Anchor.Centre } }; + + InternalChildren.OfType().ForEach(m => + { + m.GetCaptureRectangle = () => GetCaptureRectangle(); + m.UpdateCapture = r => UpdateCapture(r); + m.FinishCapture = () => FinishCapture(); + }); } } public class Marker : CompositeDrawable { + public Func GetCaptureRectangle; + public Action UpdateCapture; + public Action FinishCapture; + private float marker_size = 10; public Marker() @@ -231,6 +286,44 @@ namespace osu.Game.Rulesets.Edit Child = new Box { RelativeSizeAxes = Axes.Both } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + var currentRectangle = GetCaptureRectangle(); + + float left = currentRectangle.Left; + float right = currentRectangle.Right; + float top = currentRectangle.Top; + float bottom = currentRectangle.Bottom; + + // Apply modifications to the capture rectangle + if ((Anchor & Anchor.y0) > 0) + top += state.Mouse.Delta.Y; + else if ((Anchor & Anchor.y2) > 0) + bottom += state.Mouse.Delta.Y; + + if ((Anchor & Anchor.x0) > 0) + left += state.Mouse.Delta.X; + else if ((Anchor & Anchor.x2) > 0) + right += state.Mouse.Delta.X; + + UpdateCapture(RectangleF.FromLTRB(left, top, right, bottom)); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + FinishCapture(); + return true; + } } public class CentreMarker : CompositeDrawable @@ -260,5 +353,11 @@ namespace osu.Game.Rulesets.Edit }, }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } } } From fb92b3551e8f3a2705293d7032e2702adbadd618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Dec 2017 21:12:06 +0900 Subject: [PATCH 073/239] Correct panel y positions when filtered panels are present --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fb6761eb8d..aa41cad67e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -436,18 +436,16 @@ namespace osu.Game.Screens.Select foreach (BeatmapPanel panel in group.BeatmapPanels) { - if (panel.Filtered) continue; - if (panel == selectedPanel) selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; panel.MoveToX(-50, 500, Easing.OutExpo); //on first display we want to begin hidden under our group's header. - if (panel.Alpha == 0) + if (panel.Filtered || panel.Alpha == 0) panel.MoveToY(headerY); - movePanel(panel, true, animated, ref currentY); + movePanel(panel, !panel.Filtered, animated, ref currentY); } } else From c12c2416335fb9fa65a26075e472bb2934616612 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 21:27:19 +0900 Subject: [PATCH 074/239] Implement hover colours --- osu.Game/Rulesets/Edit/SelectionLayer.cs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/SelectionLayer.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs index 4e8fba11b0..2c59de06f0 100644 --- a/osu.Game/Rulesets/Edit/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/SelectionLayer.cs @@ -269,11 +269,14 @@ namespace osu.Game.Rulesets.Edit public class Marker : CompositeDrawable { + private const float marker_size = 10; + public Func GetCaptureRectangle; public Action UpdateCapture; public Action FinishCapture; - private float marker_size = 10; + private Color4 normalColour; + private Color4 hoverColour; public Marker() { @@ -290,7 +293,8 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OsuColour colours) { - Colour = colours.Yellow; + Colour = normalColour = colours.Yellow; + hoverColour = colours.YellowDarker; } protected override bool OnDragStart(InputState state) => true; @@ -324,12 +328,23 @@ namespace osu.Game.Rulesets.Edit FinishCapture(); return true; } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(normalColour, 100); + } } public class CentreMarker : CompositeDrawable { - private float marker_size = 10; - private float line_width = 2; + private const float marker_size = 10; + private const float line_width = 2; public CentreMarker() { From b10240d7ef41f1a41edbfa6d0717a907823be6ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Dec 2017 21:31:26 +0900 Subject: [PATCH 075/239] Handle the case where the selected panel is no longer a valid selection better --- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 45 +++++++++++---------- osu.Game/Screens/Select/BeatmapCarousel.cs | 10 +++-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 59ff7b06e5..e0536584bd 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -47,31 +47,34 @@ namespace osu.Game.Beatmaps.Drawables set { state = value; - - switch (value) - { - case BeatmapGroupState.Expanded: - Header.State = PanelSelectedState.Selected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : - !panel.Filtered ? PanelSelectedState.NotSelected : PanelSelectedState.Hidden; - break; - case BeatmapGroupState.Collapsed: - Header.State = PanelSelectedState.NotSelected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - case BeatmapGroupState.Hidden: - Header.State = PanelSelectedState.Hidden; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - } - + UpdateState(); StateChanged?.Invoke(state); } } + public void UpdateState() + { + switch (state) + { + case BeatmapGroupState.Expanded: + Header.State = PanelSelectedState.Selected; + foreach (BeatmapPanel panel in BeatmapPanels) + panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : + !panel.Filtered ? PanelSelectedState.NotSelected : PanelSelectedState.Hidden; + break; + case BeatmapGroupState.Collapsed: + Header.State = PanelSelectedState.NotSelected; + foreach (BeatmapPanel panel in BeatmapPanels) + panel.State = PanelSelectedState.Hidden; + break; + case BeatmapGroupState.Hidden: + Header.State = PanelSelectedState.Hidden; + foreach (BeatmapPanel panel in BeatmapPanels) + panel.State = PanelSelectedState.Hidden; + break; + } + } + public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager) { if (beatmapSet == null) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index aa41cad67e..f6b832d0f7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -327,6 +327,8 @@ namespace osu.Game.Screens.Select computeYPositions(); + selectedGroup?.UpdateState(); + if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden) SelectNext(); else @@ -441,11 +443,13 @@ namespace osu.Game.Screens.Select panel.MoveToX(-50, 500, Easing.OutExpo); + bool isHidden = panel.State == PanelSelectedState.Hidden; + //on first display we want to begin hidden under our group's header. - if (panel.Filtered || panel.Alpha == 0) + if (isHidden || panel.Alpha == 0) panel.MoveToY(headerY); - movePanel(panel, !panel.Filtered, animated, ref currentY); + movePanel(panel, !isHidden, animated, ref currentY); } } else @@ -479,7 +483,7 @@ namespace osu.Game.Screens.Select { try { - if (panel == null) + if (panel == null || panel.Filtered == true) panel = group.BeatmapPanels.First(p => !p.Filtered); if (selectedPanel == panel) return; From 8dea999908ab034ba6e6825ac66707bf61ef7cca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Dec 2017 21:35:47 +0900 Subject: [PATCH 076/239] Order difficulty icons by ruleset --- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index e0536584bd..8b2670f39c 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps.Drawables RelativeSizeAxes = Axes.X, }; - BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b) + BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.RulesetID).ThenBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b) { Alpha = 0, GainedSelection = panelGainedSelection, From 2f1063c5c05cab75c2370f72fed6c634dffc1fb0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 21:56:58 +0900 Subject: [PATCH 077/239] Refactor into separate files + add xmldocs + rename some methods --- .../Edit/Layers/Selection/CentreMarker.cs | 47 +++ .../Edit/Layers/Selection/DragSelector.cs | 170 ++++++++ .../Rulesets/Edit/Layers/Selection/Marker.cs | 102 +++++ .../Edit/Layers/Selection/MarkerContainer.cs | 89 +++++ .../Edit/Layers/Selection/SelectionLayer.cs | 55 +++ osu.Game/Rulesets/Edit/SelectionLayer.cs | 378 ------------------ osu.Game/osu.Game.csproj | 8 +- 7 files changed, 469 insertions(+), 380 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs delete mode 100644 osu.Game/Rulesets/Edit/SelectionLayer.cs diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs new file mode 100644 index 0000000000..6da6c72849 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs @@ -0,0 +1,47 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents the centre of a . + /// + public class CentreMarker : CompositeDrawable + { + private const float marker_size = 10; + private const float line_width = 2; + + public CentreMarker() + { + Size = new Vector2(marker_size); + + InternalChildren = new[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = line_width + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = line_width + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs new file mode 100644 index 0000000000..1e86988c01 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// A box that represents a drag selection. + /// + public class DragSelector : CompositeDrawable + { + /// + /// The s that can be selected through a drag-selection. + /// + public IEnumerable CapturableObjects; + + private readonly Container borderMask; + private readonly Drawable background; + private readonly MarkerContainer markers; + + private Color4 captureFinishedColour; + + private readonly Vector2 startPos; + + /// + /// Creates a new . + /// + /// The point at which the drag was initiated, in the parent's coordinates. + public DragSelector(Vector2 startPos) + { + this.startPos = startPos; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(-1), + Child = borderMask = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + MaskingSmoothness = 1, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + AlwaysPresent = true + }, + } + }, + markers = new MarkerContainer + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + GetDragRectangle = () => dragRectangle, + UpdateDragRectangle = r => + { + updateDragRectangle(r); + BeginCapture(); + }, + FinishCapture = FinishCapture + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + captureFinishedColour = colours.Yellow; + } + + /// + /// The secondary corner of the drag selection box. A rectangle will be fit between the starting position and this value. + /// + public Vector2 DragEndPosition { set => updateDragRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, value.X, value.Y)); } + + private RectangleF dragRectangle; + private void updateDragRectangle(RectangleF rectangle) + { + dragRectangle = rectangle; + + Position = new Vector2( + Math.Min(rectangle.Left, rectangle.Right), + Math.Min(rectangle.Top, rectangle.Bottom)); + + Size = new Vector2( + Math.Max(rectangle.Left, rectangle.Right) - Position.X, + Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); + } + + private readonly List capturedHitObjects = new List(); + public IReadOnlyList CapturedHitObjects => capturedHitObjects; + + /// + /// Processes hitobjects to determine which ones are captured by the drag selection. + /// Captured hitobjects will be enclosed by the drag selection upon . + /// + public void BeginCapture() + { + capturedHitObjects.Clear(); + + foreach (var obj in CapturableObjects) + { + if (!obj.IsAlive || !obj.IsPresent) + continue; + + var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); + if (ScreenSpaceDrawQuad.Contains(objectPosition)) + capturedHitObjects.Add(obj); + } + } + + /// + /// Encloses hitobjects captured through in the drag selection box. + /// + public void FinishCapture() + { + if (CapturedHitObjects.Count == 0) + { + Hide(); + return; + } + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + foreach (var obj in capturedHitObjects) + { + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); + } + + topLeft -= new Vector2(5); + bottomRight += new Vector2(5); + + this.MoveTo(topLeft, 200, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + + dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); + + borderMask.BorderThickness = 3; + borderMask.FadeColour(captureFinishedColour, 200); + + // Transform into markers to let the user modify the drag selection further. + background.Delay(50).FadeOut(200); + markers.FadeIn(200); + } + + private bool isActive = true; + public override bool HandleInput => isActive; + + public override void Hide() + { + isActive = false; + this.FadeOut(400, Easing.OutQuint).Expire(); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs new file mode 100644 index 0000000000..3519adb3b3 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs @@ -0,0 +1,102 @@ +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents a marker visible on the border of a which exposes + /// properties that are used to resize a . + /// + public class Marker : CompositeDrawable + { + private const float marker_size = 10; + + /// + /// Invoked when this requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when this wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when this has finished updates to the drag rectangle. + /// + public Action FinishCapture; + + private Color4 normalColour; + private Color4 hoverColour; + + public Marker() + { + Size = new Vector2(marker_size); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = normalColour = colours.Yellow; + hoverColour = colours.YellowDarker; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + var currentRectangle = GetDragRectangle(); + + float left = currentRectangle.Left; + float right = currentRectangle.Right; + float top = currentRectangle.Top; + float bottom = currentRectangle.Bottom; + + // Apply modifications to the capture rectangle + if ((Anchor & Anchor.y0) > 0) + top += state.Mouse.Delta.Y; + else if ((Anchor & Anchor.y2) > 0) + bottom += state.Mouse.Delta.Y; + + if ((Anchor & Anchor.x0) > 0) + left += state.Mouse.Delta.X; + else if ((Anchor & Anchor.x2) > 0) + right += state.Mouse.Delta.X; + + UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + FinishCapture(); + return true; + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(normalColour, 100); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs new file mode 100644 index 0000000000..3b35528cf1 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// A that has s around its border. + /// + public class MarkerContainer : CompositeDrawable + { + /// + /// Invoked when a requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when a wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when a has finished updates to the drag rectangle. + /// + public Action FinishCapture; + + public MarkerContainer() + { + InternalChildren = new Drawable[] + { + new Marker + { + Anchor = Anchor.TopLeft, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.TopRight, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.BottomRight, + Origin = Anchor.Centre + }, + new Marker + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.Centre + }, + new CentreMarker + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }; + + InternalChildren.OfType().ForEach(m => + { + m.GetDragRectangle = () => GetDragRectangle(); + m.UpdateDragRectangle = r => UpdateDragRectangle(r); + m.FinishCapture = () => FinishCapture(); + }); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs new file mode 100644 index 0000000000..a38be68461 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -0,0 +1,55 @@ +// 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.Input; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class SelectionLayer : CompositeDrawable + { + private readonly Playfield playfield; + + public SelectionLayer(Playfield playfield) + { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + } + + private DragSelector selector; + + protected override bool OnDragStart(InputState state) + { + // Hide the previous drag box - we won't be working with it any longer + selector?.Hide(); + AddInternal(selector = new DragSelector(ToLocalSpace(state.Mouse.NativeState.Position)) + { + CapturableObjects = playfield.HitObjects.Objects + }); + + return true; + } + + protected override bool OnDrag(InputState state) + { + selector.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); + selector.BeginCapture(); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + selector.FinishCapture(); + return true; + } + + protected override bool OnClick(InputState state) + { + selector?.Hide(); + return true; + } + } +} diff --git a/osu.Game/Rulesets/Edit/SelectionLayer.cs b/osu.Game/Rulesets/Edit/SelectionLayer.cs deleted file mode 100644 index 2c59de06f0..0000000000 --- a/osu.Game/Rulesets/Edit/SelectionLayer.cs +++ /dev/null @@ -1,378 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using OpenTK; -using OpenTK.Graphics; -using System.Collections.Generic; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI; -using System.Linq; -using osu.Game.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; - -namespace osu.Game.Rulesets.Edit -{ - public class SelectionLayer : CompositeDrawable - { - private readonly Playfield playfield; - - public SelectionLayer(Playfield playfield) - { - this.playfield = playfield; - - RelativeSizeAxes = Axes.Both; - } - - private DragContainer dragBox; - - protected override bool OnDragStart(InputState state) - { - dragBox?.Hide(); - AddInternal(dragBox = new DragContainer(ToLocalSpace(state.Mouse.NativeState.Position)) - { - CapturableObjects = playfield.HitObjects.Objects - }); - - return true; - } - - protected override bool OnDrag(InputState state) - { - dragBox.Track(ToLocalSpace(state.Mouse.NativeState.Position)); - dragBox.UpdateCapture(); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - if (dragBox.CapturedHitObjects.Count == 0) - dragBox.Hide(); - else - dragBox.FinishCapture(); - return true; - } - - protected override bool OnClick(InputState state) - { - dragBox?.Hide(); - return true; - } - } - - public class DragContainer : CompositeDrawable - { - public IEnumerable CapturableObjects; - - private readonly Container borderMask; - private readonly Drawable background; - private readonly MarkerContainer markers; - - private Color4 captureFinishedColour; - - private readonly Vector2 startPos; - - public DragContainer(Vector2 startPos) - { - this.startPos = startPos; - - InternalChildren = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(-1), - Child = borderMask = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - MaskingSmoothness = 1, - Child = background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - AlwaysPresent = true - }, - } - }, - markers = new MarkerContainer - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - GetCaptureRectangle = () => trackingRectangle, - UpdateCapture = r => - { - trackRectangle(r); - UpdateCapture(); - }, - FinishCapture = FinishCapture - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - captureFinishedColour = colours.Yellow; - } - - public void Track(Vector2 position) => trackRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, position.X, position.Y)); - - private RectangleF trackingRectangle; - private void trackRectangle(RectangleF rectangle) - { - trackingRectangle = rectangle; - - Position = new Vector2( - Math.Min(rectangle.Left, rectangle.Right), - Math.Min(rectangle.Top, rectangle.Bottom)); - - Size = new Vector2( - Math.Max(rectangle.Left, rectangle.Right) - Position.X, - Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); - } - - private List capturedHitObjects = new List(); - public IReadOnlyList CapturedHitObjects => capturedHitObjects; - - public void UpdateCapture() - { - capturedHitObjects.Clear(); - - foreach (var obj in CapturableObjects) - { - if (!obj.IsAlive || !obj.IsPresent) - continue; - - var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); - if (ScreenSpaceDrawQuad.Contains(objectPosition)) - capturedHitObjects.Add(obj); - } - } - - public void FinishCapture() - { - if (CapturedHitObjects.Count == 0) - { - Hide(); - return; - } - - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - foreach (var obj in capturedHitObjects) - { - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - this.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); - - trackingRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); - - borderMask.BorderThickness = 3; - borderMask.FadeColour(captureFinishedColour, 200); - - background.Delay(50).FadeOut(200); - markers.FadeIn(200); - } - - private bool isActive = true; - public override bool HandleInput => isActive; - - public override void Hide() - { - isActive = false; - this.FadeOut(400, Easing.OutQuint).Expire(); - } - } - - public class MarkerContainer : CompositeDrawable - { - public Func GetCaptureRectangle; - public Action UpdateCapture; - public Action FinishCapture; - - public MarkerContainer() - { - InternalChildren = new Drawable[] - { - new Marker - { - Anchor = Anchor.TopLeft, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.TopRight, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.BottomRight, - Origin = Anchor.Centre - }, - new Marker - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre - }, - new CentreMarker - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } - }; - - InternalChildren.OfType().ForEach(m => - { - m.GetCaptureRectangle = () => GetCaptureRectangle(); - m.UpdateCapture = r => UpdateCapture(r); - m.FinishCapture = () => FinishCapture(); - }); - } - } - - public class Marker : CompositeDrawable - { - private const float marker_size = 10; - - public Func GetCaptureRectangle; - public Action UpdateCapture; - public Action FinishCapture; - - private Color4 normalColour; - private Color4 hoverColour; - - public Marker() - { - Size = new Vector2(marker_size); - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = normalColour = colours.Yellow; - hoverColour = colours.YellowDarker; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - var currentRectangle = GetCaptureRectangle(); - - float left = currentRectangle.Left; - float right = currentRectangle.Right; - float top = currentRectangle.Top; - float bottom = currentRectangle.Bottom; - - // Apply modifications to the capture rectangle - if ((Anchor & Anchor.y0) > 0) - top += state.Mouse.Delta.Y; - else if ((Anchor & Anchor.y2) > 0) - bottom += state.Mouse.Delta.Y; - - if ((Anchor & Anchor.x0) > 0) - left += state.Mouse.Delta.X; - else if ((Anchor & Anchor.x2) > 0) - right += state.Mouse.Delta.X; - - UpdateCapture(RectangleF.FromLTRB(left, top, right, bottom)); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - FinishCapture(); - return true; - } - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 100); - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(normalColour, 100); - } - } - - public class CentreMarker : CompositeDrawable - { - private const float marker_size = 10; - private const float line_width = 2; - - public CentreMarker() - { - Size = new Vector2(marker_size); - - InternalChildren = new[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = line_width - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = line_width - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c61a3172f6..207dae1b54 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -310,6 +310,11 @@ + + + + + @@ -573,7 +578,6 @@ - @@ -856,4 +860,4 @@ - + \ No newline at end of file From a303bf71cf5a26b1b181865bab4a2f9830823fdb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 22:19:02 +0900 Subject: [PATCH 078/239] Give control over screen space conversion to DrawableHitObject --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs | 3 +-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7e6892e70b..42f54af801 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => initialCircle.ApproachCircle; - public override Vector2 SelectionPoint => body.Position; + public override Vector2 SelectionPoint => ToScreenSpace(body.Position); public override Quad SelectionQuad => body.PathDrawQuad; } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs index 1e86988c01..54d5a959a8 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs @@ -115,8 +115,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection if (!obj.IsAlive || !obj.IsPresent) continue; - var objectPosition = obj.ToScreenSpace(obj.SelectionPoint); - if (ScreenSpaceDrawQuad.Contains(objectPosition)) + if (ScreenSpaceDrawQuad.Contains(obj.SelectionPoint)) capturedHitObjects.Add(obj); } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 834bfd53a2..57db36fda5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -42,12 +42,12 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// The local point that causes this to be selected in the Editor. + /// The screen-space point that causes this to be selected in the Editor. /// - public virtual Vector2 SelectionPoint => DrawSize / 2f; + public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; /// - /// The local rectangle that outlines this for selections in the Editor. + /// The screen-space quad that outlines this for selections in the Editor. /// public virtual Quad SelectionQuad => ScreenSpaceDrawQuad; } From 666dcdbd6281b1b9f805367049a965a6200ea902 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 22:20:02 +0900 Subject: [PATCH 079/239] Give HitObjectComposer a SelectionLayer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3184b84e98..4487f74364 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; @@ -77,7 +78,8 @@ namespace osu.Game.Rulesets.Edit Alpha = 0, AlwaysPresent = true, }, - rulesetContainer + rulesetContainer, + new SelectionLayer(rulesetContainer.Playfield) } } }, From a6a07b1aa7259373bcdfca4745740d7b30457a8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 22:20:07 +0900 Subject: [PATCH 080/239] Cleanups --- .../Visual/TestCaseEditorCompose.cs | 2 -- .../Visual/TestCaseEditorSelectionLayer.cs | 34 +++++++++---------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index 226329a852..d52f27f4ab 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -2,10 +2,8 @@ // 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.Game.Beatmaps; -using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Screens.Compose; namespace osu.Game.Tests.Visual diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 79f3e4f1d3..6b7dedf9cf 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -7,12 +7,10 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Tests.Visual { @@ -22,22 +20,24 @@ namespace osu.Game.Tests.Visual public TestCaseEditorSelectionLayer() { - var playfield = new OsuEditPlayfield(); - playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); - playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f })); - playfield.Add(new DrawableSlider(new Slider + var playfield = new OsuEditPlayfield { - ControlPoints = new List + new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }), + new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }), + new DrawableSlider(new Slider { - new Vector2(128, 256), - new Vector2(344, 256), - }, - Distance = 400, - Position = new Vector2(128, 256), - Velocity = 1, - TickDistance = 100, - Scale = 0.5f - })); + ControlPoints = new List + { + new Vector2(128, 256), + new Vector2(344, 256), + }, + Distance = 400, + Position = new Vector2(128, 256), + Velocity = 1, + TickDistance = 100, + Scale = 0.5f + }) + }; Children = new Drawable[] { From 3d51301e0317ff7936bf223088c2250a1651ef29 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 11 Dec 2017 22:21:25 +0900 Subject: [PATCH 081/239] Add license headers --- osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs | 5 ++++- osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs | 5 ++++- osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs | 5 ++++- osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs index 6da6c72849..0ed7339134 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.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.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs index 54d5a959a8..b83ed2e270 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.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 System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs index 3519adb3b3..cee6ac63d5 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs index 3b35528cf1..e222583348 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.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 System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; From 69653c7f4fa166f60faac99123ed3a557cbb2b38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Dec 2017 13:07:34 +0900 Subject: [PATCH 082/239] Don't use nested ternary if --- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 8b2670f39c..9eb84421d4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -59,8 +59,12 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapGroupState.Expanded: Header.State = PanelSelectedState.Selected; foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : - !panel.Filtered ? PanelSelectedState.NotSelected : PanelSelectedState.Hidden; + if (panel == SelectedPanel) + panel.State = PanelSelectedState.Selected; + else if (panel.Filtered) + panel.State = PanelSelectedState.Hidden; + else + panel.State = PanelSelectedState.NotSelected; break; case BeatmapGroupState.Collapsed: Header.State = PanelSelectedState.NotSelected; From d11bf379d8b8c72d2bcc4ca09b828f77e1054293 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Tue, 12 Dec 2017 11:04:11 +0100 Subject: [PATCH 083/239] no longer select beatmapsets on import/download let's save some ears and eyes for now --- osu.Game/Screens/Select/SongSelect.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index cf08ab273e..fffd05e7b5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -309,14 +309,7 @@ namespace osu.Game.Screens.Select carousel.Filter(criteria, debounce); } - private void onBeatmapSetAdded(BeatmapSetInfo s) - { - Schedule(() => - { - carousel.UpdateBeatmapSet(s); - carousel.SelectBeatmap(s.Beatmaps.First()); - }); - } + private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); From 9fed97267872346de7c68e79804d6c35e3ad30bd Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Tue, 12 Dec 2017 11:15:34 +0100 Subject: [PATCH 084/239] removed unnecessary using --- osu.Game/Screens/Select/SongSelect.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index fffd05e7b5..7fb6a82981 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Linq; using System.Threading; using OpenTK; using OpenTK.Input; From c6eaaf658e7fae1c1e32f3d26624ec33ac0d0602 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Tue, 12 Dec 2017 13:24:18 +0100 Subject: [PATCH 085/239] fix BeatmapInfoWedge not counting Circles/Sliders correctly --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 10 +++++----- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index bb5700976d..d67f7f6948 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -18,6 +17,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Legacy.Osu; namespace osu.Game.Rulesets.Osu { @@ -37,14 +37,14 @@ namespace osu.Game.Rulesets.Osu { new BeatmapStatistic { - Name = @"Circle count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is HitCircle).ToString(), + Name = @"Circle Count", + Content = beatmap.Beatmap.HitObjects.Count(h => h is ConvertHit).ToString(), Icon = FontAwesome.fa_dot_circle_o }, new BeatmapStatistic { - Name = @"Slider count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is Slider).ToString(), + Name = @"Slider Count", + Content = beatmap.Beatmap.HitObjects.Count(h => h is ConvertSlider).ToString(), Icon = FontAwesome.fa_circle_o } }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 479cacc52b..9527f50802 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -11,7 +11,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : HitObject, IHasCurve + public abstract class ConvertSlider : HitObject, IHasCurve { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 0c1000965c..04c67ff1e4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo + public sealed class ConvertHit : HitObject, IHasPosition, IHasCombo { public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 75a6d80560..3c15eaf14b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Slider-type, used for parsing Beatmaps. /// - internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + public sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo { public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 2b2dbe0765..eff0f1a2cb 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition + public sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition { public double EndTime { get; set; } From 1d206f7ec69f0b980b88fed402596f8716961190 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Tue, 12 Dec 2017 14:44:12 +0100 Subject: [PATCH 086/239] add visual tests for BeatmapInfoWedge --- .../Visual/TestCaseBeatmapInfoWedge.cs | 69 +++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + 2 files changed, 70 insertions(+) create mode 100644 osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs new file mode 100644 index 0000000000..0168cedc86 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseBeatmapInfoWedge : OsuTestCase + { + private BeatmapManager beatmaps; + private readonly Random random; + private readonly BeatmapInfoWedge infoWedge; + private readonly Bindable beatmap = new Bindable(); + + public TestCaseBeatmapInfoWedge() + { + random = new Random(0123); + + Add(infoWedge = new BeatmapInfoWedge + { + Size = new Vector2(0.5f, 245), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding + { + Top = 20, + }, + }); + + AddStep("show", () => + { + Content.FadeInFromZero(250); + infoWedge.State = Visibility.Visible; + infoWedge.UpdateBeatmap(beatmap); + }); + AddStep("hide", () => + { + infoWedge.State = Visibility.Hidden; + Content.FadeOut(100); + }); + AddStep("random beatmap", randomBeatmap); + AddStep("null beatmap", () => infoWedge.UpdateBeatmap(beatmap.Default)); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + beatmap.BindTo(game.Beatmap); + } + + private void randomBeatmap() + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count == 0) + return; + + var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; + beatmap.Value = beatmaps.GetWorkingBeatmap(b); + infoWedge.UpdateBeatmap(beatmap); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 12d47591ec..1542269f00 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -90,6 +90,7 @@ + From d4cd8354196626689fdeaeb1332e3aa51ba8a174 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Wed, 13 Dec 2017 16:32:32 +0100 Subject: [PATCH 087/239] correctly count HitObjects for their type also legacy classes are internal again --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 37 ++++++++++++------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- .../Rulesets/Objects/Legacy/Osu/ConvertHit.cs | 2 +- .../Objects/Legacy/Osu/ConvertSlider.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d67f7f6948..48516a8cef 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -17,7 +17,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects.Legacy.Osu; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu { @@ -33,21 +34,29 @@ namespace osu.Game.Rulesets.Osu new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; - public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) { - new BeatmapStatistic + IEnumerable hitObjects = beatmap.Beatmap.HitObjects; + IEnumerable durationObjects = hitObjects.Where(d => d is IHasEndTime); + IEnumerable circles = hitObjects.Except(durationObjects); + IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); + + return new[] { - Name = @"Circle Count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is ConvertHit).ToString(), - Icon = FontAwesome.fa_dot_circle_o - }, - new BeatmapStatistic - { - Name = @"Slider Count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is ConvertSlider).ToString(), - Icon = FontAwesome.fa_circle_o - } - }; + new BeatmapStatistic + { + Name = @"Circle Count", + Content = circles.Count().ToString(), + Icon = FontAwesome.fa_circle_o + }, + new BeatmapStatistic + { + Name = @"Slider Count", + Content = sliders.Count().ToString(), + Icon = FontAwesome.fa_circle + } + }; + } public override IEnumerable GetModsFor(ModType type) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 9527f50802..479cacc52b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -11,7 +11,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - public abstract class ConvertSlider : HitObject, IHasCurve + internal abstract class ConvertSlider : HitObject, IHasCurve { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index 04c67ff1e4..0c1000965c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Hit-type, used for parsing Beatmaps. /// - public sealed class ConvertHit : HitObject, IHasPosition, IHasCombo + internal sealed class ConvertHit : HitObject, IHasPosition, IHasCombo { public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index 3c15eaf14b..75a6d80560 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Slider-type, used for parsing Beatmaps. /// - public sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo + internal sealed class ConvertSlider : Legacy.ConvertSlider, IHasPosition, IHasCombo { public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index eff0f1a2cb..2b2dbe0765 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - public sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasPosition { public double EndTime { get; set; } From 345b67ac85973641f76535d44a5627e045b343a3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Dec 2017 12:46:02 +0900 Subject: [PATCH 088/239] Remove unnecessary .Except --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 48516a8cef..85a46f3aaa 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -37,8 +37,7 @@ namespace osu.Game.Rulesets.Osu public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) { IEnumerable hitObjects = beatmap.Beatmap.HitObjects; - IEnumerable durationObjects = hitObjects.Where(d => d is IHasEndTime); - IEnumerable circles = hitObjects.Except(durationObjects); + IEnumerable circles = hitObjects.Where(d => !(d is IHasEndTime)); IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); return new[] From 65a6946a9ad9d65bf1a43fa42e2ef96500f30637 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Dec 2017 12:47:47 +0900 Subject: [PATCH 089/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 8480ab5009..dd7dba7212 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 8480ab5009b2b4a7810a817a12433959424d5339 +Subproject commit dd7dba7212845b780fa50e0aa12b1aaa7155cddb From 15c9e4a4468aae212b9044d9ebe3bd3fab1efb02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Dec 2017 15:10:39 +0900 Subject: [PATCH 090/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index e57f32e483..97ac45b9c3 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit e57f32e483fa47ee066becc9e0a1b3187b340606 +Subproject commit 97ac45b9c3c92544c9489b26dcda95e49f87b513 From 6a690908cfb3c3bbdd094730c2931340f4286cac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Dec 2017 20:24:24 +0900 Subject: [PATCH 091/239] Fix up possible nullref due to early access of Parent --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 387a098a5a..f03acb2fa0 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Osu.UI { get { + if (Parent == null) + return Vector2.Zero; + var parentSize = Parent.DrawSize; var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y); From b28b86dea9dc267ba41a64deab6c3bef507b169a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 Dec 2017 20:27:51 +0900 Subject: [PATCH 092/239] Use Lazy for threadsafety on playfield --- osu.Game/Rulesets/UI/RulesetContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 5b4565e8a8..fe7c0c05ed 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -55,11 +55,11 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } - private Playfield playfield; + private readonly Lazy playfield; /// /// The playfield. /// - public Playfield Playfield => playfield ?? (playfield = CreatePlayfield()); + public Playfield Playfield => playfield.Value; protected readonly Ruleset Ruleset; @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.UI protected RulesetContainer(Ruleset ruleset) { Ruleset = ruleset; + playfield = new Lazy(CreatePlayfield); } public abstract ScoreProcessor CreateScoreProcessor(); From 59e8536ff7b5ff490f31bb55c5882addf7e640c5 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 14 Dec 2017 17:33:56 +0100 Subject: [PATCH 093/239] moved action to construction arguments --- .../Settings/Sections/Maintenance/GeneralSettings.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 66be5bc0ac..2c554a7e6b 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.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.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -39,12 +38,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = "Delete ALL beatmaps", Action = () => { - Action deletion = delegate + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => { deleteButton.Enabled.Value = false; Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); - }; - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(deletion)); + })); } }, restoreButton = new SettingsButton From f329b1ed722cc1f0e2d14ce88b573d3ab3a506da Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Thu, 14 Dec 2017 19:55:15 +0100 Subject: [PATCH 094/239] add Spinner Count to BeatmapInfoWedge - added Tooltips to the respective InfoLabels - made the TestCase internal like all others --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 9 +++++- .../Visual/TestCaseBeatmapInfoWedge.cs | 2 +- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 31 ++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 85a46f3aaa..19d60c9046 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -37,8 +37,9 @@ namespace osu.Game.Rulesets.Osu public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) { IEnumerable hitObjects = beatmap.Beatmap.HitObjects; - IEnumerable circles = hitObjects.Where(d => !(d is IHasEndTime)); + IEnumerable circles = hitObjects.Where(c => !(c is IHasEndTime)); IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); + IEnumerable spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve)); return new[] { @@ -53,6 +54,12 @@ namespace osu.Game.Rulesets.Osu Name = @"Slider Count", Content = sliders.Count().ToString(), Icon = FontAwesome.fa_circle + }, + new BeatmapStatistic + { + Name = @"Spinner Count", + Content = spinners.Count().ToString(), + Icon = FontAwesome.fa_circle } }; } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 0168cedc86..4ac7469a5a 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -12,7 +12,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual { - public class TestCaseBeatmapInfoWedge : OsuTestCase + internal class TestCaseBeatmapInfoWedge : OsuTestCase { private BeatmapManager beatmaps; private readonly Random random; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 5ced60a9da..12e93d9ff7 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Cursor; namespace osu.Game.Screens.Select { @@ -217,8 +218,8 @@ namespace osu.Game.Screens.Select }, new FillFlowContainer { - Margin = new MarginPadding { Top = 20, Left = 10 }, - Spacing = new Vector2(40, 0), + Margin = new MarginPadding { Top = 20 }, + Spacing = new Vector2(20, 0), AutoSizeAxes = Axes.Both, Children = labels }, @@ -232,43 +233,51 @@ namespace osu.Game.Screens.Select double bpmMax = beatmap.ControlPointInfo.BPMMaximum; double bpmMin = beatmap.ControlPointInfo.BPMMinimum; - if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}bpm"; + if (Precision.AlmostEquals(bpmMin, bpmMax)) return $"{bpmMin:0}"; - return $"{bpmMin:0}-{bpmMax:0}bpm (mostly {beatmap.ControlPointInfo.BPMMode:0}bpm)"; + return $"{bpmMin:0}-{bpmMax:0} (mostly {beatmap.ControlPointInfo.BPMMode:0})"; } - public class InfoLabel : Container + public class InfoLabel : Container, IHasTooltip { + public string TooltipText { get; private set; } + public InfoLabel(BeatmapStatistic statistic) { + TooltipText = statistic.Name; AutoSizeAxes = Axes.Both; + Padding = new MarginPadding { Left = 16 }; + Children = new Drawable[] { new SpriteIcon { + Anchor = Anchor.CentreLeft, + Colour = OsuColour.FromHex(@"441288"), Icon = FontAwesome.fa_square, Origin = Anchor.Centre, - Colour = new Color4(68, 17, 136, 255), Rotation = 45, Size = new Vector2(20), }, new SpriteIcon { + Anchor = Anchor.CentreLeft, + Colour = OsuColour.FromHex(@"f7dd55"), Icon = statistic.Icon, Origin = Anchor.Centre, - Colour = new Color4(255, 221, 85, 255), Scale = new Vector2(0.8f), Size = new Vector2(20), }, new OsuSpriteText { - Margin = new MarginPadding { Left = 13 }, - Font = @"Exo2.0-Bold", + Anchor = Anchor.CentreLeft, Colour = new Color4(255, 221, 85, 255), + Font = @"Exo2.0-Bold", + Margin = new MarginPadding { Left = 13 }, + Origin = Anchor.CentreLeft, Text = statistic.Content, TextSize = 17, - Origin = Anchor.CentreLeft - }, + } }; } } From 33654ee5b449dab1e9d9554a8e27253f9c203e68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Dec 2017 13:01:06 +0900 Subject: [PATCH 095/239] Remove explicit padding on icon --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 42 ++++++++++++--------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 12e93d9ff7..3ef6ceeaeb 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -246,35 +246,43 @@ namespace osu.Game.Screens.Select { TooltipText = statistic.Name; AutoSizeAxes = Axes.Both; - Padding = new MarginPadding { Left = 16 }; Children = new Drawable[] { - new SpriteIcon + new Container { Anchor = Anchor.CentreLeft, - Colour = OsuColour.FromHex(@"441288"), - Icon = FontAwesome.fa_square, - Origin = Anchor.Centre, - Rotation = 45, - Size = new Vector2(20), - }, - new SpriteIcon - { - Anchor = Anchor.CentreLeft, - Colour = OsuColour.FromHex(@"f7dd55"), - Icon = statistic.Icon, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), + Origin = Anchor.CentreLeft, Size = new Vector2(20), + Children = new[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"441288"), + Icon = FontAwesome.fa_square, + Rotation = 45, + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.8f), + Colour = OsuColour.FromHex(@"f7dd55"), + Icon = statistic.Icon, + }, + } }, new OsuSpriteText { Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Colour = new Color4(255, 221, 85, 255), Font = @"Exo2.0-Bold", - Margin = new MarginPadding { Left = 13 }, - Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 30 }, Text = statistic.Content, TextSize = 17, } From 5caafd215f24ea66b32d6fcccb39e622fe5d5275 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Dec 2017 13:02:49 +0900 Subject: [PATCH 096/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index dd7dba7212..fc6de01ad6 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit dd7dba7212845b780fa50e0aa12b1aaa7155cddb +Subproject commit fc6de01ad6045544991bf278316c9eed8ea01ef1 From eeb3440ffa387d946345834fe4443325d9abbb59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Dec 2017 13:37:32 +0900 Subject: [PATCH 097/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 97ac45b9c3..fc6de01ad6 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 97ac45b9c3c92544c9489b26dcda95e49f87b513 +Subproject commit fc6de01ad6045544991bf278316c9eed8ea01ef1 From 78dd975a351440a675cafda188a39b5392ba875b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Dec 2017 17:48:38 +0900 Subject: [PATCH 098/239] Initial carousel infrastructue changes --- .../Visual/TestCasePlaySongSelect.cs | 13 +- osu.Game/Beatmaps/Drawables/BeatmapGroup.cs | 147 ------ osu.Game/Screens/Select/BeatmapCarousel.cs | 419 ++++++++---------- .../Select/Carousel/CarouselBeatmap.cs | 42 ++ .../Select/Carousel/CarouselBeatmapSet.cs | 62 +++ .../Screens/Select/Carousel/CarouselGroup.cs | 54 +++ .../Carousel/CarouselGroupEagerSelect.cs | 28 ++ .../Screens/Select/Carousel/CarouselItem.cs | 57 +++ .../Carousel/DrawableCarouselBeatmap.cs} | 115 ++--- .../Carousel/DrawableCarouselBeatmapSet.cs} | 75 ++-- .../Select/Carousel/DrawableCarouselItem.cs} | 80 ++-- osu.Game/Screens/Select/EditSongSelect.cs | 4 +- osu.Game/Screens/Select/FilterCriteria.cs | 54 --- osu.Game/Screens/Select/MatchSongSelect.cs | 4 +- osu.Game/Screens/Select/PlaySongSelect.cs | 25 +- osu.Game/Screens/Select/SongSelect.cs | 36 +- osu.Game/osu.Game.csproj | 14 +- 17 files changed, 594 insertions(+), 635 deletions(-) delete mode 100644 osu.Game/Beatmaps/Drawables/BeatmapGroup.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselGroup.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs create mode 100644 osu.Game/Screens/Select/Carousel/CarouselItem.cs rename osu.Game/{Beatmaps/Drawables/BeatmapPanel.cs => Screens/Select/Carousel/DrawableCarouselBeatmap.cs} (81%) rename osu.Game/{Beatmaps/Drawables/BeatmapSetHeader.cs => Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs} (75%) rename osu.Game/{Beatmaps/Drawables/Panel.cs => Screens/Select/Carousel/DrawableCarouselItem.cs} (71%) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 7c070fd3df..16c757702c 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -26,10 +26,16 @@ namespace osu.Game.Tests.Visual private DependencyContainer dependencies; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatmapCarousel), + typeof(SongSelect), + }; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); [BackgroundDependencyLoader] - private void load() + private void load(BeatmapManager baseMaanger) { PlaySongSelect songSelect; @@ -43,7 +49,10 @@ namespace osu.Game.Tests.Visual Func contextFactory = () => context; dependencies.Cache(rulesets = new RulesetStore(contextFactory)); - dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)); + dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) + { + DefaultBeatmap = baseMaanger.GetWorkingBeatmap(null) + }); for (int i = 0; i < 100; i += 10) manager.Import(createTestBeatmapSet(i)); diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs deleted file mode 100644 index 9eb84421d4..0000000000 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ /dev/null @@ -1,147 +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.Collections.Generic; -using System.Linq; -using osu.Framework; -using osu.Framework.Graphics; - -namespace osu.Game.Beatmaps.Drawables -{ - public class BeatmapGroup : IStateful - { - public event Action StateChanged; - - public BeatmapPanel SelectedPanel; - - /// - /// Fires when one of our difficulties was selected. Will fire on first expand. - /// - public Action SelectionChanged; - - /// - /// Fires when one of our difficulties is clicked when already selected. Should start playing the map. - /// - public Action StartRequested; - - public Action DeleteRequested; - - public Action RestoreHiddenRequested; - - public Action HideDifficultyRequested; - - public Action EditRequested; - - public BeatmapSetHeader Header; - - public List BeatmapPanels; - - public BeatmapSetInfo BeatmapSet; - - private BeatmapGroupState state; - - public BeatmapGroupState State - { - get { return state; } - set - { - state = value; - UpdateState(); - StateChanged?.Invoke(state); - } - } - - public void UpdateState() - { - switch (state) - { - case BeatmapGroupState.Expanded: - Header.State = PanelSelectedState.Selected; - foreach (BeatmapPanel panel in BeatmapPanels) - if (panel == SelectedPanel) - panel.State = PanelSelectedState.Selected; - else if (panel.Filtered) - panel.State = PanelSelectedState.Hidden; - else - panel.State = PanelSelectedState.NotSelected; - break; - case BeatmapGroupState.Collapsed: - Header.State = PanelSelectedState.NotSelected; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - case BeatmapGroupState.Hidden: - Header.State = PanelSelectedState.Hidden; - foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = PanelSelectedState.Hidden; - break; - } - } - - public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager) - { - if (beatmapSet == null) - throw new ArgumentNullException(nameof(beatmapSet)); - if (manager == null) - throw new ArgumentNullException(nameof(manager)); - - BeatmapSet = beatmapSet; - WorkingBeatmap beatmap = manager.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault()); - - Header = new BeatmapSetHeader(beatmap) - { - GainedSelection = headerGainedSelection, - DeleteRequested = b => DeleteRequested(b), - RestoreHiddenRequested = b => RestoreHiddenRequested(b), - RelativeSizeAxes = Axes.X, - }; - - BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.RulesetID).ThenBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b) - { - Alpha = 0, - GainedSelection = panelGainedSelection, - HideRequested = p => HideDifficultyRequested?.Invoke(p), - StartRequested = p => StartRequested?.Invoke(p.Beatmap), - EditRequested = p => EditRequested?.Invoke(p.Beatmap), - RelativeSizeAxes = Axes.X, - }).ToList(); - - Header.AddDifficultyIcons(BeatmapPanels); - } - - - private void headerGainedSelection(BeatmapSetHeader panel) - { - State = BeatmapGroupState.Expanded; - - //we want to make sure one of our children is selected in the case none have been selected yet. - if (SelectedPanel == null) - BeatmapPanels.First(p => !p.Filtered).State = PanelSelectedState.Selected; - } - - private void panelGainedSelection(BeatmapPanel panel) - { - try - { - if (SelectedPanel == panel) return; - - if (SelectedPanel != null) - SelectedPanel.State = PanelSelectedState.NotSelected; - SelectedPanel = panel; - } - finally - { - State = BeatmapGroupState.Expanded; - SelectionChanged?.Invoke(this, SelectedPanel); - } - } - } - - public enum BeatmapGroupState - { - Collapsed, - Expanded, - Hidden, - } -} diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f6b832d0f7..cede452402 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; using System.Linq; -using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Framework.Input; using OpenTK.Input; @@ -15,17 +14,19 @@ using osu.Framework.MathUtils; using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Configuration; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using osu.Game.Screens.Select.Carousel; namespace osu.Game.Screens.Select { public class BeatmapCarousel : OsuScrollContainer { - public BeatmapInfo SelectedBeatmap => selectedPanel?.Beatmap; + public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; public override bool HandleInput => AllowSelection; @@ -33,27 +34,30 @@ namespace osu.Game.Screens.Select public IEnumerable Beatmaps { - get { return groups.Select(g => g.BeatmapSet); } + get { return carouselSets.Select(g => g.BeatmapSet); } set { scrollableContent.Clear(false); - panels.Clear(); - groups.Clear(); + items.Clear(); + carouselSets.Clear(); - List newGroups = null; + List newSets = null; Task.Run(() => { - newGroups = value.Select(createGroup).Where(g => g != null).ToList(); - criteria.Filter(newGroups); + newSets = value.Select(createGroup).Where(g => g != null).ToList(); + newSets.ForEach(g => g.Filter(criteria)); }).ContinueWith(t => { Schedule(() => { - foreach (var g in newGroups) + foreach (var g in newSets) addGroup(g); - computeYPositions(); + root = new CarouselGroup(newSets.OfType().ToList()); + items = root.Drawables.Value.ToList(); + + yPositionsCache.Invalidate(); BeatmapsChanged?.Invoke(); }); }); @@ -62,24 +66,19 @@ namespace osu.Game.Screens.Select private readonly List yPositions = new List(); - /// - /// Required for now unfortunately. - /// - private BeatmapManager manager; + private readonly Container scrollableContent; - private readonly Container scrollableContent; - - private readonly List groups = new List(); + private readonly List carouselSets = new List(); private Bindable randomType; - private readonly List seenGroups = new List(); + private readonly List seenSets = new List(); - private readonly List panels = new List(); + private List items = new List(); + private CarouselGroup root = new CarouselGroup(); - private readonly Stack> randomSelectedBeatmaps = new Stack>(); + private readonly Stack randomSelectedBeatmaps = new Stack(); - private BeatmapGroup selectedGroup; - private BeatmapPanel selectedPanel; + private CarouselBeatmap selectedBeatmap; public BeatmapCarousel() { @@ -87,7 +86,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = scrollableContent = new Container + Child = scrollableContent = new Container { RelativeSizeAxes = Axes.X, } @@ -96,45 +95,44 @@ namespace osu.Game.Screens.Select public void RemoveBeatmap(BeatmapSetInfo beatmapSet) { - Schedule(() => removeGroup(groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); + Schedule(() => removeGroup(carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - // todo: this method should be smarter as to not recreate panels that haven't changed, etc. - var oldGroup = groups.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + // todo: this method should be smarter as to not recreate items that haven't changed, etc. + var oldGroup = carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); - var newGroup = createGroup(beatmapSet); + bool hadSelection = oldGroup?.State == CarouselItemState.Selected; - int index = groups.IndexOf(oldGroup); + var newSet = createGroup(beatmapSet); + + int index = carouselSets.IndexOf(oldGroup); if (index >= 0) - groups.RemoveAt(index); + carouselSets.RemoveAt(index); - if (newGroup != null) + if (newSet != null) { if (index >= 0) - groups.Insert(index, newGroup); + carouselSets.Insert(index, newSet); else - addGroup(newGroup); + addGroup(newSet); } - bool hadSelection = selectedGroup == oldGroup; - - if (hadSelection && newGroup == null) - selectedGroup = null; + if (hadSelection && newSet == null) + SelectNext(); Filter(null, false); //check if we can/need to maintain our current selection. - if (hadSelection && newGroup != null) + if (hadSelection && newSet != null) { - var newSelection = - newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID); + var newSelection = newSet.Beatmaps.Find(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID); - if (newSelection == null && oldGroup != null && selectedPanel != null) - newSelection = newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, oldGroup.BeatmapPanels.IndexOf(selectedPanel))]; + if (newSelection == null && selectedBeatmap != null) + newSelection = newSet.Beatmaps[Math.Min(newSet.Beatmaps.Count - 1, oldGroup.Beatmaps.IndexOf(selectedBeatmap))]; - selectGroup(newGroup, newSelection); + select(newSelection); } } @@ -148,12 +146,12 @@ namespace osu.Game.Screens.Select if (beatmap == SelectedBeatmap) return; - foreach (BeatmapGroup group in groups) + foreach (CarouselBeatmapSet group in carouselSets) { - var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); - if (panel != null) + var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + if (item != null) { - selectGroup(group, panel, animated); + select(item, animated); return; } } @@ -161,20 +159,9 @@ namespace osu.Game.Screens.Select public Action SelectionChanged; - public Action StartRequested; - - public Action DeleteRequested; - - public Action RestoreRequested; - - public Action EditRequested; - - public Action HideDifficultyRequested; - private void selectNullBeatmap() { - selectedGroup = null; - selectedPanel = null; + selectedBeatmap = null; SelectionChanged?.Invoke(null); } @@ -186,90 +173,71 @@ namespace osu.Game.Screens.Select public void SelectNext(int direction = 1, bool skipDifficulties = true) { // todo: we may want to refactor and remove this as an optimisation in the future. - if (groups.All(g => g.State == BeatmapGroupState.Hidden)) + if (carouselSets.All(g => g.State == CarouselItemState.Hidden)) { selectNullBeatmap(); return; } - int originalIndex = Math.Max(0, groups.IndexOf(selectedGroup)); + int originalIndex = Math.Max(0, items.IndexOf(selectedBeatmap?.Drawables.Value.First())); int currentIndex = originalIndex; // local function to increment the index in the required direction, wrapping over extremities. - int incrementIndex() => currentIndex = (currentIndex + direction + groups.Count) % groups.Count; + int incrementIndex() => currentIndex = (currentIndex + direction + items.Count) % items.Count; - // in the case we are skipping difficulties, we want to increment the index once before starting to find out new target - // (we don't care about the currently selected group). - if (skipDifficulties) - incrementIndex(); - - do + while (incrementIndex() != originalIndex) { - var group = groups[currentIndex]; + var item = items[currentIndex].Item; - if (group.State == BeatmapGroupState.Hidden) continue; + if (item.Filtered || item.State == CarouselItemState.Selected) continue; - // we are only interested in non-filtered panels. - IEnumerable validPanels = group.BeatmapPanels.Where(p => !p.Filtered); - - // if we are considering difficulties, we need to do a few extrea steps. - if (!skipDifficulties) + switch (item) { - // we want to reverse the panel order if we are searching backwards. - if (direction < 0) - validPanels = validPanels.Reverse(); - - // if we are currently on the selected panel, let's try to find a valid difficulty before leaving to the next group. - // the first valid difficulty is found by skipping to the selected panel and then one further. - if (currentIndex == originalIndex) - validPanels = validPanels.SkipWhile(p => p != selectedPanel).Skip(1); + case CarouselBeatmap beatmap: + if (skipDifficulties) continue; + select(beatmap); + return; + case CarouselBeatmapSet set: + select(set); + return; } - - var next = validPanels.FirstOrDefault(); - - // at this point, we can perform the selection change if we have a valid new target, else continue to increment in the specified direction. - if (next != null) - { - selectGroup(group, next); - return; - } - } while (incrementIndex() != originalIndex); + } } - private IEnumerable getVisibleGroups() => groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden); + private IEnumerable getVisibleGroups() => carouselSets.Where(select => select.State != CarouselItemState.NotSelected); public void SelectNextRandom() { - if (groups.Count == 0) + if (carouselSets.Count == 0) return; var visibleGroups = getVisibleGroups(); if (!visibleGroups.Any()) return; - if (selectedGroup != null) - randomSelectedBeatmaps.Push(new KeyValuePair(selectedGroup, selectedGroup.SelectedPanel)); + if (selectedBeatmap != null) + randomSelectedBeatmaps.Push(selectedBeatmap); - BeatmapGroup group; + CarouselBeatmapSet group; if (randomType == SelectionRandomType.RandomPermutation) { - var notSeenGroups = visibleGroups.Except(seenGroups); + var notSeenGroups = visibleGroups.Except(seenSets); if (!notSeenGroups.Any()) { - seenGroups.Clear(); + seenSets.Clear(); notSeenGroups = visibleGroups; } group = notSeenGroups.ElementAt(RNG.Next(notSeenGroups.Count())); - seenGroups.Add(group); + seenSets.Add(group); } else group = visibleGroups.ElementAt(RNG.Next(visibleGroups.Count())); - BeatmapPanel panel = group.BeatmapPanels[RNG.Next(group.BeatmapPanels.Count)]; + CarouselBeatmap item = group.Beatmaps[RNG.Next(group.Beatmaps.Count)]; - selectGroup(group, panel); + select(item); } public void SelectPreviousRandom() @@ -277,17 +245,13 @@ namespace osu.Game.Screens.Select if (!randomSelectedBeatmaps.Any()) return; - var visibleGroups = getVisibleGroups(); - if (!visibleGroups.Any()) - return; - while (randomSelectedBeatmaps.Any()) { - var beatmapCoordinates = randomSelectedBeatmaps.Pop(); - var group = beatmapCoordinates.Key; - if (visibleGroups.Contains(group)) + var beatmap = randomSelectedBeatmaps.Pop(); + + if (beatmap.Visible) { - selectGroup(group, beatmapCoordinates.Value); + select(beatmap); break; } } @@ -314,25 +278,14 @@ namespace osu.Game.Screens.Select { filterTask = null; - criteria.Filter(groups); + carouselSets.ForEach(s => s.Filter(criteria)); - var filtered = new List(groups); + yPositionsCache.Invalidate(); - scrollableContent.Clear(false); - panels.Clear(); - groups.Clear(); - - foreach (var g in filtered) - addGroup(g); - - computeYPositions(); - - selectedGroup?.UpdateState(); - - if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden) + if (selectedBeatmap?.Visible != true) SelectNext(); else - selectGroup(selectedGroup, selectedPanel); + select(selectedBeatmap); }; filterTask?.Cancel(); @@ -350,54 +303,60 @@ namespace osu.Game.Screens.Select ScrollTo(selectedY, animated); } - private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet) + private CarouselBeatmapSet createGroup(BeatmapSetInfo beatmapSet) { if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; + // todo: remove the need for this. foreach (var b in beatmapSet.Beatmaps) { if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; } - return new BeatmapGroup(beatmapSet, manager) + var set = new CarouselBeatmapSet(beatmapSet); + + foreach (var c in set.Beatmaps) { - SelectionChanged = (g, p) => selectGroup(g, p), - StartRequested = b => StartRequested?.Invoke(), - DeleteRequested = b => DeleteRequested?.Invoke(b), - RestoreHiddenRequested = s => RestoreRequested?.Invoke(s), - EditRequested = b => EditRequested?.Invoke(b), - HideDifficultyRequested = b => HideDifficultyRequested?.Invoke(b), - State = BeatmapGroupState.Collapsed - }; + c.State.ValueChanged += v => + { + if (v == CarouselItemState.Selected) + { + selectedBeatmap = c; + SelectionChanged?.Invoke(c.Beatmap); + yPositionsCache.Invalidate(); + Schedule(() => ScrollToSelected()); + } + }; + } + + return set; } [BackgroundDependencyLoader(permitNulls: true)] - private void load(BeatmapManager manager, OsuConfigManager config) + private void load(OsuConfigManager config) { - this.manager = manager; - randomType = config.GetBindable(OsuSetting.SelectionRandomType); } - private void addGroup(BeatmapGroup group) + private void addGroup(CarouselBeatmapSet set) { // prevent duplicates by concurrent independent actions trying to add a group - if (groups.Any(g => g.BeatmapSet.ID == group.BeatmapSet.ID)) + //todo: check this + if (carouselSets.Any(g => g.BeatmapSet.ID == set.BeatmapSet.ID)) return; - groups.Add(group); - panels.Add(group.Header); - panels.AddRange(group.BeatmapPanels); + //todo: add to root + carouselSets.Add(set); } - private void removeGroup(BeatmapGroup group) + private void removeGroup(CarouselBeatmapSet set) { - if (group == null) + if (set == null) return; - if (selectedGroup == group) + if (set.State == CarouselItemState.Selected) { if (getVisibleGroups().Count() == 1) selectNullBeatmap(); @@ -405,21 +364,23 @@ namespace osu.Game.Screens.Select SelectNext(); } - groups.Remove(group); - panels.Remove(group.Header); - foreach (var p in group.BeatmapPanels) - panels.Remove(p); + carouselSets.Remove(set); - scrollableContent.Remove(group.Header); - scrollableContent.RemoveRange(group.BeatmapPanels); + foreach (var d in set.Drawables.Value) + { + items.Remove(d); + scrollableContent.Remove(d); + } - computeYPositions(); + yPositionsCache.Invalidate(); } + private Cached yPositionsCache = new Cached(); + /// - /// Computes the target Y positions for every panel in the carousel. + /// Computes the target Y positions for every item in the carousel. /// - /// The Y position of the currently selected panel. + /// The Y position of the currently selected item. private float computeYPositions(bool animated = true) { yPositions.Clear(); @@ -427,88 +388,61 @@ namespace osu.Game.Screens.Select float currentY = DrawHeight / 2; float selectedY = currentY; - foreach (BeatmapGroup group in groups) + float lastSetY = 0; + + foreach (DrawableCarouselItem d in items) { - movePanel(group.Header, group.State != BeatmapGroupState.Hidden, animated, ref currentY); - - if (group.State == BeatmapGroupState.Expanded) + switch (d) { - group.Header.MoveToX(-100, 500, Easing.OutExpo); - var headerY = group.Header.Position.Y; + case DrawableCarouselBeatmapSet set: + set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - foreach (BeatmapPanel panel in group.BeatmapPanels) - { - if (panel == selectedPanel) - selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; + lastSetY = set.Position.Y; - panel.MoveToX(-50, 500, Easing.OutExpo); + movePanel(set, set.Item.Visible, animated, ref currentY); + break; + case DrawableCarouselBeatmap beatmap: + beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); - bool isHidden = panel.State == PanelSelectedState.Hidden; + if (beatmap.Item == selectedBeatmap) + selectedY = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; - //on first display we want to begin hidden under our group's header. - if (isHidden || panel.Alpha == 0) - panel.MoveToY(headerY); + // on first display we want to begin hidden under our group's header. + if (animated && !beatmap.IsPresent) + beatmap.MoveToY(lastSetY); - movePanel(panel, !isHidden, animated, ref currentY); - } - } - else - { - group.Header.MoveToX(0, 500, Easing.OutExpo); - - foreach (BeatmapPanel panel in group.BeatmapPanels) - { - panel.MoveToX(0, 500, Easing.OutExpo); - movePanel(panel, false, animated, ref currentY); - } + movePanel(beatmap, beatmap.Item.Visible, animated, ref currentY); + break; } } currentY += DrawHeight / 2; scrollableContent.Height = currentY; + yPositionsCache.Validate(); + return selectedY; } - private void movePanel(Panel panel, bool advance, bool animated, ref float currentY) + private void movePanel(DrawableCarouselItem item, bool advance, bool animated, ref float currentY) { yPositions.Add(currentY); - panel.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); + item.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); if (advance) - currentY += panel.DrawHeight + 5; + currentY += item.DrawHeight + 5; } - private void selectGroup(BeatmapGroup group, BeatmapPanel panel = null, bool animated = true) + private void select(CarouselBeatmapSet beatmapSet = null) { - try - { - if (panel == null || panel.Filtered == true) - panel = group.BeatmapPanels.First(p => !p.Filtered); + if (beatmapSet == null) return; + beatmapSet.State.Value = CarouselItemState.Selected; + } - if (selectedPanel == panel) return; - - Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group"); - - if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden) - selectedGroup.State = BeatmapGroupState.Collapsed; - - group.State = BeatmapGroupState.Expanded; - group.SelectedPanel = panel; - - panel.State = PanelSelectedState.Selected; - - if (selectedPanel == panel) return; - - selectedPanel = panel; - selectedGroup = group; - - SelectionChanged?.Invoke(panel.Beatmap); - } - finally - { - ScrollToSelected(animated); - } + private void select(CarouselBeatmap beatmap = null, bool animated = true) + { + if (beatmap == null) return; + beatmap.State.Value = CarouselItemState.Selected; } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) @@ -547,66 +481,67 @@ namespace osu.Game.Screens.Select float drawHeight = DrawHeight; - // Remove all panels that should no longer be on-screen - scrollableContent.RemoveAll(delegate(Panel p) + if (!yPositionsCache.IsValid) + computeYPositions(); + + // Remove all items that should no longer be on-screen + scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) { - float panelPosY = p.Position.Y; - bool remove = panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !p.IsPresent; + float itemPosY = p.Position.Y; + bool remove = itemPosY < Current - p.DrawHeight || itemPosY > Current + drawHeight || !p.IsPresent; return remove; }); - // Find index range of all panels that should be on-screen - Trace.Assert(panels.Count == yPositions.Count); + // Find index range of all items that should be on-screen + Trace.Assert(items.Count == yPositions.Count); - int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT); + int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(Current + drawHeight); if (lastIndex < 0) { lastIndex = ~lastIndex; - // Add the first panel of the last visible beatmap group to preload its data. - if (lastIndex != 0 && panels[lastIndex - 1] is BeatmapSetHeader) + // Add the first item of the last visible beatmap group to preload its data. + if (lastIndex != 0 && items[lastIndex - 1] is DrawableCarouselBeatmapSet) lastIndex++; } - // Add those panels within the previously found index range that should be displayed. + // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - Panel panel = panels[i]; - if (panel.State == PanelSelectedState.Hidden) - continue; + DrawableCarouselItem item = items[i]; // Only add if we're not already part of the content. - if (!scrollableContent.Contains(panel)) + if (!scrollableContent.Contains(item)) { - // Makes sure headers are always _below_ panels, + // Makes sure headers are always _below_ items, // and depth flows downward. - panel.Depth = i + (panel is BeatmapSetHeader ? panels.Count : 0); + item.Depth = i + (item is DrawableCarouselBeatmapSet ? items.Count : 0); - switch (panel.LoadState) + switch (item.LoadState) { case LoadState.NotLoaded: - LoadComponentAsync(panel); + LoadComponentAsync(item); break; case LoadState.Loading: break; default: - scrollableContent.Add(panel); + scrollableContent.Add(item); break; } } } - // Update externally controlled state of currently visible panels + // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). float halfHeight = drawHeight / 2; - foreach (Panel p in scrollableContent.Children) - updatePanel(p, halfHeight); + foreach (DrawableCarouselItem p in scrollableContent.Children) + updateItem(p, halfHeight); } /// - /// Computes the x-offset of currently visible panels. Makes the carousel appear round. + /// Computes the x-offset of currently visible items. Makes the carousel appear round. /// /// /// Vertical distance from the center of the carousel container @@ -624,20 +559,20 @@ namespace osu.Game.Screens.Select } /// - /// Update a panel's x position and multiplicative alpha based on its y position and + /// Update a item's x position and multiplicative alpha based on its y position and /// the current scroll position. /// - /// The panel to be updated. + /// The item to be updated. /// Half the draw height of the carousel container. - private void updatePanel(Panel p, float halfHeight) + private void updateItem(DrawableCarouselItem p, float halfHeight) { var height = p.IsPresent ? p.DrawHeight : 0; - float panelDrawY = p.Position.Y - Current + height / 2; - float dist = Math.Abs(1f - panelDrawY / halfHeight); + float itemDrawY = p.Position.Y - Current + height / 2; + float dist = Math.Abs(1f - itemDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential - // local transformation we may want to apply (e.g. when a panel gets selected, we + // local transformation we may want to apply (e.g. when a item gets selected, we // may want to smoothly transform it leftwards.) p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs new file mode 100644 index 0000000000..f1bc24db50 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.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 System; +using System.Linq; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselBeatmap : CarouselItem + { + public readonly BeatmapInfo Beatmap; + + public CarouselBeatmap(BeatmapInfo beatmap) + { + Beatmap = beatmap; + State.Value = CarouselItemState.Hidden; + } + + protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this) + { + /*GainedSelection = panelGainedSelection, + HideRequested = p => HideDifficultyRequested?.Invoke(p), + StartRequested = p => StartRequested?.Invoke(p.beatmap), + EditRequested = p => EditRequested?.Invoke(p.beatmap),*/ + }; + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + + bool match = criteria.Ruleset == null || (Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + + if (!string.IsNullOrEmpty(criteria.SearchText)) + match &= + Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0) || + Beatmap.Version.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0; + + Filtered.Value = !match; + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs new file mode 100644 index 0000000000..525bb6c600 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -0,0 +1,62 @@ +// 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.Game.Beatmaps; + +namespace osu.Game.Screens.Select.Carousel +{ + public class CarouselBeatmapSet : CarouselGroupEagerSelect + { + public readonly List Beatmaps; + + public BeatmapSetInfo BeatmapSet; + + public CarouselBeatmapSet(BeatmapSetInfo beatmapSet) + { + if (beatmapSet == null) throw new ArgumentNullException(nameof(beatmapSet)); + + BeatmapSet = beatmapSet; + + Children = Beatmaps = beatmapSet.Beatmaps + .Where(b => !b.Hidden) + .OrderBy(b => b.RulesetID).ThenBy(b => b.StarDifficulty) + .Select(b => new CarouselBeatmap(b)) + .ToList(); + } + + protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + Filtered.Value = Children.All(i => i.Filtered); + + /*switch (criteria.Sort) + { + default: + case SortMode.Artist: + groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); + break; + case SortMode.Title: + groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); + break; + case SortMode.Author: + groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author.Username, y.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase)); + break; + case SortMode.Difficulty: + groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); + break; + }*/ + } + } + + public enum CarouselItemState + { + Hidden, + NotSelected, + Selected, + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs new file mode 100644 index 0000000000..597fed7a34 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -0,0 +1,54 @@ +// 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 osu.Framework.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Screens.Select.Carousel +{ + /// + /// A group which ensures only one child is selected. + /// + public class CarouselGroup : CarouselItem + { + private readonly List items; + + public readonly Bindable Selected = new Bindable(); + + protected override DrawableCarouselItem CreateDrawableRepresentation() => null; + + protected override IEnumerable Children + { + get { return base.Children; } + set + { + base.Children = value; + value.ForEach(i => i.State.ValueChanged += v => itemStateChanged(i, v)); + } + } + + public CarouselGroup(List items = null) + { + if (items != null) Children = items; + } + + private void itemStateChanged(CarouselItem item, CarouselItemState value) + { + // todo: check state of selected item. + + // ensure we are the only item selected + if (value == CarouselItemState.Selected) + { + foreach (var b in Children) + { + if (item == b) continue; + b.State.Value = CarouselItemState.NotSelected; + } + + State.Value = CarouselItemState.Selected; + Selected.Value = item; + } + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs new file mode 100644 index 0000000000..351f0ed55b --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.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.Linq; + +namespace osu.Game.Screens.Select.Carousel +{ + /// + /// A group which ensures at least one child is selected (if the group itself is selected). + /// + public class CarouselGroupEagerSelect : CarouselGroup + { + public CarouselGroupEagerSelect() + { + State.ValueChanged += v => + { + if (v == CarouselItemState.Selected) + { + foreach (var c in Children.Where(c => c.State.Value == CarouselItemState.Hidden)) + c.State.Value = CarouselItemState.NotSelected; + + if (Children.Any(c => c.Visible) && Children.All(c => c.State != CarouselItemState.Selected)) + Children.First(c => c.Visible).State.Value = CarouselItemState.Selected; + } + }; + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs new file mode 100644 index 0000000000..d6d0615b6e --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -0,0 +1,57 @@ +// 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.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Screens.Select.Carousel +{ + public abstract class CarouselItem + { + public readonly BindableBool Filtered = new BindableBool(); + + public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); + + protected virtual IEnumerable Children { get; set; } + + public bool Visible => State.Value != CarouselItemState.Hidden && !Filtered.Value; + + public readonly Lazy> Drawables; + + protected CarouselItem() + { + Drawables = new Lazy>(() => + { + List items = new List(); + + var self = CreateDrawableRepresentation(); + if (self != null) items.Add(self); + + if (Children != null) + foreach (var c in Children) + items.AddRange(c.Drawables.Value); + + return items; + }); + + State.ValueChanged += v => + { + if (Children == null) return; + + switch (v) + { + case CarouselItemState.Hidden: + case CarouselItemState.NotSelected: + Children.ForEach(c => c.State.Value = CarouselItemState.Hidden); + break; + } + }; + } + + protected abstract DrawableCarouselItem CreateDrawableRepresentation(); + + public virtual void Filter(FilterCriteria criteria) => Children?.ForEach(c => c.Filter(criteria)); + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs similarity index 81% rename from osu.Game/Beatmaps/Drawables/BeatmapPanel.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index d8ba1e9195..5170e0b98b 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -2,84 +2,50 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Configuration; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Input; -using osu.Game.Graphics.Sprites; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class BeatmapPanel : Panel, IHasContextMenu + public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu { - public BeatmapInfo Beatmap; + private readonly BeatmapInfo beatmap; + private readonly Sprite background; - public Action GainedSelection; - public Action StartRequested; - public Action EditRequested; + public Action StartRequested; + public Action EditRequested; public Action HideRequested; private readonly Triangles triangles; private readonly StarCounter starCounter; - protected override void Selected() + [BackgroundDependencyLoader] + private void load(SongSelect songSelect) { - base.Selected(); - - GainedSelection?.Invoke(this); - - background.Colour = ColourInfo.GradientVertical( - new Color4(20, 43, 51, 255), - new Color4(40, 86, 102, 255)); - - triangles.Colour = Color4.White; + StartRequested = songSelect.Start; + EditRequested = songSelect.Edit; } - protected override void Deselected() + public DrawableCarouselBeatmap(CarouselBeatmap panel) + : base(panel) { - base.Deselected(); - - background.Colour = new Color4(20, 43, 51, 255); - triangles.Colour = OsuColour.Gray(0.5f); - } - - protected override bool OnClick(InputState state) - { - if (State == PanelSelectedState.Selected) - StartRequested?.Invoke(this); - - return base.OnClick(state); - } - - public BindableBool Filtered = new BindableBool(); - - protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) - { - if (!IsLoaded) return; - - base.ApplyState(last); - - if (last == PanelSelectedState.Hidden && State != last) - starCounter.ReplayAnimation(); - } - - public BeatmapPanel(BeatmapInfo beatmap) - { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - Beatmap = beatmap; + beatmap = panel.Beatmap; Height *= 0.60f; Children = new Drawable[] @@ -160,11 +126,46 @@ namespace osu.Game.Beatmaps.Drawables }; } + protected override void Selected() + { + base.Selected(); + + background.Colour = ColourInfo.GradientVertical( + new Color4(20, 43, 51, 255), + new Color4(40, 86, 102, 255)); + + triangles.Colour = Color4.White; + } + + protected override void Deselected() + { + base.Deselected(); + + background.Colour = new Color4(20, 43, 51, 255); + triangles.Colour = OsuColour.Gray(0.5f); + } + + protected override bool OnClick(InputState state) + { + if (Item.State == CarouselItemState.Selected) + StartRequested?.Invoke(beatmap); + + return base.OnClick(state); + } + + protected override void ApplyState() + { + if (Item.State.Value != CarouselItemState.Hidden && Alpha == 0) + starCounter.ReplayAnimation(); + + base.ApplyState(); + } + public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)), + new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(beatmap)), }; } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs similarity index 75% rename from osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9bb7b5c737..f4e43d14aa 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -4,46 +4,40 @@ using System; using System.Collections.Generic; using System.Linq; -using OpenTK; -using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; -using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using OpenTK; +using OpenTK.Graphics; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class BeatmapSetHeader : Panel, IHasContextMenu + public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { - public Action GainedSelection; + public Action GainedSelection; public Action DeleteRequested; public Action RestoreHiddenRequested; - private readonly WorkingBeatmap beatmap; + private readonly BeatmapSetInfo beatmapSet; private readonly FillFlowContainer difficultyIcons; - public BeatmapSetHeader(WorkingBeatmap beatmap) + public DrawableCarouselBeatmapSet(CarouselBeatmapSet set) + : base(set) { - if (beatmap == null) - throw new ArgumentNullException(nameof(beatmap)); - - this.beatmap = beatmap; - - difficultyIcons = new FillFlowContainer - { - Margin = new MarginPadding { Top = 5 }, - AutoSizeAxes = Axes.Both, - }; + beatmapSet = set.BeatmapSet; } protected override void Selected() @@ -53,15 +47,17 @@ namespace osu.Game.Beatmaps.Drawables } [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation) + private void load(LocalisationEngine localisation, BeatmapManager manager) { if (localisation == null) throw new ArgumentNullException(nameof(localisation)); + var working = manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()); + Children = new Drawable[] { new DelayedLoadWrapper( - new PanelBackground(beatmap) + new PanelBackground(working) { RelativeSizeAxes = Axes.Both, OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), @@ -76,18 +72,23 @@ namespace osu.Game.Beatmaps.Drawables new OsuSpriteText { Font = @"Exo2.0-BoldItalic", - Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title), + Current = localisation.GetUnicodePreference(beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title), TextSize = 22, Shadow = true, }, new OsuSpriteText { Font = @"Exo2.0-SemiBoldItalic", - Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + Current = localisation.GetUnicodePreference(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist), TextSize = 17, Shadow = true, }, - difficultyIcons + new FillFlowContainer + { + Margin = new MarginPadding { Top = 5 }, + AutoSizeAxes = Axes.Both, + Children = ((CarouselBeatmapSet)Item).Beatmaps.Select(b => new FilterableDifficultyIcon(b)).ToList() + } } } }; @@ -153,27 +154,19 @@ namespace osu.Game.Beatmaps.Drawables } } - public void AddDifficultyIcons(IEnumerable panels) - { - if (panels == null) - throw new ArgumentNullException(nameof(panels)); - - difficultyIcons.AddRange(panels.Select(p => new FilterableDifficultyIcon(p))); - } - public MenuItem[] ContextMenuItems { get { List items = new List(); - if (State == PanelSelectedState.NotSelected) - items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected)); + if (Item.State == CarouselItemState.NotSelected) + items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo))); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmapSet))); - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo))); + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmapSet))); return items.ToArray(); } @@ -183,11 +176,11 @@ namespace osu.Game.Beatmaps.Drawables { private readonly BindableBool filtered = new BindableBool(); - public FilterableDifficultyIcon(BeatmapPanel panel) - : base(panel.Beatmap) + public FilterableDifficultyIcon(CarouselBeatmap item) + : base(item.Beatmap) { - filtered.BindTo(panel.Filtered); - filtered.ValueChanged += v => this.FadeTo(v ? 0.1f : 1, 100); + filtered.BindTo(item.Filtered); + filtered.ValueChanged += v => Schedule(() => this.FadeTo(v ? 0.1f : 1, 100)); filtered.TriggerChange(); } } diff --git a/osu.Game/Beatmaps/Drawables/Panel.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs similarity index 71% rename from osu.Game/Beatmaps/Drawables/Panel.cs rename to osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c990a0ea46..040ff22f4e 100644 --- a/osu.Game/Beatmaps/Drawables/Panel.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,41 +1,44 @@ // 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; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.MathUtils; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -namespace osu.Game.Beatmaps.Drawables +namespace osu.Game.Screens.Select.Carousel { - public class Panel : Container, IStateful + public abstract class DrawableCarouselItem : Container { public const float MAX_HEIGHT = 80; - public event Action StateChanged; - public override bool RemoveWhenNotAlive => false; - private readonly Container nestedContainer; + public override bool IsPresent => base.IsPresent || Item.Visible; + public readonly CarouselItem Item; + + private readonly Container nestedContainer; private readonly Container borderContainer; private readonly Box hoverLayer; protected override Container Content => nestedContainer; - protected Panel() + protected DrawableCarouselItem(CarouselItem item) { + Item = item; + Item.Filtered.ValueChanged += v => Schedule(ApplyState); + Item.State.ValueChanged += v => Schedule(ApplyState); + Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; @@ -59,8 +62,6 @@ namespace osu.Game.Beatmaps.Drawables }, } }); - - Alpha = 0; } private SampleChannel sampleHover; @@ -86,10 +87,7 @@ namespace osu.Game.Beatmaps.Drawables base.OnHoverLost(state); } - public void SetMultiplicativeAlpha(float alpha) - { - borderContainer.Alpha = alpha; - } + public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha; protected override void LoadComplete() { @@ -97,49 +95,30 @@ namespace osu.Game.Beatmaps.Drawables ApplyState(); } - protected virtual void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) + protected virtual void ApplyState() { if (!IsLoaded) return; - switch (state) + switch (Item.State.Value) { - case PanelSelectedState.Hidden: - case PanelSelectedState.NotSelected: + case CarouselItemState.NotSelected: Deselected(); break; - case PanelSelectedState.Selected: + case CarouselItemState.Selected: Selected(); break; } - if (state == PanelSelectedState.Hidden) + if (!Item.Visible) this.FadeOut(300, Easing.OutQuint); else this.FadeIn(250); } - private PanelSelectedState state = PanelSelectedState.NotSelected; - - public PanelSelectedState State - { - get { return state; } - - set - { - if (state == value) - return; - - var last = state; - state = value; - - ApplyState(last); - - StateChanged?.Invoke(State); - } - } - protected virtual void Selected() { + Item.State.Value = CarouselItemState.Selected; + borderContainer.BorderThickness = 2.5f; borderContainer.EdgeEffect = new EdgeEffectParameters { @@ -152,6 +131,8 @@ namespace osu.Game.Beatmaps.Drawables protected virtual void Deselected() { + Item.State.Value = CarouselItemState.NotSelected; + borderContainer.BorderThickness = 0; borderContainer.EdgeEffect = new EdgeEffectParameters { @@ -164,15 +145,8 @@ namespace osu.Game.Beatmaps.Drawables protected override bool OnClick(InputState state) { - State = PanelSelectedState.Selected; + Item.State.Value = CarouselItemState.Selected; return true; } } - - public enum PanelSelectedState - { - Hidden, - NotSelected, - Selected - } } diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index 907c080729..f02d25501e 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -1,14 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; - namespace osu.Game.Screens.Select { public class EditSongSelect : SongSelect { protected override bool ShowFooter => false; - protected override void OnSelected(InputState state) => Exit(); + protected override void Start() => Exit(); } } diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index f410c69212..8e99e29c1f 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,11 +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 osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -18,54 +13,5 @@ namespace osu.Game.Screens.Select public string SearchText; public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; - - private bool canConvert(BeatmapInfo beatmapInfo) => beatmapInfo.RulesetID == Ruleset.ID || beatmapInfo.RulesetID == 0 && Ruleset.ID > 0 && AllowConvertedBeatmaps; - - public void Filter(List groups) - { - foreach (var g in groups) - { - var set = g.BeatmapSet; - - // we only support converts from osu! mode to other modes for now. - // in the future this will have to change, at which point this condition will become a touch more complicated. - bool hasCurrentMode = set.Beatmaps.Any(canConvert); - - bool match = hasCurrentMode; - - if (!string.IsNullOrEmpty(SearchText)) - match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); - - foreach (var panel in g.BeatmapPanels) - panel.Filtered.Value = !canConvert(panel.Beatmap); - - switch (g.State) - { - case BeatmapGroupState.Hidden: - if (match) g.State = BeatmapGroupState.Collapsed; - break; - default: - if (!match) g.State = BeatmapGroupState.Hidden; - break; - } - } - - switch (Sort) - { - default: - case SortMode.Artist: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Title: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Author: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author.Username, y.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase)); - break; - case SortMode.Difficulty: - groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); - break; - } - } } } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 2d3b198478..898c195432 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -1,12 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input; - namespace osu.Game.Screens.Select { public class MatchSongSelect : SongSelect { - protected override void OnSelected(InputState state) => Exit(); + protected override void Start() => Exit(); } } diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index bba6ddf577..565a7a0a01 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -8,7 +8,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -114,22 +113,22 @@ namespace osu.Game.Screens.Select return false; } - protected override void OnSelected(InputState state) + protected override void Start() { if (player != null) return; - if (state?.Keyboard.ControlPressed == true) - { - var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); - var autoType = auto.GetType(); + //if (state?.Keyboard.ControlPressed == true) + //{ + // var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); + // var autoType = auto.GetType(); - var mods = modSelect.SelectedMods.Value; - if (mods.All(m => m.GetType() != autoType)) - { - modSelect.SelectedMods.Value = mods.Concat(new[] { auto }); - removeAutoModOnResume = true; - } - } + // var mods = modSelect.SelectedMods.Value; + // if (mods.All(m => m.GetType() != autoType)) + // { + // modSelect.SelectedMods.Value = mods.Concat(new[] { auto }); + // removeAutoModOnResume = true; + // } + //} Beatmap.Value.Track.Looping = false; Beatmap.Disabled = true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7fb6a82981..b2e2c8bac6 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -67,6 +67,10 @@ namespace osu.Game.Screens.Select public readonly FilterControl FilterControl; + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + protected SongSelect() { const float carousel_width = 640; @@ -106,13 +110,11 @@ namespace osu.Game.Screens.Select Size = new Vector2(carousel_width, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + + //todo: clicking play on another map doesn't work bindable disabled SelectionChanged = carouselSelectionChanged, BeatmapsChanged = carouselBeatmapsLoaded, - DeleteRequested = promptDelete, - RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); }, - EditRequested = editRequested, - HideDifficultyRequested = b => beatmaps.Hide(b), - StartRequested = () => carouselRaisedStart(), + //RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); }, }); Add(FilterControl = new FilterControl { @@ -163,12 +165,14 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { + dependencies.Cache(this); + if (Footer != null) { Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => promptDelete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => Delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); } if (this.beatmaps == null) @@ -197,7 +201,7 @@ namespace osu.Game.Screens.Select carousel.AllowSelection = !Beatmap.Disabled; } - private void editRequested(BeatmapInfo beatmap) + public void Edit(BeatmapInfo beatmap) { Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); Push(new Editor()); @@ -229,7 +233,7 @@ namespace osu.Game.Screens.Select carousel.SelectNextRandom(); } - private void carouselRaisedStart(InputState state = null) + public void Start(BeatmapInfo beatmap) { // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). @@ -242,7 +246,9 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - OnSelected(state); + carousel.SelectBeatmap(beatmap); + + Start(); } private ScheduledDelegate selectionChangedDebounce; @@ -261,7 +267,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 = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); ensurePlayingSelected(preview); @@ -301,7 +307,7 @@ namespace osu.Game.Screens.Select carousel.SelectNextRandom(); } - protected abstract void OnSelected(InputState state); + protected abstract void Start(); private void filterChanged(FilterCriteria criteria, bool debounce = true) { @@ -346,7 +352,7 @@ namespace osu.Game.Screens.Select logo.Action = () => { - carouselRaisedStart(); + Start(); return false; }; } @@ -458,7 +464,7 @@ namespace osu.Game.Screens.Select Beatmap.SetDefault(); } - private void promptDelete(BeatmapSetInfo beatmap) + public void Delete(BeatmapSetInfo beatmap) { if (beatmap == null) return; @@ -474,13 +480,13 @@ namespace osu.Game.Screens.Select { case Key.KeypadEnter: case Key.Enter: - carouselRaisedStart(state); + Start(); return true; case Key.Delete: if (state.Keyboard.ShiftPressed) { if (!Beatmap.IsDefault) - promptDelete(Beatmap.Value.BeatmapSetInfo); + Delete(Beatmap.Value.BeatmapSetInfo); return true; } break; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 53ad323134..0bc7fa3f67 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -262,10 +262,7 @@ - - - @@ -316,7 +313,6 @@ - @@ -756,6 +752,14 @@ + + + + + + + + @@ -855,4 +859,4 @@ - + \ No newline at end of file From 99b00143ebf59b5a0846a6c923f0100f62293739 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 12:46:02 +0900 Subject: [PATCH 099/239] More clean-ups and event bindings --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Configuration/SelectionRandomType.cs | 4 +- .../Sections/Gameplay/SongSelectSettings.cs | 4 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 63 ++++----- .../Select/Carousel/CarouselBeatmap.cs | 10 +- .../Carousel/DrawableCarouselBeatmap.cs | 3 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 60 ++++----- osu.Game/Screens/Select/SongSelect.cs | 120 +++++++++--------- osu.Game/osu.Game.csproj | 2 +- 9 files changed, 116 insertions(+), 152 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1a7d29e907..1f7808dceb 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Configuration Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); - Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); + Set(OsuSetting.SelectionRandomType, SongSelectRandomMode.RandomPermutation); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); diff --git a/osu.Game/Configuration/SelectionRandomType.cs b/osu.Game/Configuration/SelectionRandomType.cs index 298ee71e36..4ff52fab37 100644 --- a/osu.Game/Configuration/SelectionRandomType.cs +++ b/osu.Game/Configuration/SelectionRandomType.cs @@ -5,11 +5,11 @@ using System.ComponentModel; namespace osu.Game.Configuration { - public enum SelectionRandomType + public enum SongSelectRandomMode { [Description("Never repeat")] RandomPermutation, [Description("Random")] Random } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 9875ee8004..890c28aa07 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 1f }, - new SettingsEnumDropdown + new SettingsEnumDropdown { LabelText = "Random beatmap selection", - Bindable = config.GetBindable(OsuSetting.SelectionRandomType), + Bindable = config.GetBindable(OsuSetting.SelectionRandomType), } }; } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index cede452402..c332f26c09 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 newSets) - addGroup(g); + addBeatmapSet(g); root = new CarouselGroup(newSets.OfType().ToList()); items = root.Drawables.Value.ToList(); @@ -65,12 +65,13 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); + private Cached yPositionsCache = new Cached(); private readonly Container scrollableContent; private readonly List carouselSets = new List(); - private Bindable randomType; + private Bindable randomType; private readonly List seenSets = new List(); private List items = new List(); @@ -95,7 +96,7 @@ namespace osu.Game.Screens.Select public void RemoveBeatmap(BeatmapSetInfo beatmapSet) { - Schedule(() => removeGroup(carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); + Schedule(() => removeBeatmapSet(carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Select if (index >= 0) carouselSets.Insert(index, newSet); else - addGroup(newSet); + addBeatmapSet(newSet); } if (hadSelection && newSet == null) @@ -151,7 +152,7 @@ namespace osu.Game.Screens.Select var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); if (item != null) { - select(item, animated); + select(item); return; } } @@ -220,7 +221,7 @@ namespace osu.Game.Screens.Select CarouselBeatmapSet group; - if (randomType == SelectionRandomType.RandomPermutation) + if (randomType == SongSelectRandomMode.RandomPermutation) { var notSeenGroups = visibleGroups.Except(seenSets); if (!notSeenGroups.Any()) @@ -337,10 +338,10 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { - randomType = config.GetBindable(OsuSetting.SelectionRandomType); + randomType = config.GetBindable(OsuSetting.SelectionRandomType); } - private void addGroup(CarouselBeatmapSet set) + private void addBeatmapSet(CarouselBeatmapSet set) { // prevent duplicates by concurrent independent actions trying to add a group //todo: check this @@ -351,19 +352,11 @@ namespace osu.Game.Screens.Select carouselSets.Add(set); } - private void removeGroup(CarouselBeatmapSet set) + private void removeBeatmapSet(CarouselBeatmapSet set) { if (set == null) return; - if (set.State == CarouselItemState.Selected) - { - if (getVisibleGroups().Count() == 1) - selectNullBeatmap(); - else - SelectNext(); - } - carouselSets.Remove(set); foreach (var d in set.Drawables.Value) @@ -372,11 +365,12 @@ namespace osu.Game.Screens.Select scrollableContent.Remove(d); } + if (set.State == CarouselItemState.Selected) + SelectNext(); + yPositionsCache.Invalidate(); } - private Cached yPositionsCache = new Cached(); - /// /// Computes the target Y positions for every item in the carousel. /// @@ -396,10 +390,7 @@ namespace osu.Game.Screens.Select { case DrawableCarouselBeatmapSet set: set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - lastSetY = set.Position.Y; - - movePanel(set, set.Item.Visible, animated, ref currentY); break; case DrawableCarouselBeatmap beatmap: beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); @@ -410,10 +401,14 @@ namespace osu.Game.Screens.Select // on first display we want to begin hidden under our group's header. if (animated && !beatmap.IsPresent) beatmap.MoveToY(lastSetY); - - movePanel(beatmap, beatmap.Item.Visible, animated, ref currentY); break; } + + yPositions.Add(currentY); + d.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); + + if (d.Item.Visible) + currentY += d.DrawHeight + 5; } currentY += DrawHeight / 2; @@ -424,25 +419,11 @@ namespace osu.Game.Screens.Select return selectedY; } - private void movePanel(DrawableCarouselItem item, bool advance, bool animated, ref float currentY) + private void select(CarouselItem item) { - yPositions.Add(currentY); - item.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); + if (item == null) return; - if (advance) - currentY += item.DrawHeight + 5; - } - - private void select(CarouselBeatmapSet beatmapSet = null) - { - if (beatmapSet == null) return; - beatmapSet.State.Value = CarouselItemState.Selected; - } - - private void select(CarouselBeatmap beatmap = null, bool animated = true) - { - if (beatmap == null) return; - beatmap.State.Value = CarouselItemState.Selected; + item.State.Value = CarouselItemState.Selected; } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index f1bc24db50..655579269f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -17,19 +17,13 @@ namespace osu.Game.Screens.Select.Carousel State.Value = CarouselItemState.Hidden; } - protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this) - { - /*GainedSelection = panelGainedSelection, - HideRequested = p => HideDifficultyRequested?.Invoke(p), - StartRequested = p => StartRequested?.Invoke(p.beatmap), - EditRequested = p => EditRequested?.Invoke(p.beatmap),*/ - }; + protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); public override void Filter(FilterCriteria criteria) { base.Filter(criteria); - bool match = criteria.Ruleset == null || (Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps; if (!string.IsNullOrEmpty(criteria.SearchText)) match &= diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5170e0b98b..61a916d0a3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -36,10 +36,11 @@ namespace osu.Game.Screens.Select.Carousel private readonly StarCounter starCounter; [BackgroundDependencyLoader] - private void load(SongSelect songSelect) + private void load(SongSelect songSelect, BeatmapManager manager) { StartRequested = songSelect.Start; EditRequested = songSelect.Edit; + HideRequested = manager.Hide; } public DrawableCarouselBeatmap(CarouselBeatmap panel) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index f4e43d14aa..9a1ee663a1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -24,10 +24,7 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { - public Action GainedSelection; - public Action DeleteRequested; - public Action RestoreHiddenRequested; private readonly BeatmapSetInfo beatmapSet; @@ -40,18 +37,15 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet = set.BeatmapSet; } - protected override void Selected() - { - base.Selected(); - GainedSelection?.Invoke(this); - } - [BackgroundDependencyLoader] private void load(LocalisationEngine localisation, BeatmapManager manager) { if (localisation == null) throw new ArgumentNullException(nameof(localisation)); + RestoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); + DeleteRequested = manager.Delete; + var working = manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()); Children = new Drawable[] @@ -61,7 +55,8 @@ namespace osu.Game.Screens.Select.Carousel { RelativeSizeAxes = Axes.Both, OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, 300), + }, 300 + ), new FillFlowContainer { Direction = FillDirection.Vertical, @@ -94,6 +89,24 @@ namespace osu.Game.Screens.Select.Carousel }; } + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (Item.State == CarouselItemState.NotSelected) + items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); + + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmapSet))); + + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmapSet))); + + return items.ToArray(); + } + } + private class PanelBackground : BufferedContainer { public PanelBackground(WorkingBeatmap working) @@ -130,22 +143,19 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), + Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, }, new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal( - new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), + Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, }, } @@ -154,24 +164,6 @@ namespace osu.Game.Screens.Select.Carousel } } - public MenuItem[] ContextMenuItems - { - get - { - List items = new List(); - - if (Item.State == CarouselItemState.NotSelected) - items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - - if (beatmapSet.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmapSet))); - - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmapSet))); - - return items.ToArray(); - } - } - public class FilterableDifficultyIcon : DifficultyIcon { private readonly BindableBool filtered = new BindableBool(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b2e2c8bac6..759a9efa1c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -76,68 +76,68 @@ namespace osu.Game.Screens.Select const float carousel_width = 640; const float filter_height = 100; - Add(new ParallaxContainer + AddRange(new Drawable[] { - Padding = new MarginPadding { Top = filter_height }, - ParallaxAmount = 0.005f, - RelativeSizeAxes = Axes.Both, - Children = new[] + new ParallaxContainer { - new WedgeBackground + Padding = new MarginPadding { Top = filter_height }, + ParallaxAmount = 0.005f, + RelativeSizeAxes = Axes.Both, + Children = new[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, + new WedgeBackground + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = carousel_width * 0.76f }, + } } - } - }); - Add(LeftContent = new Container - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(wedged_container_size.X, 1), - Padding = new MarginPadding - { - Bottom = 50, - Top = wedged_container_size.Y + left_area_padding, - Left = left_area_padding, - Right = left_area_padding * 2, - } - }); - Add(carousel = new BeatmapCarousel - { - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - - //todo: clicking play on another map doesn't work bindable disabled - SelectionChanged = carouselSelectionChanged, - BeatmapsChanged = carouselBeatmapsLoaded, - //RestoreRequested = s => { foreach (var b in s.Beatmaps) beatmaps.Restore(b); }, - }); - Add(FilterControl = new FilterControl - { - RelativeSizeAxes = Axes.X, - Height = filter_height, - FilterChanged = criteria => filterChanged(criteria), - Exit = Exit, - }); - Add(beatmapInfoWedge = new BeatmapInfoWedge - { - Alpha = 0, - Size = wedged_container_size, - RelativeSizeAxes = Axes.X, - Margin = new MarginPadding - { - Top = left_area_padding, - Right = left_area_padding, }, - }); - Add(new ResetScrollContainer(() => carousel.ScrollToSelected()) - { - RelativeSizeAxes = Axes.Y, - Width = 250, + LeftContent = new Container + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(wedged_container_size.X, 1), + Padding = new MarginPadding + { + Bottom = 50, + Top = wedged_container_size.Y + left_area_padding, + Left = left_area_padding, + Right = left_area_padding * 2, + } + }, + carousel = new BeatmapCarousel + { + RelativeSizeAxes = Axes.Y, + Size = new Vector2(carousel_width, 1), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + SelectionChanged = carouselSelectionChanged, + BeatmapsChanged = carouselBeatmapsLoaded, + }, + FilterControl = new FilterControl + { + RelativeSizeAxes = Axes.X, + Height = filter_height, + FilterChanged = c => carousel.Filter(c), + Exit = Exit, + }, + beatmapInfoWedge = new BeatmapInfoWedge + { + Alpha = 0, + Size = wedged_container_size, + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding + { + Top = left_area_padding, + Right = left_area_padding, + }, + }, + new ResetScrollContainer(() => carousel.ScrollToSelected()) + { + RelativeSizeAxes = Axes.Y, + Width = 250, + } }); if (ShowFooter) @@ -309,11 +309,6 @@ namespace osu.Game.Screens.Select protected abstract void Start(); - private void filterChanged(FilterCriteria criteria, bool debounce = true) - { - carousel.Filter(criteria, debounce); - } - private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); @@ -489,6 +484,7 @@ namespace osu.Game.Screens.Select Delete(Beatmap.Value.BeatmapSetInfo); return true; } + break; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0bc7fa3f67..54c41ead80 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -343,7 +343,7 @@ - + From 1146ba02d713fd3781d7450e4e4f91eb93d2f2fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 18:09:14 +0900 Subject: [PATCH 100/239] Make GetWorkingBeatmap return a sane default rather than exception on lookup failure --- osu.Game/Beatmaps/BeatmapManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b0fbef235b..c4b2c93d7e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -374,12 +374,9 @@ namespace osu.Game.Beatmaps /// A instance correlating to the provided . public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { - if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; - if (beatmapInfo.BeatmapSet == null) - throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database."); - if (beatmapInfo.Metadata == null) beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; From b9298325a3168f191289a278af4061814ecf6a67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 18:10:32 +0900 Subject: [PATCH 101/239] Rename weird config setting --- osu.Game/Configuration/OsuConfigManager.cs | 4 +-- ...RandomType.cs => RandomSelectAlgorithm.cs} | 30 +++++++++---------- .../Sections/Gameplay/SongSelectSettings.cs | 6 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- osu.Game/osu.Game.csproj | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) rename osu.Game/Configuration/{SelectionRandomType.cs => RandomSelectAlgorithm.cs} (87%) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 1f7808dceb..f4c7bdb586 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Configuration Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); - Set(OsuSetting.SelectionRandomType, SongSelectRandomMode.RandomPermutation); + Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); @@ -108,7 +108,7 @@ namespace osu.Game.Configuration SaveUsername, DisplayStarsMinimum, DisplayStarsMaximum, - SelectionRandomType, + RandomSelectAlgorithm, SnakingInSliders, SnakingOutSliders, ShowFpsDisplay, diff --git a/osu.Game/Configuration/SelectionRandomType.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs similarity index 87% rename from osu.Game/Configuration/SelectionRandomType.cs rename to osu.Game/Configuration/RandomSelectAlgorithm.cs index 4ff52fab37..9ed6b9357b 100644 --- a/osu.Game/Configuration/SelectionRandomType.cs +++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs @@ -1,15 +1,15 @@ -// 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.Configuration -{ - public enum SongSelectRandomMode - { - [Description("Never repeat")] - RandomPermutation, - [Description("Random")] - Random - } -} +// 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.Configuration +{ + public enum RandomSelectAlgorithm + { + [Description("Never repeat")] + RandomPermutation, + [Description("Random")] + Random + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 890c28aa07..03cd2118b9 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay Bindable = config.GetBindable(OsuSetting.DisplayStarsMaximum), KeyboardStep = 1f }, - new SettingsEnumDropdown + new SettingsEnumDropdown { - LabelText = "Random beatmap selection", - Bindable = config.GetBindable(OsuSetting.SelectionRandomType), + LabelText = "Random selection algorithm", + Bindable = config.GetBindable(OsuSetting.RandomSelectAlgorithm), } }; } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c332f26c09..f61c8f60ef 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Select private readonly List carouselSets = new List(); - private Bindable randomType; + private Bindable randomSelectAlgorithm; private readonly List seenSets = new List(); private List items = new List(); @@ -221,7 +221,7 @@ namespace osu.Game.Screens.Select CarouselBeatmapSet group; - if (randomType == SongSelectRandomMode.RandomPermutation) + if (randomSelectAlgorithm == RandomSelectAlgorithm.RandomPermutation) { var notSeenGroups = visibleGroups.Except(seenSets); if (!notSeenGroups.Any()) @@ -338,7 +338,6 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { - randomType = config.GetBindable(OsuSetting.SelectionRandomType); } private void addBeatmapSet(CarouselBeatmapSet set) @@ -350,6 +349,7 @@ namespace osu.Game.Screens.Select //todo: add to root carouselSets.Add(set); + randomSelectAlgorithm = config.GetBindable(OsuSetting.RandomSelectAlgorithm); } private void removeBeatmapSet(CarouselBeatmapSet set) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 54c41ead80..c190d988fa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -343,7 +343,7 @@ - + From 1b859524418c876c51056cca7e648ff27bb83c78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 19:56:16 +0900 Subject: [PATCH 102/239] Cleanup and basic TestCase --- .../Visual/TestCaseBeatmapCarousel.cs | 132 ++++++++++++++++++ .../Visual/TestCasePlaySongSelect.cs | 15 +- osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Screens/Select/BeatmapCarousel.cs | 52 +++---- .../Select/Carousel/CarouselBeatmapSet.cs | 7 - .../Screens/Select/Carousel/CarouselItem.cs | 7 + .../Carousel/DrawableCarouselBeatmap.cs | 13 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 4 +- .../Select/Carousel/DrawableCarouselItem.cs | 2 + osu.Game/Screens/Select/SongSelect.cs | 2 +- 10 files changed, 193 insertions(+), 42 deletions(-) create mode 100644 osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs new file mode 100644 index 0000000000..f0a0e3405a --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -0,0 +1,132 @@ +// 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 System.Linq; +using System.Text; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; + +namespace osu.Game.Tests.Visual +{ + internal class TestCaseBeatmapCarousel : OsuTestCase + { + private BeatmapCarousel carousel; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), + }; + + [BackgroundDependencyLoader] + private void load() + { + Add(carousel = new BeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + }); + + AddStep("Load Beatmaps", () => + { + carousel.Beatmaps = new[] + { + createTestBeatmapSet(0), + createTestBeatmapSet(1), + createTestBeatmapSet(2), + createTestBeatmapSet(3), + }; + }); + + AddUntilStep(() => carousel.Beatmaps.Any(), "Wait for load"); + + AddStep("SelectNext set", () => carousel.SelectNext()); + AddAssert("set1 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.First()); + + AddStep("SelectNext diff", () => carousel.SelectNext(1, false)); + AddAssert("set1 diff2", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.Skip(1).First()); + + AddStep("SelectNext backwards", () => carousel.SelectNext(-1)); + AddAssert("set4 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.Last().Beatmaps.First()); + + AddStep("SelectNext diff backwards", () => carousel.SelectNext(-1, false)); + AddAssert("set3 diff3", () => carousel.SelectedBeatmap == carousel.Beatmaps.Reverse().Skip(1).First().Beatmaps.Last()); + + AddStep("SelectNext", () => carousel.SelectNext()); + AddStep("SelectNext", () => carousel.SelectNext()); + AddAssert("set1 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.First()); + + AddStep("SelectNext diff backwards", () => carousel.SelectNext(-1, false)); + AddAssert("set4 diff3", () => carousel.SelectedBeatmap == carousel.Beatmaps.Last().Beatmaps.Last()); + + // AddStep("Clear beatmaps", () => carousel.Beatmaps = new BeatmapSetInfo[] { }); + // AddStep("SelectNext (noop)", () => carousel.SelectNext()); + } + + private BeatmapSetInfo createTestBeatmapSet(int i) + { + return new BeatmapSetInfo + { + OnlineBeatmapSetID = 1234 + i, + Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), + Metadata = new BeatmapMetadata + { + OnlineBeatmapSetID = 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), + AuthorString = "Some Guy " + RNG.Next(0, 9), + }, + Beatmaps = new List(new[] + { + new BeatmapInfo + { + OnlineBeatmapID = 1234 + i, + Path = "normal.osu", + Version = "Normal", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 3.5f, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = 1235 + i, + Path = "hard.osu", + Version = "Hard", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 5, + } + }, + new BeatmapInfo + { + OnlineBeatmapID = 1236 + i, + Path = "insane.osu", + Version = "Insane", + BaseDifficulty = new BeatmapDifficulty + { + OverallDifficulty = 7, + } + }, + }), + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 16c757702c..d4cef5b160 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; using osu.Game.Tests.Platform; @@ -28,8 +29,20 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { - typeof(BeatmapCarousel), typeof(SongSelect), + typeof(BeatmapCarousel), + + typeof(CarouselItem), + typeof(CarouselGroup), + typeof(CarouselGroupEagerSelect), + typeof(CarouselBeatmap), + typeof(CarouselBeatmapSet), + + typeof(DrawableCarouselItem), + typeof(CarouselItemState), + + typeof(DrawableCarouselBeatmap), + typeof(DrawableCarouselBeatmapSet), }; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 1542269f00..184561faa7 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -88,6 +88,7 @@ + diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f61c8f60ef..970aba0a6d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -26,20 +26,35 @@ namespace osu.Game.Screens.Select { public class BeatmapCarousel : OsuScrollContainer { + /// + /// Triggered when the loaded change and are completely loaded. + /// + public Action BeatmapsChanged; + + /// + /// The currently selected beatmap. + /// public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; - public override bool HandleInput => AllowSelection; + /// + /// Raised when the is changed. + /// + public Action SelectionChanged; - public Action BeatmapsChanged; + public override bool HandleInput => AllowSelection; public IEnumerable Beatmaps { get { return carouselSets.Select(g => g.BeatmapSet); } set { - scrollableContent.Clear(false); - items.Clear(); - carouselSets.Clear(); + Schedule(() => + { + scrollableContent.Clear(false); + items.Clear(); + carouselSets.Clear(); + yPositionsCache.Invalidate(); + }); List newSets = null; @@ -51,8 +66,7 @@ namespace osu.Game.Screens.Select { Schedule(() => { - foreach (var g in newSets) - addBeatmapSet(g); + carouselSets.AddRange(newSets); root = new CarouselGroup(newSets.OfType().ToList()); items = root.Drawables.Value.ToList(); @@ -94,7 +108,7 @@ namespace osu.Game.Screens.Select }); } - public void RemoveBeatmap(BeatmapSetInfo beatmapSet) + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { Schedule(() => removeBeatmapSet(carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); } @@ -116,8 +130,8 @@ namespace osu.Game.Screens.Select { if (index >= 0) carouselSets.Insert(index, newSet); - else - addBeatmapSet(newSet); + //else + // addBeatmapSet(newSet); } if (hadSelection && newSet == null) @@ -158,8 +172,6 @@ namespace osu.Game.Screens.Select } } - public Action SelectionChanged; - private void selectNullBeatmap() { selectedBeatmap = null; @@ -180,7 +192,7 @@ namespace osu.Game.Screens.Select return; } - int originalIndex = Math.Max(0, items.IndexOf(selectedBeatmap?.Drawables.Value.First())); + int originalIndex = items.IndexOf(selectedBeatmap?.Drawables.Value.First()); int currentIndex = originalIndex; // local function to increment the index in the required direction, wrapping over extremities. @@ -338,17 +350,6 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { - } - - private void addBeatmapSet(CarouselBeatmapSet set) - { - // prevent duplicates by concurrent independent actions trying to add a group - //todo: check this - if (carouselSets.Any(g => g.BeatmapSet.ID == set.BeatmapSet.ID)) - return; - - //todo: add to root - carouselSets.Add(set); randomSelectAlgorithm = config.GetBindable(OsuSetting.RandomSelectAlgorithm); } @@ -422,7 +423,6 @@ namespace osu.Game.Screens.Select private void select(CarouselItem item) { if (item == null) return; - item.State.Value = CarouselItemState.Selected; } @@ -466,7 +466,7 @@ namespace osu.Game.Screens.Select computeYPositions(); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) + scrollableContent.RemoveAll(delegate(DrawableCarouselItem p) { float itemPosY = p.Position.Y; bool remove = itemPosY < Current - p.DrawHeight || itemPosY > Current + drawHeight || !p.IsPresent; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 525bb6c600..d0ccda0e28 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -52,11 +52,4 @@ namespace osu.Game.Screens.Select.Carousel }*/ } } - - public enum CarouselItemState - { - Hidden, - NotSelected, - Selected, - } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index d6d0615b6e..68148814e0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -54,4 +54,11 @@ namespace osu.Game.Screens.Select.Carousel public virtual void Filter(FilterCriteria criteria) => Children?.ForEach(c => c.Filter(criteria)); } + + public enum CarouselItemState + { + Hidden, + NotSelected, + Selected, + } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 61a916d0a3..1fca23a2ec 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -35,12 +35,17 @@ namespace osu.Game.Screens.Select.Carousel private readonly Triangles triangles; private readonly StarCounter starCounter; - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(SongSelect songSelect, BeatmapManager manager) { - StartRequested = songSelect.Start; - EditRequested = songSelect.Edit; - HideRequested = manager.Hide; + if (songSelect != null) + { + StartRequested = songSelect.Start; + EditRequested = songSelect.Edit; + } + + if (manager != null) + HideRequested = manager.Hide; } public DrawableCarouselBeatmap(CarouselBeatmap panel) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9a1ee663a1..9987cc4a38 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -46,12 +46,10 @@ namespace osu.Game.Screens.Select.Carousel RestoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); DeleteRequested = manager.Delete; - var working = manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()); - Children = new Drawable[] { new DelayedLoadWrapper( - new PanelBackground(working) + new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 040ff22f4e..ad91706154 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -42,6 +42,8 @@ namespace osu.Game.Screens.Select.Carousel Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; + Alpha = 0; + AddInternal(borderContainer = new Container { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 759a9efa1c..eec4679bfd 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -454,7 +454,7 @@ namespace osu.Game.Screens.Select private void removeBeatmapSet(BeatmapSetInfo beatmapSet) { - carousel.RemoveBeatmap(beatmapSet); + carousel.RemoveBeatmapSet(beatmapSet); if (carousel.SelectedBeatmap == null) Beatmap.SetDefault(); } From ec4f99c92ef9d530074477edeea7e2b5c1e1ca52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 21:08:12 +0900 Subject: [PATCH 103/239] Clean up tests some more --- .../Visual/TestCaseBeatmapCarousel.cs | 80 ++++++++++++++----- osu.Game/Screens/Select/BeatmapCarousel.cs | 36 ++++----- 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f0a0e3405a..da5865c96d 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -9,7 +9,6 @@ using System.Text; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual { internal class TestCaseBeatmapCarousel : OsuTestCase { - private BeatmapCarousel carousel; + private TestBeatmapCarousel carousel; public override IReadOnlyList RequiredTypes => new[] { @@ -38,7 +37,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Add(carousel = new BeatmapCarousel + Add(carousel = new TestBeatmapCarousel { RelativeSizeAxes = Axes.Both, }); @@ -47,36 +46,66 @@ namespace osu.Game.Tests.Visual { carousel.Beatmaps = new[] { - createTestBeatmapSet(0), createTestBeatmapSet(1), createTestBeatmapSet(2), createTestBeatmapSet(3), + createTestBeatmapSet(4), }; }); + void checkSelected(int set, int diff) => + AddAssert($"selected is set{set} diff{diff}", () => + carousel.SelectedBeatmap == carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + + void advanceSelection(bool diff, int direction = 1, int count = 1) + { + if (count == 1) + AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff)); + else + { + AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff), count); + } + } + + void checkVisibleItemCount(bool diff, int count) => + AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => + carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + AddUntilStep(() => carousel.Beatmaps.Any(), "Wait for load"); - AddStep("SelectNext set", () => carousel.SelectNext()); - AddAssert("set1 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.First()); + advanceSelection(direction: 1, diff: false); + checkSelected(1, 1); - AddStep("SelectNext diff", () => carousel.SelectNext(1, false)); - AddAssert("set1 diff2", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.Skip(1).First()); + advanceSelection(direction: 1, diff: true); + checkSelected(1, 2); - AddStep("SelectNext backwards", () => carousel.SelectNext(-1)); - AddAssert("set4 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.Last().Beatmaps.First()); + advanceSelection(direction: -1, diff: false); + checkSelected(4, 1); - AddStep("SelectNext diff backwards", () => carousel.SelectNext(-1, false)); - AddAssert("set3 diff3", () => carousel.SelectedBeatmap == carousel.Beatmaps.Reverse().Skip(1).First().Beatmaps.Last()); + advanceSelection(direction: -1, diff: true); + checkSelected(3, 3); - AddStep("SelectNext", () => carousel.SelectNext()); - AddStep("SelectNext", () => carousel.SelectNext()); - AddAssert("set1 diff1", () => carousel.SelectedBeatmap == carousel.Beatmaps.First().Beatmaps.First()); + advanceSelection(diff: false); + advanceSelection(diff: false); + checkSelected(1, 1); - AddStep("SelectNext diff backwards", () => carousel.SelectNext(-1, false)); - AddAssert("set4 diff3", () => carousel.SelectedBeatmap == carousel.Beatmaps.Last().Beatmaps.Last()); + advanceSelection(direction: -1, diff: true); + checkSelected(4, 3); - // AddStep("Clear beatmaps", () => carousel.Beatmaps = new BeatmapSetInfo[] { }); - // AddStep("SelectNext (noop)", () => carousel.SelectNext()); + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3" }, false)); + checkVisibleItemCount(diff: false, count: 1); + checkVisibleItemCount(diff: true, count: 3); + checkSelected(3, 1); + + advanceSelection(diff: true, count: 4); + checkSelected(3, 2); + + AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); + AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + checkVisibleItemCount(diff: false, count: 4); + checkVisibleItemCount(diff: true, count: 3); } private BeatmapSetInfo createTestBeatmapSet(int i) @@ -89,9 +118,9 @@ namespace osu.Game.Tests.Visual { OnlineBeatmapSetID = 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), - AuthorString = "Some Guy " + RNG.Next(0, 9), + Artist = "peppy", + Title = "test set #" + i, + AuthorString = "peppy" }, Beatmaps = new List(new[] { @@ -128,5 +157,12 @@ namespace osu.Game.Tests.Visual }), }; } + + private class TestBeatmapCarousel : BeatmapCarousel + { + public new List Items => base.Items; + + public bool PendingFilterTask => FilterTask != null; + } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 970aba0a6d..9f210e023b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select Schedule(() => { scrollableContent.Clear(false); - items.Clear(); + Items.Clear(); carouselSets.Clear(); yPositionsCache.Invalidate(); }); @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select carouselSets.AddRange(newSets); root = new CarouselGroup(newSets.OfType().ToList()); - items = root.Drawables.Value.ToList(); + Items = root.Drawables.Value.ToList(); yPositionsCache.Invalidate(); BeatmapsChanged?.Invoke(); @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Select private Bindable randomSelectAlgorithm; private readonly List seenSets = new List(); - private List items = new List(); + protected List Items = new List(); private CarouselGroup root = new CarouselGroup(); private readonly Stack randomSelectedBeatmaps = new Stack(); @@ -192,15 +192,15 @@ namespace osu.Game.Screens.Select return; } - int originalIndex = items.IndexOf(selectedBeatmap?.Drawables.Value.First()); + int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.Value.First()); int currentIndex = originalIndex; // local function to increment the index in the required direction, wrapping over extremities. - int incrementIndex() => currentIndex = (currentIndex + direction + items.Count) % items.Count; + int incrementIndex() => currentIndex = (currentIndex + direction + Items.Count) % Items.Count; while (incrementIndex() != originalIndex) { - var item = items[currentIndex].Item; + var item = Items[currentIndex].Item; if (item.Filtered || item.State == CarouselItemState.Selected) continue; @@ -272,13 +272,13 @@ namespace osu.Game.Screens.Select private FilterCriteria criteria = new FilterCriteria(); - private ScheduledDelegate filterTask; + protected ScheduledDelegate FilterTask; public bool AllowSelection = true; public void FlushPendingFilters() { - if (filterTask?.Completed == false) + if (FilterTask?.Completed == false) Filter(null, false); } @@ -289,7 +289,7 @@ namespace osu.Game.Screens.Select Action perform = delegate { - filterTask = null; + FilterTask = null; carouselSets.ForEach(s => s.Filter(criteria)); @@ -301,11 +301,11 @@ namespace osu.Game.Screens.Select select(selectedBeatmap); }; - filterTask?.Cancel(); - filterTask = null; + FilterTask?.Cancel(); + FilterTask = null; if (debounce) - filterTask = Scheduler.AddDelayed(perform, 250); + FilterTask = Scheduler.AddDelayed(perform, 250); else perform(); } @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Select foreach (var d in set.Drawables.Value) { - items.Remove(d); + Items.Remove(d); scrollableContent.Remove(d); } @@ -385,7 +385,7 @@ namespace osu.Game.Screens.Select float lastSetY = 0; - foreach (DrawableCarouselItem d in items) + foreach (DrawableCarouselItem d in Items) { switch (d) { @@ -474,7 +474,7 @@ namespace osu.Game.Screens.Select }); // Find index range of all items that should be on-screen - Trace.Assert(items.Count == yPositions.Count); + Trace.Assert(Items.Count == yPositions.Count); int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; @@ -484,21 +484,21 @@ namespace osu.Game.Screens.Select lastIndex = ~lastIndex; // Add the first item of the last visible beatmap group to preload its data. - if (lastIndex != 0 && items[lastIndex - 1] is DrawableCarouselBeatmapSet) + if (lastIndex != 0 && Items[lastIndex - 1] is DrawableCarouselBeatmapSet) lastIndex++; } // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { - DrawableCarouselItem item = items[i]; + DrawableCarouselItem item = Items[i]; // Only add if we're not already part of the content. if (!scrollableContent.Contains(item)) { // Makes sure headers are always _below_ items, // and depth flows downward. - item.Depth = i + (item is DrawableCarouselBeatmapSet ? items.Count : 0); + item.Depth = i + (item is DrawableCarouselBeatmapSet ? Items.Count : 0); switch (item.LoadState) { From 48f30d2bb5b10302b4b159da05476a1e7454738a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 21:24:33 +0900 Subject: [PATCH 104/239] Get ready for more tests --- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index da5865c96d..e77337d488 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -57,6 +57,10 @@ namespace osu.Game.Tests.Visual AddAssert($"selected is set{set} diff{diff}", () => carousel.SelectedBeatmap == carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + void setSelected(int set, int diff) => + AddStep($"select set{set} diff{diff}", () => + carousel.SelectBeatmap(carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + void advanceSelection(bool diff, int direction = 1, int count = 1) { if (count == 1) @@ -106,6 +110,8 @@ namespace osu.Game.Tests.Visual AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); checkVisibleItemCount(diff: false, count: 4); checkVisibleItemCount(diff: true, count: 3); + + setSelected(1, diff: 2); } private BeatmapSetInfo createTestBeatmapSet(int i) From 8646d5d1e081d850482766f60dd471734663c214 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Dec 2017 21:47:42 +0900 Subject: [PATCH 105/239] Add testing and fix filtering only some difficulties --- .../Visual/TestCaseBeatmapCarousel.cs | 12 ++++++++++- osu.Game/Screens/Select/BeatmapCarousel.cs | 21 ++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index e77337d488..f034dc7baf 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -79,6 +79,8 @@ namespace osu.Game.Tests.Visual AddUntilStep(() => carousel.Beatmaps.Any(), "Wait for load"); + // test traversal + advanceSelection(direction: 1, diff: false); checkSelected(1, 1); @@ -98,6 +100,8 @@ namespace osu.Game.Tests.Visual advanceSelection(direction: -1, diff: true); checkSelected(4, 3); + // test basic filtering + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3" }, false)); checkVisibleItemCount(diff: false, count: 1); checkVisibleItemCount(diff: true, count: 3); @@ -111,7 +115,13 @@ namespace osu.Game.Tests.Visual checkVisibleItemCount(diff: false, count: 4); checkVisibleItemCount(diff: true, count: 3); - setSelected(1, diff: 2); + // test filtering some difficulties (and keeping current beatmap set selected). + + setSelected(1, 2); + AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); + checkSelected(1, 1); + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + checkSelected(1, 1); } private BeatmapSetInfo createTestBeatmapSet(int i) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 9f210e023b..5e3efb55c9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -94,6 +94,7 @@ namespace osu.Game.Screens.Select private readonly Stack randomSelectedBeatmaps = new Stack(); private CarouselBeatmap selectedBeatmap; + private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); public BeatmapCarousel() { @@ -291,14 +292,28 @@ namespace osu.Game.Screens.Select { FilterTask = null; + var lastSet = selectedBeatmapSet; + var lastBeatmap = selectedBeatmap; + carouselSets.ForEach(s => s.Filter(criteria)); yPositionsCache.Invalidate(); - if (selectedBeatmap?.Visible != true) - SelectNext(); - else + if (selectedBeatmap?.Filtered == false) select(selectedBeatmap); + else if (lastBeatmap != null && !lastSet.Filtered) + { + var searchable = lastSet.Beatmaps.AsEnumerable(); + + // search forwards first then backwards if nothing found. + var nextAvailable = + searchable.SkipWhile(b => b != lastBeatmap).FirstOrDefault(b => !b.Filtered) ?? + searchable.Reverse().SkipWhile(b => b != lastBeatmap).FirstOrDefault(b => !b.Filtered); + + select(nextAvailable); + } + else + SelectNext(); }; FilterTask?.Cancel(); From 5cbb9b9b1886f750e7a7a16d33ef7f062db3aac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 12:59:35 +0900 Subject: [PATCH 106/239] Fix random and add tests Also exposes SelectedBeatmapSet. --- .../Visual/TestCaseBeatmapCarousel.cs | 58 +++++++++++++- osu.Game/Screens/Select/BeatmapCarousel.cs | 79 ++++++++++--------- osu.Game/Screens/Select/SongSelect.cs | 4 +- 3 files changed, 97 insertions(+), 44 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f034dc7baf..971760239f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -10,6 +10,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; @@ -44,7 +45,7 @@ namespace osu.Game.Tests.Visual AddStep("Load Beatmaps", () => { - carousel.Beatmaps = new[] + carousel.BeatmapSets = new[] { createTestBeatmapSet(1), createTestBeatmapSet(2), @@ -55,11 +56,11 @@ namespace osu.Game.Tests.Visual void checkSelected(int set, int diff) => AddAssert($"selected is set{set} diff{diff}", () => - carousel.SelectedBeatmap == carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); void setSelected(int set, int diff) => AddStep($"select set{set} diff{diff}", () => - carousel.SelectBeatmap(carousel.Beatmaps.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); void advanceSelection(bool diff, int direction = 1, int count = 1) { @@ -77,7 +78,7 @@ namespace osu.Game.Tests.Visual AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); - AddUntilStep(() => carousel.Beatmaps.Any(), "Wait for load"); + AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); // test traversal @@ -122,6 +123,55 @@ namespace osu.Game.Tests.Visual checkSelected(1, 1); AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); checkSelected(1, 1); + + // test random non-repeating algorithm + + Stack selectedSets = new Stack(); + + void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count()); + + void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + setSelected(1, 1); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + prevRandom(); + ensureRandomFetchSuccess(); + prevRandom(); + ensureRandomFetchSuccess(); + + nextRandom(); + ensureRandomDidntRepeat(); + nextRandom(); + ensureRandomDidntRepeat(); + + nextRandom(); + AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); } private BeatmapSetInfo createTestBeatmapSet(int i) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5e3efb55c9..810cc8c408 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -27,15 +27,24 @@ namespace osu.Game.Screens.Select public class BeatmapCarousel : OsuScrollContainer { /// - /// Triggered when the loaded change and are completely loaded. + /// Triggered when the loaded change and are completely loaded. /// - public Action BeatmapsChanged; + public Action BeatmapSetsChanged; /// /// The currently selected beatmap. /// public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; + private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State == CarouselItemState.Selected); + + /// + /// The currently selected beatmap set. + /// + public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; + + private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); + /// /// Raised when the is changed. /// @@ -43,7 +52,7 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; - public IEnumerable Beatmaps + public IEnumerable BeatmapSets { get { return carouselSets.Select(g => g.BeatmapSet); } set @@ -72,7 +81,7 @@ namespace osu.Game.Screens.Select Items = root.Drawables.Value.ToList(); yPositionsCache.Invalidate(); - BeatmapsChanged?.Invoke(); + BeatmapSetsChanged?.Invoke(); }); }); } @@ -85,17 +94,14 @@ namespace osu.Game.Screens.Select private readonly List carouselSets = new List(); - private Bindable randomSelectAlgorithm; - private readonly List seenSets = new List(); + public Bindable RandomAlgorithm = new Bindable(); + private readonly List previouslyVisitedRandomSets = new List(); protected List Items = new List(); private CarouselGroup root = new CarouselGroup(); private readonly Stack randomSelectedBeatmaps = new Stack(); - private CarouselBeatmap selectedBeatmap; - private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); - public BeatmapCarousel() { Add(new OsuContextMenuContainer @@ -173,12 +179,6 @@ namespace osu.Game.Screens.Select } } - private void selectNullBeatmap() - { - selectedBeatmap = null; - SelectionChanged?.Invoke(null); - } - /// /// Increment selection in the carousel in a chosen direction. /// @@ -187,9 +187,9 @@ namespace osu.Game.Screens.Select public void SelectNext(int direction = 1, bool skipDifficulties = true) { // todo: we may want to refactor and remove this as an optimisation in the future. - if (carouselSets.All(g => g.State == CarouselItemState.Hidden)) + if (carouselSets.All(g => !g.Visible)) { - selectNullBeatmap(); + SelectionChanged?.Invoke(null); return; } @@ -218,53 +218,57 @@ namespace osu.Game.Screens.Select } } - private IEnumerable getVisibleGroups() => carouselSets.Where(select => select.State != CarouselItemState.NotSelected); + private IEnumerable getVisibleSets() => carouselSets.Where(select => select.Visible); public void SelectNextRandom() { if (carouselSets.Count == 0) return; - var visibleGroups = getVisibleGroups(); - if (!visibleGroups.Any()) + var visible = getVisibleSets().ToList(); + if (!visible.Any()) return; if (selectedBeatmap != null) + { randomSelectedBeatmaps.Push(selectedBeatmap); - CarouselBeatmapSet group; + // when performing a random, we want to add the current set to the previously visited list + // else the user may be "randomised" to the existing selection. + if (previouslyVisitedRandomSets.LastOrDefault() != selectedBeatmapSet) + previouslyVisitedRandomSets.Add(selectedBeatmapSet); + } - if (randomSelectAlgorithm == RandomSelectAlgorithm.RandomPermutation) + CarouselBeatmapSet set; + + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) { - var notSeenGroups = visibleGroups.Except(seenSets); - if (!notSeenGroups.Any()) + var notYetVisitedSets = visible.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { - seenSets.Clear(); - notSeenGroups = visibleGroups; + previouslyVisitedRandomSets.Clear(); + notYetVisitedSets = visible; } - group = notSeenGroups.ElementAt(RNG.Next(notSeenGroups.Count())); - seenSets.Add(group); + set = notYetVisitedSets.ElementAt(RNG.Next(notYetVisitedSets.Count())); + previouslyVisitedRandomSets.Add(set); } else - group = visibleGroups.ElementAt(RNG.Next(visibleGroups.Count())); + set = visible.ElementAt(RNG.Next(visible.Count())); - CarouselBeatmap item = group.Beatmaps[RNG.Next(group.Beatmaps.Count)]; - - select(item); + select(set.Beatmaps[RNG.Next(set.Beatmaps.Count)]); } public void SelectPreviousRandom() { - if (!randomSelectedBeatmaps.Any()) - return; - while (randomSelectedBeatmaps.Any()) { var beatmap = randomSelectedBeatmaps.Pop(); - if (beatmap.Visible) + if (!beatmap.Filtered) { + if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + previouslyVisitedRandomSets.Remove(selectedBeatmapSet); select(beatmap); break; } @@ -351,7 +355,6 @@ namespace osu.Game.Screens.Select { if (v == CarouselItemState.Selected) { - selectedBeatmap = c; SelectionChanged?.Invoke(c.Beatmap); yPositionsCache.Invalidate(); Schedule(() => ScrollToSelected()); @@ -365,7 +368,7 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuConfigManager config) { - randomSelectAlgorithm = config.GetBindable(OsuSetting.RandomSelectAlgorithm); + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); } private void removeBeatmapSet(CarouselBeatmapSet set) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index eec4679bfd..0c3baf2c07 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = carouselSelectionChanged, - BeatmapsChanged = carouselBeatmapsLoaded, + BeatmapSetsChanged = carouselBeatmapsLoaded, }, FilterControl = new FilterControl { @@ -193,7 +193,7 @@ namespace osu.Game.Screens.Select initialAddSetsTask = new CancellationTokenSource(); - carousel.Beatmaps = this.beatmaps.GetAllUsableBeatmapSets(); + carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); Beatmap.ValueChanged += beatmap_ValueChanged; From 7814b2df1470e90d5409fef7a7452f6d96832481 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 13:49:43 +0900 Subject: [PATCH 107/239] More renaming --- osu.Game/Screens/Select/BeatmapCarousel.cs | 38 ++++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 810cc8c408..94fc9ebdb7 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select /// public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; - private CarouselBeatmapSet selectedBeatmapSet => carouselSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); + private CarouselBeatmapSet selectedBeatmapSet => beatmapSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); /// /// Raised when the is changed. @@ -52,16 +52,18 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; + private readonly List beatmapSets = new List(); + public IEnumerable BeatmapSets { - get { return carouselSets.Select(g => g.BeatmapSet); } + get { return beatmapSets.Select(g => g.BeatmapSet); } set { Schedule(() => { scrollableContent.Clear(false); Items.Clear(); - carouselSets.Clear(); + beatmapSets.Clear(); yPositionsCache.Invalidate(); }); @@ -75,7 +77,7 @@ namespace osu.Game.Screens.Select { Schedule(() => { - carouselSets.AddRange(newSets); + beatmapSets.AddRange(newSets); root = new CarouselGroup(newSets.OfType().ToList()); Items = root.Drawables.Value.ToList(); @@ -92,7 +94,7 @@ namespace osu.Game.Screens.Select private readonly Container scrollableContent; - private readonly List carouselSets = new List(); + public Bindable RandomAlgorithm = new Bindable(); private readonly List previouslyVisitedRandomSets = new List(); @@ -104,7 +106,7 @@ namespace osu.Game.Screens.Select public BeatmapCarousel() { - Add(new OsuContextMenuContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -112,31 +114,31 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X, } - }); + }; } public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { - Schedule(() => removeBeatmapSet(carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); + Schedule(() => removeBeatmapSet(beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { // todo: this method should be smarter as to not recreate items that haven't changed, etc. - var oldGroup = carouselSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + var oldGroup = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); bool hadSelection = oldGroup?.State == CarouselItemState.Selected; var newSet = createGroup(beatmapSet); - int index = carouselSets.IndexOf(oldGroup); + int index = beatmapSets.IndexOf(oldGroup); if (index >= 0) - carouselSets.RemoveAt(index); + beatmapSets.RemoveAt(index); if (newSet != null) { if (index >= 0) - carouselSets.Insert(index, newSet); + beatmapSets.Insert(index, newSet); //else // addBeatmapSet(newSet); } @@ -168,7 +170,7 @@ namespace osu.Game.Screens.Select if (beatmap == SelectedBeatmap) return; - foreach (CarouselBeatmapSet group in carouselSets) + foreach (CarouselBeatmapSet group in beatmapSets) { var item = group.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); if (item != null) @@ -187,7 +189,7 @@ namespace osu.Game.Screens.Select public void SelectNext(int direction = 1, bool skipDifficulties = true) { // todo: we may want to refactor and remove this as an optimisation in the future. - if (carouselSets.All(g => !g.Visible)) + if (beatmapSets.All(g => !g.Visible)) { SelectionChanged?.Invoke(null); return; @@ -218,11 +220,11 @@ namespace osu.Game.Screens.Select } } - private IEnumerable getVisibleSets() => carouselSets.Where(select => select.Visible); + private IEnumerable getVisibleSets() => beatmapSets.Where(select => select.Visible); public void SelectNextRandom() { - if (carouselSets.Count == 0) + if (beatmapSets.Count == 0) return; var visible = getVisibleSets().ToList(); @@ -299,7 +301,7 @@ namespace osu.Game.Screens.Select var lastSet = selectedBeatmapSet; var lastBeatmap = selectedBeatmap; - carouselSets.ForEach(s => s.Filter(criteria)); + beatmapSets.ForEach(s => s.Filter(criteria)); yPositionsCache.Invalidate(); @@ -376,7 +378,7 @@ namespace osu.Game.Screens.Select if (set == null) return; - carouselSets.Remove(set); + beatmapSets.Remove(set); foreach (var d in set.Drawables.Value) { From b4b2f12116ba9a7c5038661f50a7c9e23d6ac6b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 16:59:08 +0900 Subject: [PATCH 108/239] Add support for adding/removing items Tests accompany of course --- .../Visual/TestCaseBeatmapCarousel.cs | 32 ++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 82 +++++++++---------- .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- 3 files changed, 66 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 971760239f..f7f2108f33 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual }); void ensureRandomDidntRepeat() => - AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count()); + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); void prevRandom() => AddStep("select random last", () => { @@ -153,6 +153,7 @@ namespace osu.Game.Tests.Visual AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); setSelected(1, 1); + nextRandom(); ensureRandomDidntRepeat(); nextRandom(); @@ -172,29 +173,44 @@ namespace osu.Game.Tests.Visual nextRandom(); AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); + + // test adding and removing + + AddStep("Add new set #5", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(5))); + AddStep("Add new set #6", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(6))); + + checkVisibleItemCount(false, 6); + + AddStep("Remove set #4", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(4))); + + checkVisibleItemCount(false, 5); + + } private BeatmapSetInfo createTestBeatmapSet(int i) { return new BeatmapSetInfo { - OnlineBeatmapSetID = 1234 + i, + ID = i, + OnlineBeatmapSetID = i, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { - OnlineBeatmapSetID = 1234 + i, + OnlineBeatmapSetID = i, // Create random metadata, then we can check if sorting works based on these Artist = "peppy", Title = "test set #" + i, - AuthorString = "peppy" + AuthorString = "peppy", }, Beatmaps = new List(new[] { new BeatmapInfo { - OnlineBeatmapID = 1234 + i, + OnlineBeatmapID = i * 10, Path = "normal.osu", Version = "Normal", + StarDifficulty = 2, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, @@ -202,9 +218,10 @@ namespace osu.Game.Tests.Visual }, new BeatmapInfo { - OnlineBeatmapID = 1235 + i, + OnlineBeatmapID = i * 10 + 1, Path = "hard.osu", Version = "Hard", + StarDifficulty = 5, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 5, @@ -212,9 +229,10 @@ namespace osu.Game.Tests.Visual }, new BeatmapInfo { - OnlineBeatmapID = 1236 + i, + OnlineBeatmapID = i * 10 + 2, Path = "insane.osu", Version = "Insane", + StarDifficulty = 6, BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 7, diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 94fc9ebdb7..bdfb772704 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -71,39 +71,45 @@ namespace osu.Game.Screens.Select Task.Run(() => { - newSets = value.Select(createGroup).Where(g => g != null).ToList(); + newSets = value.Select(createCarouselSet).Where(g => g != null).ToList(); newSets.ForEach(g => g.Filter(criteria)); }).ContinueWith(t => { Schedule(() => { beatmapSets.AddRange(newSets); - - root = new CarouselGroup(newSets.OfType().ToList()); - Items = root.Drawables.Value.ToList(); - - yPositionsCache.Invalidate(); - BeatmapSetsChanged?.Invoke(); + updateItems(); }); }); } } + /// + /// Call after altering in any way. + /// + private void updateItems() + { + scrollableContent.Clear(false); + + root = new CarouselGroup(beatmapSets.OfType().ToList()); + Items = root.Drawables.Value.ToList(); + + yPositionsCache.Invalidate(); + BeatmapSetsChanged?.Invoke(); + } + private readonly List yPositions = new List(); private Cached yPositionsCache = new Cached(); private readonly Container scrollableContent; - - public Bindable RandomAlgorithm = new Bindable(); private readonly List previouslyVisitedRandomSets = new List(); + private readonly Stack randomSelectedBeatmaps = new Stack(); protected List Items = new List(); private CarouselGroup root = new CarouselGroup(); - private readonly Stack randomSelectedBeatmaps = new Stack(); - public BeatmapCarousel() { Child = new OsuContextMenuContainer @@ -119,19 +125,28 @@ namespace osu.Game.Screens.Select public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { - Schedule(() => removeBeatmapSet(beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID))); + var existingSet = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + + if (existingSet == null) + return; + + beatmapSets.Remove(existingSet); + + updateItems(); + + if (existingSet.State == CarouselItemState.Selected) + SelectNext(); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - // todo: this method should be smarter as to not recreate items that haven't changed, etc. - var oldGroup = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + CarouselBeatmapSet existingSet = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); - bool hadSelection = oldGroup?.State == CarouselItemState.Selected; + bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; - var newSet = createGroup(beatmapSet); + var newSet = createCarouselSet(beatmapSet); - int index = beatmapSets.IndexOf(oldGroup); + int index = beatmapSets.IndexOf(existingSet); if (index >= 0) beatmapSets.RemoveAt(index); @@ -139,8 +154,8 @@ namespace osu.Game.Screens.Select { if (index >= 0) beatmapSets.Insert(index, newSet); - //else - // addBeatmapSet(newSet); + else + beatmapSets.Add(newSet); } if (hadSelection && newSet == null) @@ -154,10 +169,12 @@ namespace osu.Game.Screens.Select var newSelection = newSet.Beatmaps.Find(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID); if (newSelection == null && selectedBeatmap != null) - newSelection = newSet.Beatmaps[Math.Min(newSet.Beatmaps.Count - 1, oldGroup.Beatmaps.IndexOf(selectedBeatmap))]; + newSelection = newSet.Beatmaps[Math.Min(newSet.Beatmaps.Count - 1, existingSet.Beatmaps.IndexOf(selectedBeatmap))]; select(newSelection); } + + updateItems(); } public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) @@ -252,11 +269,11 @@ namespace osu.Game.Screens.Select notYetVisitedSets = visible; } - set = notYetVisitedSets.ElementAt(RNG.Next(notYetVisitedSets.Count())); + set = notYetVisitedSets.ElementAt(RNG.Next(notYetVisitedSets.Count)); previouslyVisitedRandomSets.Add(set); } else - set = visible.ElementAt(RNG.Next(visible.Count())); + set = visible.ElementAt(RNG.Next(visible.Count)); select(set.Beatmaps[RNG.Next(set.Beatmaps.Count)]); } @@ -337,7 +354,7 @@ namespace osu.Game.Screens.Select ScrollTo(selectedY, animated); } - private CarouselBeatmapSet createGroup(BeatmapSetInfo beatmapSet) + private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { if (beatmapSet.Beatmaps.All(b => b.Hidden)) return null; @@ -373,25 +390,6 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); } - private void removeBeatmapSet(CarouselBeatmapSet set) - { - if (set == null) - return; - - beatmapSets.Remove(set); - - foreach (var d in set.Drawables.Value) - { - Items.Remove(d); - scrollableContent.Remove(d); - } - - if (set.State == CarouselItemState.Selected) - SelectNext(); - - yPositionsCache.Invalidate(); - } - /// /// Computes the target Y positions for every item in the carousel. /// diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 9987cc4a38..b5636ae9ed 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select.Carousel new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), + OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), }, 300 ), new FillFlowContainer From 67f05977ea017e14804a1c546ef350c5da3d42ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 20:40:58 +0900 Subject: [PATCH 109/239] Add sorting support --- .../Visual/TestCaseBeatmapCarousel.cs | 156 +++++++++++------- osu.Game/Screens/Select/BeatmapCarousel.cs | 74 ++++----- .../Select/Carousel/CarouselBeatmap.cs | 17 ++ .../Select/Carousel/CarouselBeatmapSet.cs | 45 ++--- .../Screens/Select/Carousel/CarouselGroup.cs | 15 +- .../Carousel/CarouselGroupEagerSelect.cs | 6 +- .../Screens/Select/Carousel/CarouselItem.cs | 44 +++-- 7 files changed, 204 insertions(+), 153 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f7f2108f33..6163daaf39 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -13,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.Visual { @@ -35,6 +36,9 @@ namespace osu.Game.Tests.Visual typeof(DrawableCarouselBeatmapSet), }; + + private readonly Stack selectedSets = new Stack(); + [BackgroundDependencyLoader] private void load() { @@ -54,34 +58,68 @@ namespace osu.Game.Tests.Visual }; }); - void checkSelected(int set, int diff) => - AddAssert($"selected is set{set} diff{diff}", () => - carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); - - void setSelected(int set, int diff) => - AddStep($"select set{set} diff{diff}", () => - carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); - - void advanceSelection(bool diff, int direction = 1, int count = 1) - { - if (count == 1) - AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff)); - else - { - AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => - carousel.SelectNext(direction, !diff), count); - } - } - - void checkVisibleItemCount(bool diff, int count) => - AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => - carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); - AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); - // test traversal + testTraversal(); + testFiltering(); + testRandom(); + testAddRemove(); + testSorting(); + } + private void ensureRandomFetchSuccess() => + AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + + private void checkSelected(int set, int diff) => + AddAssert($"selected is set{set} diff{diff}", () => + carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + + private void setSelected(int set, int diff) => + AddStep($"select set{set} diff{diff}", () => + carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First())); + + private void advanceSelection(bool diff, int direction = 1, int count = 1) + { + if (count == 1) + AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff)); + else + { + AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () => + carousel.SelectNext(direction, !diff), count); + } + } + + private void checkVisibleItemCount(bool diff, int count) => + AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => + carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); + + private void nextRandom() => + AddStep("select random next", () => + { + carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; + + if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + selectedSets.Push(carousel.SelectedBeatmapSet); + + carousel.SelectNextRandom(); + selectedSets.Push(carousel.SelectedBeatmapSet); + }); + + private void ensureRandomDidntRepeat() => + AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); + + private void prevRandom() => AddStep("select random last", () => + { + carousel.SelectPreviousRandom(); + selectedSets.Pop(); + }); + + /// + /// Test keyboard traversal + /// + private void testTraversal() + { advanceSelection(direction: 1, diff: false); checkSelected(1, 1); @@ -100,8 +138,14 @@ namespace osu.Game.Tests.Visual advanceSelection(direction: -1, diff: true); checkSelected(4, 3); + } - // test basic filtering + /// + /// Test filtering + /// + private void testFiltering() + { + // basic filtering AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3" }, false)); checkVisibleItemCount(diff: false, count: 1); @@ -123,35 +167,13 @@ namespace osu.Game.Tests.Visual checkSelected(1, 1); AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); checkSelected(1, 1); + } - // test random non-repeating algorithm - - Stack selectedSets = new Stack(); - - void nextRandom() => - AddStep("select random next", () => - { - carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; - - if (!selectedSets.Any() && carousel.SelectedBeatmap != null) - selectedSets.Push(carousel.SelectedBeatmapSet); - - carousel.SelectNextRandom(); - selectedSets.Push(carousel.SelectedBeatmapSet); - }); - - void ensureRandomDidntRepeat() => - AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count); - - void prevRandom() => AddStep("select random last", () => - { - carousel.SelectPreviousRandom(); - selectedSets.Pop(); - }); - - void ensureRandomFetchSuccess() => - AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); - + /// + /// Test random non-repeating algorithm + /// + private void testRandom() + { setSelected(1, 1); nextRandom(); @@ -173,21 +195,37 @@ namespace osu.Game.Tests.Visual nextRandom(); AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet)); + } - // test adding and removing - + /// + /// Test adding and removing beatmap sets + /// + private void testAddRemove() + { AddStep("Add new set #5", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(5))); AddStep("Add new set #6", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(6))); checkVisibleItemCount(false, 6); - AddStep("Remove set #4", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(4))); + AddStep("Remove set #5", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(5))); checkVisibleItemCount(false, 5); - + AddStep("Remove set #6", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(6))); } + /// + /// Test sorting + /// + private void testSorting() + { + AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); + AddAssert("Check yyyyy is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "yyyyy"); + AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddAssert("Check #4 is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith("#4")); + } + + private BeatmapSetInfo createTestBeatmapSet(int i) { return new BeatmapSetInfo @@ -201,7 +239,7 @@ namespace osu.Game.Tests.Visual // Create random metadata, then we can check if sorting works based on these Artist = "peppy", Title = "test set #" + i, - AuthorString = "peppy", + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - i), 5)) }, Beatmaps = new List(new[] { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index bdfb772704..e51ce85546 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -17,6 +17,7 @@ using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Threading; using osu.Framework.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -52,32 +53,26 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; - private readonly List beatmapSets = new List(); + private IEnumerable beatmapSets => root.Children?.OfType() ?? new CarouselBeatmapSet[] { }; public IEnumerable BeatmapSets { get { return beatmapSets.Select(g => g.BeatmapSet); } set { - Schedule(() => - { - scrollableContent.Clear(false); - Items.Clear(); - beatmapSets.Clear(); - yPositionsCache.Invalidate(); - }); - List newSets = null; + CarouselGroup newRoot = new CarouselGroup(); + Task.Run(() => { - newSets = value.Select(createCarouselSet).Where(g => g != null).ToList(); - newSets.ForEach(g => g.Filter(criteria)); + value.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); + newRoot.Filter(criteria); }).ContinueWith(t => { Schedule(() => { - beatmapSets.AddRange(newSets); + root = newRoot; updateItems(); }); }); @@ -91,8 +86,7 @@ namespace osu.Game.Screens.Select { scrollableContent.Clear(false); - root = new CarouselGroup(beatmapSets.OfType().ToList()); - Items = root.Drawables.Value.ToList(); + Items = root.Drawables.ToList(); yPositionsCache.Invalidate(); BeatmapSetsChanged?.Invoke(); @@ -125,13 +119,12 @@ namespace osu.Game.Screens.Select public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { - var existingSet = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); if (existingSet == null) return; - beatmapSets.Remove(existingSet); - + root.RemoveChild(existingSet); updateItems(); if (existingSet.State == CarouselItemState.Selected) @@ -140,39 +133,29 @@ namespace osu.Game.Screens.Select public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - CarouselBeatmapSet existingSet = beatmapSets.Find(b => b.BeatmapSet.ID == beatmapSet.ID); + CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; var newSet = createCarouselSet(beatmapSet); - int index = beatmapSets.IndexOf(existingSet); - if (index >= 0) - beatmapSets.RemoveAt(index); + if (existingSet != null) + root.RemoveChild(existingSet); - if (newSet != null) + if (newSet == null) { - if (index >= 0) - beatmapSets.Insert(index, newSet); - else - beatmapSets.Add(newSet); + updateItems(); + SelectNext(); + return; } - if (hadSelection && newSet == null) - SelectNext(); + root.AddChild(newSet); - Filter(null, false); + Filter(debounce: false); //check if we can/need to maintain our current selection. - if (hadSelection && newSet != null) - { - var newSelection = newSet.Beatmaps.Find(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID); - - if (newSelection == null && selectedBeatmap != null) - newSelection = newSet.Beatmaps[Math.Min(newSet.Beatmaps.Count - 1, existingSet.Beatmaps.IndexOf(selectedBeatmap))]; - - select(newSelection); - } + if (hadSelection) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); updateItems(); } @@ -212,7 +195,7 @@ namespace osu.Game.Screens.Select return; } - int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.Value.First()); + int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.First()); int currentIndex = originalIndex; // local function to increment the index in the required direction, wrapping over extremities. @@ -241,7 +224,7 @@ namespace osu.Game.Screens.Select public void SelectNextRandom() { - if (beatmapSets.Count == 0) + if (!beatmapSets.Any()) return; var visible = getVisibleSets().ToList(); @@ -275,7 +258,7 @@ namespace osu.Game.Screens.Select else set = visible.ElementAt(RNG.Next(visible.Count)); - select(set.Beatmaps[RNG.Next(set.Beatmaps.Count)]); + select(set.Beatmaps.Skip(RNG.Next(set.Beatmaps.Count())).FirstOrDefault()); } public void SelectPreviousRandom() @@ -303,7 +286,7 @@ namespace osu.Game.Screens.Select public void FlushPendingFilters() { if (FilterTask?.Completed == false) - Filter(null, false); + Filter(debounce: false); } public void Filter(FilterCriteria newCriteria = null, bool debounce = true) @@ -318,9 +301,8 @@ namespace osu.Game.Screens.Select var lastSet = selectedBeatmapSet; var lastBeatmap = selectedBeatmap; - beatmapSets.ForEach(s => s.Filter(criteria)); - - yPositionsCache.Invalidate(); + root.Filter(criteria); + updateItems(); if (selectedBeatmap?.Filtered == false) select(selectedBeatmap); @@ -337,6 +319,8 @@ namespace osu.Game.Screens.Select } else SelectNext(); + + ScrollToSelected(false); }; FilterTask?.Cancel(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 655579269f..a0d91cf23b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select.Carousel { @@ -32,5 +33,21 @@ namespace osu.Game.Screens.Select.Carousel Filtered.Value = !match; } + + public override int CompareTo(FilterCriteria criteria, CarouselItem other) + { + if (!(other is CarouselBeatmap otherBeatmap)) + return base.CompareTo(criteria, other); + + switch (criteria.Sort) + { + default: + case SortMode.Difficulty: + var ruleset = Beatmap.RulesetID.CompareTo(otherBeatmap.Beatmap.RulesetID); + if (ruleset != 0) return ruleset; + + return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); + } + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index d0ccda0e28..655ed2057d 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -4,52 +4,53 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select.Carousel { public class CarouselBeatmapSet : CarouselGroupEagerSelect { - public readonly List Beatmaps; + public IEnumerable Beatmaps => InternalChildren.OfType(); public BeatmapSetInfo BeatmapSet; public CarouselBeatmapSet(BeatmapSetInfo beatmapSet) { - if (beatmapSet == null) throw new ArgumentNullException(nameof(beatmapSet)); + BeatmapSet = beatmapSet ?? throw new ArgumentNullException(nameof(beatmapSet)); - BeatmapSet = beatmapSet; - - Children = Beatmaps = beatmapSet.Beatmaps - .Where(b => !b.Hidden) - .OrderBy(b => b.RulesetID).ThenBy(b => b.StarDifficulty) - .Select(b => new CarouselBeatmap(b)) - .ToList(); + beatmapSet.Beatmaps + .Where(b => !b.Hidden) + .Select(b => new CarouselBeatmap(b)) + .ForEach(AddChild); } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this); - public override void Filter(FilterCriteria criteria) + public override int CompareTo(FilterCriteria criteria, CarouselItem other) { - base.Filter(criteria); - Filtered.Value = Children.All(i => i.Filtered); + if (!(other is CarouselBeatmapSet otherSet)) + return base.CompareTo(criteria, other); - /*switch (criteria.Sort) + switch (criteria.Sort) { default: case SortMode.Artist: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); - break; + return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); case SortMode.Title: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); - break; + return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); case SortMode.Author: - groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author.Username, y.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase)); - break; + return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); case SortMode.Difficulty: - groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); - break; - }*/ + return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); + } + } + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + Filtered.Value = InternalChildren.All(i => i.Filtered); } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 597fed7a34..695a8e1bc4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Framework.Configuration; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Screens.Select.Carousel { @@ -18,19 +17,15 @@ namespace osu.Game.Screens.Select.Carousel protected override DrawableCarouselItem CreateDrawableRepresentation() => null; - protected override IEnumerable Children + public override void AddChild(CarouselItem i) { - get { return base.Children; } - set - { - base.Children = value; - value.ForEach(i => i.State.ValueChanged += v => itemStateChanged(i, v)); - } + i.State.ValueChanged += v => itemStateChanged(i, v); + base.AddChild(i); } public CarouselGroup(List items = null) { - if (items != null) Children = items; + if (items != null) InternalChildren = items; } private void itemStateChanged(CarouselItem item, CarouselItemState value) @@ -40,7 +35,7 @@ namespace osu.Game.Screens.Select.Carousel // ensure we are the only item selected if (value == CarouselItemState.Selected) { - foreach (var b in Children) + foreach (var b in InternalChildren) { if (item == b) continue; b.State.Value = CarouselItemState.NotSelected; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 351f0ed55b..b9312882a1 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -16,11 +16,11 @@ namespace osu.Game.Screens.Select.Carousel { if (v == CarouselItemState.Selected) { - foreach (var c in Children.Where(c => c.State.Value == CarouselItemState.Hidden)) + foreach (var c in InternalChildren.Where(c => c.State.Value == CarouselItemState.Hidden)) c.State.Value = CarouselItemState.NotSelected; - if (Children.Any(c => c.Visible) && Children.All(c => c.State != CarouselItemState.Selected)) - Children.First(c => c.Visible).State.Value = CarouselItemState.Selected; + if (InternalChildren.Any(c => c.Visible) && InternalChildren.All(c => c.State != CarouselItemState.Selected)) + InternalChildren.First(c => c.Visible).State.Value = CarouselItemState.Selected; } }; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 68148814e0..7b0202cd87 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Configuration; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Screens.Select.Carousel { @@ -14,45 +13,62 @@ namespace osu.Game.Screens.Select.Carousel public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); - protected virtual IEnumerable Children { get; set; } + public IReadOnlyList Children => InternalChildren; + + protected List InternalChildren { get; set; } public bool Visible => State.Value != CarouselItemState.Hidden && !Filtered.Value; - public readonly Lazy> Drawables; - - protected CarouselItem() + public IEnumerable Drawables { - Drawables = new Lazy>(() => + get { List items = new List(); - var self = CreateDrawableRepresentation(); + var self = drawableRepresentation.Value; if (self != null) items.Add(self); - if (Children != null) - foreach (var c in Children) - items.AddRange(c.Drawables.Value); + if (InternalChildren != null) + foreach (var c in InternalChildren) + items.AddRange(c.Drawables); return items; - }); + } + } + + public virtual void AddChild(CarouselItem i) => (InternalChildren ?? (InternalChildren = new List())).Add(i); + + public virtual void RemoveChild(CarouselItem i) => InternalChildren?.Remove(i); + + protected CarouselItem() + { + drawableRepresentation = new Lazy(CreateDrawableRepresentation); State.ValueChanged += v => { - if (Children == null) return; + if (InternalChildren == null) return; switch (v) { case CarouselItemState.Hidden: case CarouselItemState.NotSelected: - Children.ForEach(c => c.State.Value = CarouselItemState.Hidden); + InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Hidden); break; } }; } + private readonly Lazy drawableRepresentation; + protected abstract DrawableCarouselItem CreateDrawableRepresentation(); - public virtual void Filter(FilterCriteria criteria) => Children?.ForEach(c => c.Filter(criteria)); + public virtual void Filter(FilterCriteria criteria) + { + InternalChildren?.Sort((x, y) => x.CompareTo(criteria, y)); + InternalChildren?.ForEach(c => c.Filter(criteria)); + } + + public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => GetHashCode().CompareTo(other.GetHashCode()); } public enum CarouselItemState From 2817ed0d4678b6c5aec1a9bb22f20f29d3eeefb3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 20:45:59 +0900 Subject: [PATCH 110/239] Fix typo --- osu.Game.Tests/Visual/TestCasePlaySongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index d4cef5b160..7421546c43 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); [BackgroundDependencyLoader] - private void load(BeatmapManager baseMaanger) + private void load(BeatmapManager baseManager) { PlaySongSelect songSelect; @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual dependencies.Cache(rulesets = new RulesetStore(contextFactory)); dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null) { - DefaultBeatmap = baseMaanger.GetWorkingBeatmap(null) + DefaultBeatmap = baseManager.GetWorkingBeatmap(null) }); for (int i = 0; i < 100; i += 10) From 48e53a76b0e5e02d9a2b18a8a34ae3ee8e99c728 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Dec 2017 21:51:09 +0900 Subject: [PATCH 111/239] Fix incorrect line endings --- .../Configuration/RandomSelectAlgorithm.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Configuration/RandomSelectAlgorithm.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs index 9ed6b9357b..cde657dba8 100644 --- a/osu.Game/Configuration/RandomSelectAlgorithm.cs +++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs @@ -1,15 +1,15 @@ -// 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.Configuration -{ - public enum RandomSelectAlgorithm - { - [Description("Never repeat")] - RandomPermutation, - [Description("Random")] - Random - } -} +// 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.Configuration +{ + public enum RandomSelectAlgorithm + { + [Description("Never repeat")] + RandomPermutation, + [Description("Random")] + Random + } +} From 2e3332e3fe2b3c69e07012a9f63176c74eff3596 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 00:11:47 +0900 Subject: [PATCH 112/239] Shortcut non-visible panels to avoid adding as drawables --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e51ce85546..1f2bee52a5 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -498,6 +498,8 @@ namespace osu.Game.Screens.Select // Only add if we're not already part of the content. if (!scrollableContent.Contains(item)) { + if (!item.Item.Visible) continue; + // Makes sure headers are always _below_ items, // and depth flows downward. item.Depth = i + (item is DrawableCarouselBeatmapSet ? Items.Count : 0); From 192ceb5465b1df5b1769091c5fcc7250ad11b7ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 00:23:39 +0900 Subject: [PATCH 113/239] Avoid multiple access to selectedBeatmap during y position computation --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 1f2bee52a5..b0b5c932ba 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -387,6 +387,8 @@ namespace osu.Game.Screens.Select float lastSetY = 0; + var selected = selectedBeatmap; + foreach (DrawableCarouselItem d in Items) { switch (d) @@ -398,7 +400,7 @@ namespace osu.Game.Screens.Select case DrawableCarouselBeatmap beatmap: beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); - if (beatmap.Item == selectedBeatmap) + if (beatmap.Item == selected) selectedY = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; // on first display we want to begin hidden under our group's header. From ed5b6cc16f884db3cf6d809fb922118b2ef6d512 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 12:08:11 +0900 Subject: [PATCH 114/239] Add back ctrl-enter autoplay shortcut --- osu.Game/Screens/Select/PlaySongSelect.cs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 565a7a0a01..4a0ee31fbb 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -117,18 +117,19 @@ namespace osu.Game.Screens.Select { if (player != null) return; - //if (state?.Keyboard.ControlPressed == true) - //{ - // var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); - // var autoType = auto.GetType(); + // Ctrl+Enter should start map with autoplay enabled. + if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) + { + var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); + var autoType = auto.GetType(); - // var mods = modSelect.SelectedMods.Value; - // if (mods.All(m => m.GetType() != autoType)) - // { - // modSelect.SelectedMods.Value = mods.Concat(new[] { auto }); - // removeAutoModOnResume = true; - // } - //} + var mods = modSelect.SelectedMods.Value; + if (mods.All(m => m.GetType() != autoType)) + { + modSelect.SelectedMods.Value = mods.Concat(new[] { auto }); + removeAutoModOnResume = true; + } + } Beatmap.Value.Track.Looping = false; Beatmap.Disabled = true; From 59d512762e1f5a780f1f86dcdba77e82da75cd4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 12:48:15 +0900 Subject: [PATCH 115/239] SongSelect tidying --- osu.Game/Screens/Select/SongSelect.cs | 131 ++++++++++++-------------- 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 0c3baf2c07..d71108a686 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -27,25 +27,11 @@ namespace osu.Game.Screens.Select { public abstract class SongSelect : OsuScreen { - private BeatmapManager beatmaps; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); - - private readonly BeatmapCarousel carousel; - private DialogOverlay dialogOverlay; - private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - + private static readonly Vector2 background_blur = new Vector2(20); private const float left_area_padding = 20; - private readonly BeatmapInfoWedge beatmapInfoWedge; - - protected Container LeftContent; - - private static readonly Vector2 background_blur = new Vector2(20); - private CancellationTokenSource initialAddSetsTask; - - private SampleChannel sampleChangeDifficulty; - private SampleChannel sampleChangeBeatmap; + public readonly FilterControl FilterControl; protected virtual bool ShowFooter => true; @@ -65,10 +51,21 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - public readonly FilterControl FilterControl; + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); + + protected Container LeftContent; + + private readonly BeatmapCarousel carousel; + private readonly BeatmapInfoWedge beatmapInfoWedge; + private DialogOverlay dialogOverlay; + private BeatmapManager beatmaps; + + private SampleChannel sampleChangeDifficulty; + private SampleChannel sampleChangeBeatmap; + + private CancellationTokenSource initialAddSetsTask; private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); protected SongSelect() @@ -172,7 +169,7 @@ namespace osu.Game.Screens.Select Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); - BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => Delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); } if (this.beatmaps == null) @@ -195,10 +192,14 @@ namespace osu.Game.Screens.Select carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); - Beatmap.ValueChanged += beatmap_ValueChanged; - Beatmap.DisabledChanged += disabled => carousel.AllowSelection = !disabled; - carousel.AllowSelection = !Beatmap.Disabled; + Beatmap.TriggerChange(); + + Beatmap.ValueChanged += b => + { + if (!IsCurrentScreen) return; + carousel.SelectBeatmap(b?.BeatmapInfo); + }; } public void Edit(BeatmapInfo beatmap) @@ -207,32 +208,6 @@ namespace osu.Game.Screens.Select Push(new Editor()); } - private void onBeatmapRestored(BeatmapInfo beatmap) - { - Schedule(() => - { - var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); - carousel.UpdateBeatmapSet(beatmapSet); - }); - } - - private void onBeatmapHidden(BeatmapInfo beatmap) - { - Schedule(() => - { - var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); - carousel.UpdateBeatmapSet(beatmapSet); - }); - } - - private void carouselBeatmapsLoaded() - { - if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) - carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false); - else - carousel.SelectNextRandom(); - } - public void Start(BeatmapInfo beatmap) { // if we have a pending filter operation, we want to run it now. @@ -251,6 +226,11 @@ namespace osu.Game.Screens.Select Start(); } + /// + /// Called when a selection is made. + /// + protected abstract void Start(); + private ScheduledDelegate selectionChangedDebounce; // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. @@ -307,18 +287,11 @@ namespace osu.Game.Screens.Select carousel.SelectNextRandom(); } - protected abstract void Start(); - - private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); - - private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); - protected override void OnEntering(Screen last) { base.OnEntering(last); Content.FadeInFromZero(250); - FilterControl.Activate(); } @@ -359,13 +332,6 @@ namespace osu.Game.Screens.Select logo.FadeOut(logo_transition / 2, Easing.Out); } - private void beatmap_ValueChanged(WorkingBeatmap beatmap) - { - if (!IsCurrentScreen) return; - - carousel.SelectBeatmap(beatmap?.BeatmapInfo); - } - protected override void OnResuming(Screen last) { if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) @@ -426,8 +392,7 @@ namespace osu.Game.Screens.Select /// The working beatmap. protected virtual void UpdateBeatmap(WorkingBeatmap beatmap) { - var backgroundModeBeatmap = Background as BackgroundScreenBeatmap; - if (backgroundModeBeatmap != null) + if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint); @@ -452,18 +417,44 @@ namespace osu.Game.Screens.Select } } - private void removeBeatmapSet(BeatmapSetInfo beatmapSet) + private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); + + private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => { - carousel.RemoveBeatmapSet(beatmapSet); + carousel.RemoveBeatmapSet(s); if (carousel.SelectedBeatmap == null) Beatmap.SetDefault(); + }); + + private void onBeatmapRestored(BeatmapInfo beatmap) + { + Schedule(() => + { + var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); + carousel.UpdateBeatmapSet(beatmapSet); + }); } - public void Delete(BeatmapSetInfo beatmap) + private void onBeatmapHidden(BeatmapInfo beatmap) { - if (beatmap == null) - return; + Schedule(() => + { + var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); + carousel.UpdateBeatmapSet(beatmapSet); + }); + } + private void carouselBeatmapsLoaded() + { + if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) + carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false); + else + carousel.SelectNextRandom(); + } + + private void delete(BeatmapSetInfo beatmap) + { + if (beatmap == null) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } @@ -481,7 +472,7 @@ namespace osu.Game.Screens.Select if (state.Keyboard.ShiftPressed) { if (!Beatmap.IsDefault) - Delete(Beatmap.Value.BeatmapSetInfo); + delete(Beatmap.Value.BeatmapSetInfo); return true; } From e6cac4a6753d505322b22ea4d92899067d333055 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 14:36:11 +0900 Subject: [PATCH 116/239] Allow tests to work with a variable number of beatmap sets loaded --- .../Visual/TestCaseBeatmapCarousel.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 6163daaf39..12e6e292f6 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -39,6 +39,8 @@ namespace osu.Game.Tests.Visual private readonly Stack selectedSets = new Stack(); + private const int set_count = 1000; + [BackgroundDependencyLoader] private void load() { @@ -47,16 +49,12 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, }); - AddStep("Load Beatmaps", () => - { - carousel.BeatmapSets = new[] - { - createTestBeatmapSet(1), - createTestBeatmapSet(2), - createTestBeatmapSet(3), - createTestBeatmapSet(4), - }; - }); + List beatmapSets = new List(); + + for (int i = 1; i <= set_count; i++) + beatmapSets.Add(createTestBeatmapSet(i)); + + AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); @@ -127,17 +125,17 @@ namespace osu.Game.Tests.Visual checkSelected(1, 2); advanceSelection(direction: -1, diff: false); - checkSelected(4, 1); + checkSelected(set_count, 1); advanceSelection(direction: -1, diff: true); - checkSelected(3, 3); + checkSelected(set_count - 1, 3); advanceSelection(diff: false); advanceSelection(diff: false); checkSelected(1, 1); advanceSelection(direction: -1, diff: true); - checkSelected(4, 3); + checkSelected(set_count, 3); } /// @@ -147,7 +145,7 @@ namespace osu.Game.Tests.Visual { // basic filtering - AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3" }, false)); + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); checkVisibleItemCount(diff: false, count: 1); checkVisibleItemCount(diff: true, count: 3); checkSelected(3, 1); @@ -157,7 +155,7 @@ namespace osu.Game.Tests.Visual AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); - checkVisibleItemCount(diff: false, count: 4); + checkVisibleItemCount(diff: false, count: set_count); checkVisibleItemCount(diff: true, count: 3); // test filtering some difficulties (and keeping current beatmap set selected). @@ -202,16 +200,18 @@ namespace osu.Game.Tests.Visual /// private void testAddRemove() { - AddStep("Add new set #5", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(5))); - AddStep("Add new set #6", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(6))); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1))); + AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2))); - checkVisibleItemCount(false, 6); + checkVisibleItemCount(false, set_count + 2); - AddStep("Remove set #5", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(5))); + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2))); - checkVisibleItemCount(false, 5); + checkVisibleItemCount(false, set_count + 1); - AddStep("Remove set #6", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(6))); + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1))); + + checkVisibleItemCount(false, set_count); } /// @@ -220,9 +220,9 @@ namespace osu.Game.Tests.Visual private void testSorting() { AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false)); - AddAssert("Check yyyyy is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "yyyyy"); - AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); - AddAssert("Check #4 is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith("#4")); + AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz"); + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } @@ -237,9 +237,9 @@ namespace osu.Game.Tests.Visual { OnlineBeatmapSetID = i, // Create random metadata, then we can check if sorting works based on these - Artist = "peppy", - Title = "test set #" + i, - AuthorString = string.Concat(Enumerable.Repeat((char)('z' - i), 5)) + Artist = $"peppy{i.ToString().PadLeft(6,'0')}", + Title = $"test set #{i}!", + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25,i - 1)), 5)) }, Beatmaps = new List(new[] { From 66b19b6c977ea26c215196bcd8c650245f88b4a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 Dec 2017 14:48:24 +0900 Subject: [PATCH 117/239] Expose selected objects from SelectionLayer --- .../Edit/Layers/Selection/DragSelector.cs | 14 ++++++++++++-- .../Edit/Layers/Selection/SelectionInfo.cs | 19 +++++++++++++++++++ .../Edit/Layers/Selection/SelectionLayer.cs | 11 ++++++++++- osu.Game/osu.Game.csproj | 1 + 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs index b83ed2e270..2201a661b7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using OpenTK.Graphics; +using osu.Framework.Configuration; namespace osu.Game.Rulesets.Edit.Layers.Selection { @@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public class DragSelector : CompositeDrawable { + public readonly Bindable Selection = new Bindable(); + /// /// The s that can be selected through a drag-selection. /// @@ -103,7 +106,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection } private readonly List capturedHitObjects = new List(); - public IReadOnlyList CapturedHitObjects => capturedHitObjects; /// /// Processes hitobjects to determine which ones are captured by the drag selection. @@ -128,7 +130,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public void FinishCapture() { - if (CapturedHitObjects.Count == 0) + if (capturedHitObjects.Count == 0) { Hide(); return; @@ -158,6 +160,12 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // Transform into markers to let the user modify the drag selection further. background.Delay(50).FadeOut(200); markers.FadeIn(200); + + Selection.Value = new SelectionInfo + { + Objects = capturedHitObjects, + SelectionQuad = Parent.ToScreenSpace(dragRectangle) + }; } private bool isActive = true; @@ -167,6 +175,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { isActive = false; this.FadeOut(400, Easing.OutQuint).Expire(); + + Selection.Value = null; } } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs new file mode 100644 index 0000000000..402bec1706 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using osu.Framework.Graphics.Primitives; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class SelectionInfo + { + /// + /// The objects that are captured by the selection. + /// + public IEnumerable Objects; + + /// + /// The screen space quad of the selection box surrounding . + /// + public Quad SelectionQuad; + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index a38be68461..af8eac847e 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -1,15 +1,21 @@ // 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.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit.Layers.Selection { public class SelectionLayer : CompositeDrawable { + public readonly Bindable Selection = new Bindable(); + private readonly Playfield playfield; public SelectionLayer(Playfield playfield) @@ -25,11 +31,14 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { // Hide the previous drag box - we won't be working with it any longer selector?.Hide(); + AddInternal(selector = new DragSelector(ToLocalSpace(state.Mouse.NativeState.Position)) { - CapturableObjects = playfield.HitObjects.Objects + CapturableObjects = playfield.HitObjects.Objects, }); + Selection.BindTo(selector.Selection); + return true; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 207dae1b54..63cf0ae0f5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -314,6 +314,7 @@ + From acfdd3278331069d508a1a01697bdb7c8fbac265 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 18:38:09 +0900 Subject: [PATCH 118/239] Move DrawableCarouselBeatmap initialisation to BDL oops --- .../Carousel/DrawableCarouselBeatmap.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1fca23a2ec..cd6cc55037 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -26,14 +26,20 @@ namespace osu.Game.Screens.Select.Carousel { private readonly BeatmapInfo beatmap; - private readonly Sprite background; + private Sprite background; public Action StartRequested; public Action EditRequested; public Action HideRequested; - private readonly Triangles triangles; - private readonly StarCounter starCounter; + private Triangles triangles; + private StarCounter starCounter; + + public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) + { + beatmap = panel.Beatmap; + Height *= 0.60f; + } [BackgroundDependencyLoader(true)] private void load(SongSelect songSelect, BeatmapManager manager) @@ -46,13 +52,6 @@ namespace osu.Game.Screens.Select.Carousel if (manager != null) HideRequested = manager.Hide; - } - - public DrawableCarouselBeatmap(CarouselBeatmap panel) - : base(panel) - { - beatmap = panel.Beatmap; - Height *= 0.60f; Children = new Drawable[] { From fd9d900ae00bfe9ac5c1a59332162671fd6b8e9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Dec 2017 18:40:03 +0900 Subject: [PATCH 119/239] Simplify StarCounter and SpriteIcon --- osu.Game/Graphics/SpriteIcon.cs | 29 ++++++++++--------- .../Graphics/UserInterface/StarCounter.cs | 23 +++++---------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs index e752b1d91a..326aa2fb79 100644 --- a/osu.Game/Graphics/SpriteIcon.cs +++ b/osu.Game/Graphics/SpriteIcon.cs @@ -15,14 +15,19 @@ namespace osu.Game.Graphics { public class SpriteIcon : CompositeDrawable { - private readonly Sprite spriteShadow; - private readonly Sprite spriteMain; + private Sprite spriteShadow; + private Sprite spriteMain; private Cached layout = new Cached(); - private readonly Container shadowVisibility; + private Container shadowVisibility; - public SpriteIcon() + private FontStore store; + + [BackgroundDependencyLoader] + private void load(FontStore store) { + this.store = store; + InternalChildren = new Drawable[] { shadowVisibility = new Container @@ -39,7 +44,7 @@ namespace osu.Game.Graphics Y = 2, Colour = new Color4(0f, 0f, 0f, 0.2f), }, - Alpha = 0, + Alpha = shadow ? 1 : 0, }, spriteMain = new Sprite { @@ -49,14 +54,7 @@ namespace osu.Game.Graphics FillMode = FillMode.Fit }, }; - } - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; updateTexture(); } @@ -105,12 +103,15 @@ namespace osu.Game.Graphics } } + private bool shadow; public bool Shadow { - get { return spriteShadow.IsPresent; } + get { return shadow; } set { - shadowVisibility.Alpha = value ? 1 : 0; + shadow = value; + if (shadowVisibility != null) + shadowVisibility.Alpha = value ? 1 : 0; } } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index e581d19d54..b4ca0d2e02 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using System; +using System.Linq; namespace osu.Game.Graphics.UserInterface { @@ -72,16 +73,9 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(star_spacing), + ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => new Star { Alpha = minStarAlpha }) } }; - - for (int i = 0; i < StarCount; i++) - { - stars.Add(new Star - { - Alpha = minStarAlpha, - }); - } } protected override void LoadComplete() @@ -147,15 +141,12 @@ namespace osu.Game.Graphics.UserInterface { Size = new Vector2(star_size); - Children = new[] + Child = Icon = new SpriteIcon { - Icon = new SpriteIcon - { - Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } + Size = new Vector2(star_size), + Icon = FontAwesome.fa_star, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; } } From a8a2c233a004adf866e155fd0b95c701109475c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 00:33:09 +0900 Subject: [PATCH 120/239] Add tests for (and fix) removal of last item in carousel --- .../Visual/TestCaseBeatmapCarousel.cs | 40 +++++++++++++++++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++-- .../Screens/Select/Carousel/CarouselGroup.cs | 4 +- .../Carousel/CarouselGroupEagerSelect.cs | 17 ++++++++ .../Screens/Select/Carousel/CarouselItem.cs | 9 +++++ 5 files changed, 69 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 12e6e292f6..8c8ec8a670 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -39,7 +39,9 @@ namespace osu.Game.Tests.Visual private readonly Stack selectedSets = new Stack(); - private const int set_count = 1000; + private BeatmapInfo currentSelection; + + private const int set_count = 5; [BackgroundDependencyLoader] private void load() @@ -54,6 +56,8 @@ namespace osu.Game.Tests.Visual for (int i = 1; i <= set_count; i++) beatmapSets.Add(createTestBeatmapSet(i)); + carousel.SelectionChanged = s => currentSelection = s; + AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); @@ -63,6 +67,8 @@ namespace osu.Game.Tests.Visual testRandom(); testAddRemove(); testSorting(); + + testRemoveAll(); } private void ensureRandomFetchSuccess() => @@ -145,6 +151,8 @@ namespace osu.Game.Tests.Visual { // basic filtering + setSelected(1, 1); + AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false)); checkVisibleItemCount(diff: false, count: 1); checkVisibleItemCount(diff: true, count: 3); @@ -163,8 +171,19 @@ namespace osu.Game.Tests.Visual setSelected(1, 2); AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false)); checkSelected(1, 1); + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); checkSelected(1, 1); + + AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false)); + + checkVisibleItemCount(false, 0); + checkVisibleItemCount(true, 0); + AddAssert("Selection is null", () => currentSelection == null); + + AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); + + AddAssert("Selection is non-null", () => currentSelection != null); } /// @@ -225,6 +244,21 @@ namespace osu.Game.Tests.Visual AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!")); } + private void testRemoveAll() + { + setSelected(2, 1); + + AddAssert("Selection is non-null", () => currentSelection != null); + + AddUntilStep(() => + { + carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); + return !carousel.BeatmapSets.Any(); + }, "Remove all"); + + AddAssert("Selection is null", () => currentSelection == null); + } + private BeatmapSetInfo createTestBeatmapSet(int i) { @@ -237,9 +271,9 @@ namespace osu.Game.Tests.Visual { OnlineBeatmapSetID = i, // Create random metadata, then we can check if sorting works based on these - Artist = $"peppy{i.ToString().PadLeft(6,'0')}", + Artist = $"peppy{i.ToString().PadLeft(6, '0')}", Title = $"test set #{i}!", - AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25,i - 1)), 5)) + AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, i - 1)), 5)) }, Beatmaps = new List(new[] { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index b0b5c932ba..fab5ce473a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Select { List newSets = null; - CarouselGroup newRoot = new CarouselGroup(); + CarouselGroup newRoot = new CarouselGroupEagerSelect(); Task.Run(() => { @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Select private readonly Stack randomSelectedBeatmaps = new Stack(); protected List Items = new List(); - private CarouselGroup root = new CarouselGroup(); + private CarouselGroup root = new CarouselGroupEagerSelect(); public BeatmapCarousel() { @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Select public void SelectNext(int direction = 1, bool skipDifficulties = true) { // todo: we may want to refactor and remove this as an optimisation in the future. - if (beatmapSets.All(g => !g.Visible)) + if (beatmapSets.All(g => g.Filtered)) { SelectionChanged?.Invoke(null); return; @@ -304,7 +304,7 @@ namespace osu.Game.Screens.Select root.Filter(criteria); updateItems(); - if (selectedBeatmap?.Filtered == false) + if (selectedBeatmap != null && !selectedBeatmap.Filtered) select(selectedBeatmap); else if (lastBeatmap != null && !lastSet.Filtered) { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 695a8e1bc4..151e73f45f 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Select.Carousel public override void AddChild(CarouselItem i) { - i.State.ValueChanged += v => itemStateChanged(i, v); + i.State.ValueChanged += v => ItemStateChanged(i, v); base.AddChild(i); } @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel if (items != null) InternalChildren = items; } - private void itemStateChanged(CarouselItem item, CarouselItemState value) + protected virtual void ItemStateChanged(CarouselItem item, CarouselItemState value) { // todo: check state of selected item. diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index b9312882a1..0a3c7b40ba 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -24,5 +24,22 @@ namespace osu.Game.Screens.Select.Carousel } }; } + + protected override void ItemStateChanged(CarouselItem item, CarouselItemState value) + { + base.ItemStateChanged(item, value); + + if (value == CarouselItemState.NotSelected) + { + if (Children.All(i => i.State != CarouselItemState.Selected)) + { + var first = Children.FirstOrDefault(i => !i.Filtered); + if (first != null) + { + first.State.Value = CarouselItemState.Selected; + } + } + } + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 7b0202cd87..fba7e4321c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Configuration; +using osu.Framework.Logging; namespace osu.Game.Screens.Select.Carousel { @@ -48,6 +49,8 @@ namespace osu.Game.Screens.Select.Carousel { if (InternalChildren == null) return; + Logger.Log($"State changed to {v}"); + switch (v) { case CarouselItemState.Hidden: @@ -56,6 +59,12 @@ namespace osu.Game.Screens.Select.Carousel break; } }; + + Filtered.ValueChanged += v => + { + if (v && State == CarouselItemState.Selected) + State.Value = CarouselItemState.NotSelected; + }; } private readonly Lazy drawableRepresentation; From 49ce42d90cbe3f0be99573bc773a58dda02b3155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 16:14:01 +0900 Subject: [PATCH 121/239] Add ToString() overrides on many classes to make debugging easier --- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 4 ++++ osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ osu.Game/Users/User.cs | 2 ++ 5 files changed, 12 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 8c8ec8a670..ccf9ecb7f9 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -228,9 +228,13 @@ namespace osu.Game.Tests.Visual checkVisibleItemCount(false, set_count + 1); + setSelected(set_count + 1, 1); + AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1))); checkVisibleItemCount(false, set_count); + + checkSelected(set_count, 1); } /// diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index f3a9694982..5856eab303 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -118,6 +118,8 @@ namespace osu.Game.Beatmaps [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } + public override string ToString() => $"{Metadata} [{Version}]"; + public bool Equals(BeatmapInfo other) { if (ID == 0 || other?.ID == 0) diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index a78ef25166..e0e0bb4eb2 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -57,6 +57,8 @@ namespace osu.Game.Beatmaps public string AudioFile { get; set; } public string BackgroundFile { get; set; } + public override string ToString() => $"{Artist} - {Title} ({Author})"; + public string[] SearchableTerms => new[] { Author?.Username, diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index c870c31a8b..a41beaab81 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -33,6 +33,8 @@ namespace osu.Game.Beatmaps public List Files { get; set; } + public override string ToString() => Metadata.ToString(); + public bool Protected { get; set; } } } diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index a6fa8637fd..5c0e5f1f95 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -136,5 +136,7 @@ namespace osu.Game.Users [JsonProperty(@"rankHistory")] public RankHistoryData RankHistory; + + public override string ToString() => Username; } } From 3c406662ed2330c26305da18616376816008234d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 16:14:37 +0900 Subject: [PATCH 122/239] Ensure correct selection after deletion of currently selected Also fixes a lot of bad interactions and simplifies further. --- .../Visual/TestCaseBeatmapCarousel.cs | 18 ++++--- osu.Game/Screens/Select/BeatmapCarousel.cs | 39 +++----------- .../Select/Carousel/CarouselBeatmap.cs | 2 + .../Select/Carousel/CarouselBeatmapSet.cs | 2 + .../Screens/Select/Carousel/CarouselGroup.cs | 8 +-- .../Carousel/CarouselGroupEagerSelect.cs | 54 ++++++++++++------- .../Screens/Select/Carousel/CarouselItem.cs | 24 +++++++-- 7 files changed, 79 insertions(+), 68 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index ccf9ecb7f9..973ea71cbf 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -74,9 +74,14 @@ namespace osu.Game.Tests.Visual private void ensureRandomFetchSuccess() => AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); - private void checkSelected(int set, int diff) => - AddAssert($"selected is set{set} diff{diff}", () => - carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()); + private void checkSelected(int set, int? diff = null) => + AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => + { + if (diff != null) + return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + + return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); + }); private void setSelected(int set, int diff) => AddStep($"select set{set} diff{diff}", () => @@ -95,7 +100,7 @@ namespace osu.Game.Tests.Visual } private void checkVisibleItemCount(bool diff, int count) => - AddAssert($"{count} {(diff ? "diff" : "set")} visible", () => + AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () => carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count); private void nextRandom() => @@ -138,8 +143,9 @@ namespace osu.Game.Tests.Visual advanceSelection(diff: false); advanceSelection(diff: false); - checkSelected(1, 1); + checkSelected(1, 2); + advanceSelection(direction: -1, diff: true); advanceSelection(direction: -1, diff: true); checkSelected(set_count, 3); } @@ -234,7 +240,7 @@ namespace osu.Game.Tests.Visual checkVisibleItemCount(false, set_count); - checkSelected(set_count, 1); + checkSelected(set_count, 2); } /// diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fab5ce473a..e37040b3de 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -74,6 +74,7 @@ namespace osu.Game.Screens.Select { root = newRoot; updateItems(); + BeatmapSetsChanged?.Invoke(); }); }); } @@ -85,11 +86,11 @@ namespace osu.Game.Screens.Select private void updateItems() { scrollableContent.Clear(false); - Items = root.Drawables.ToList(); - yPositionsCache.Invalidate(); - BeatmapSetsChanged?.Invoke(); + + if (root.Children == null || root.Children.All(c => c.Filtered)) + SelectionChanged?.Invoke(null); } private readonly List yPositions = new List(); @@ -126,9 +127,6 @@ namespace osu.Game.Screens.Select root.RemoveChild(existingSet); updateItems(); - - if (existingSet.State == CarouselItemState.Selected) - SelectNext(); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) @@ -188,13 +186,6 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - // todo: we may want to refactor and remove this as an optimisation in the future. - if (beatmapSets.All(g => g.Filtered)) - { - SelectionChanged?.Invoke(null); - return; - } - int originalIndex = Items.IndexOf(selectedBeatmap?.Drawables.First()); int currentIndex = originalIndex; @@ -214,13 +205,14 @@ namespace osu.Game.Screens.Select select(beatmap); return; case CarouselBeatmapSet set: + if (!skipDifficulties) continue; select(set); return; } } } - private IEnumerable getVisibleSets() => beatmapSets.Where(select => select.Visible); + private IEnumerable getVisibleSets() => beatmapSets.Where(select => !select.Filtered); public void SelectNextRandom() { @@ -298,28 +290,9 @@ namespace osu.Game.Screens.Select { FilterTask = null; - var lastSet = selectedBeatmapSet; - var lastBeatmap = selectedBeatmap; - root.Filter(criteria); updateItems(); - if (selectedBeatmap != null && !selectedBeatmap.Filtered) - select(selectedBeatmap); - else if (lastBeatmap != null && !lastSet.Filtered) - { - var searchable = lastSet.Beatmaps.AsEnumerable(); - - // search forwards first then backwards if nothing found. - var nextAvailable = - searchable.SkipWhile(b => b != lastBeatmap).FirstOrDefault(b => !b.Filtered) ?? - searchable.Reverse().SkipWhile(b => b != lastBeatmap).FirstOrDefault(b => !b.Filtered); - - select(nextAvailable); - } - else - SelectNext(); - ScrollToSelected(false); }; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index a0d91cf23b..cd3fefe8e6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -49,5 +49,7 @@ namespace osu.Game.Screens.Select.Carousel return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); } } + + public override string ToString() => Beatmap.ToString(); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 655ed2057d..885595fc51 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -52,5 +52,7 @@ namespace osu.Game.Screens.Select.Carousel base.Filter(criteria); Filtered.Value = InternalChildren.All(i => i.Filtered); } + + public override string ToString() => BeatmapSet.ToString(); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 151e73f45f..98aa7cd5c5 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Framework.Configuration; namespace osu.Game.Screens.Select.Carousel { @@ -13,13 +12,11 @@ namespace osu.Game.Screens.Select.Carousel { private readonly List items; - public readonly Bindable Selected = new Bindable(); - protected override DrawableCarouselItem CreateDrawableRepresentation() => null; public override void AddChild(CarouselItem i) { - i.State.ValueChanged += v => ItemStateChanged(i, v); + i.State.ValueChanged += v => ChildItemStateChanged(i, v); base.AddChild(i); } @@ -28,7 +25,7 @@ namespace osu.Game.Screens.Select.Carousel if (items != null) InternalChildren = items; } - protected virtual void ItemStateChanged(CarouselItem item, CarouselItemState value) + protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) { // todo: check state of selected item. @@ -42,7 +39,6 @@ namespace osu.Game.Screens.Select.Carousel } State.Value = CarouselItemState.Selected; - Selected.Value = item; } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 0a3c7b40ba..4fca0c69b6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -15,31 +15,47 @@ namespace osu.Game.Screens.Select.Carousel State.ValueChanged += v => { if (v == CarouselItemState.Selected) - { - foreach (var c in InternalChildren.Where(c => c.State.Value == CarouselItemState.Hidden)) - c.State.Value = CarouselItemState.NotSelected; - - if (InternalChildren.Any(c => c.Visible) && InternalChildren.All(c => c.State != CarouselItemState.Selected)) - InternalChildren.First(c => c.Visible).State.Value = CarouselItemState.Selected; - } + attemptSelection(); }; } - protected override void ItemStateChanged(CarouselItem item, CarouselItemState value) - { - base.ItemStateChanged(item, value); + private int lastSelectedIndex; - if (value == CarouselItemState.NotSelected) + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + attemptSelection(); + } + + protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) + { + base.ChildItemStateChanged(item, value); + + switch (value) { - if (Children.All(i => i.State != CarouselItemState.Selected)) - { - var first = Children.FirstOrDefault(i => !i.Filtered); - if (first != null) - { - first.State.Value = CarouselItemState.Selected; - } - } + case CarouselItemState.Selected: + lastSelectedIndex = InternalChildren.IndexOf(item); + break; + case CarouselItemState.NotSelected: + attemptSelection(); + break; } } + + private void attemptSelection() + { + // we only perform eager selection if we are a currently selected group. + if (State != CarouselItemState.Selected) return; + + // we only perform eager selection if none of our children are in a selected state already. + if (Children.Any(i => i.State == CarouselItemState.Selected)) return; + + CarouselItem nextToSelect = + Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered) ?? + Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered); + + if (nextToSelect != null) + nextToSelect.State.Value = CarouselItemState.Selected; + } } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index fba7e4321c..91fe600dc7 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -18,7 +18,10 @@ namespace osu.Game.Screens.Select.Carousel protected List InternalChildren { get; set; } - public bool Visible => State.Value != CarouselItemState.Hidden && !Filtered.Value; + /// + /// This item is not in a hidden state. + /// + public bool Visible => State.Value != CarouselItemState.Hidden && !Filtered; public IEnumerable Drawables { @@ -39,7 +42,14 @@ namespace osu.Game.Screens.Select.Carousel public virtual void AddChild(CarouselItem i) => (InternalChildren ?? (InternalChildren = new List())).Add(i); - public virtual void RemoveChild(CarouselItem i) => InternalChildren?.Remove(i); + public virtual void RemoveChild(CarouselItem i) + { + InternalChildren?.Remove(i); + + // it's important we do the deselection after removing, so any further actions based on + // State.ValueChanged make decisions post-removal. + if (i.State.Value == CarouselItemState.Selected) i.State.Value = CarouselItemState.NotSelected; + } protected CarouselItem() { @@ -47,9 +57,9 @@ namespace osu.Game.Screens.Select.Carousel State.ValueChanged += v => { - if (InternalChildren == null) return; + Logger.Log($"State of {this} changed to {v}"); - Logger.Log($"State changed to {v}"); + if (InternalChildren == null) return; switch (v) { @@ -57,6 +67,12 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Hidden); break; + case CarouselItemState.Selected: + InternalChildren.ForEach(c => + { + if (c.State == CarouselItemState.Hidden) c.State.Value = CarouselItemState.NotSelected; + }); + break; } }; From df7e795aa3af6b959d78cb862f5d9ed23d69241a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 16:27:39 +0900 Subject: [PATCH 123/239] Simplify and rename filter methods --- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++++++++++---------- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e37040b3de..40c3ae0fdc 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Select Task.Run(() => { value.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); - newRoot.Filter(criteria); + newRoot.Filter(activeCriteria); }).ContinueWith(t => { Schedule(() => @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Select root.AddChild(newSet); - Filter(debounce: false); + applyActiveCriteria(false); //check if we can/need to maintain our current selection. if (hadSelection) @@ -158,7 +158,7 @@ namespace osu.Game.Screens.Select updateItems(); } - public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) + public void SelectBeatmap(BeatmapInfo beatmap) { if (beatmap == null || beatmap.Hidden) { @@ -212,14 +212,12 @@ namespace osu.Game.Screens.Select } } - private IEnumerable getVisibleSets() => beatmapSets.Where(select => !select.Filtered); - public void SelectNextRandom() { if (!beatmapSets.Any()) return; - var visible = getVisibleSets().ToList(); + var visible = beatmapSets.Where(select => !select.Filtered).ToList(); if (!visible.Any()) return; @@ -269,28 +267,33 @@ namespace osu.Game.Screens.Select } } - private FilterCriteria criteria = new FilterCriteria(); + private FilterCriteria activeCriteria = new FilterCriteria(); protected ScheduledDelegate FilterTask; public bool AllowSelection = true; - public void FlushPendingFilters() + public void FlushPendingFilterOperations() { if (FilterTask?.Completed == false) - Filter(debounce: false); + applyActiveCriteria(false); } - public void Filter(FilterCriteria newCriteria = null, bool debounce = true) + public void Filter(FilterCriteria newCriteria, bool debounce = true) { if (newCriteria != null) - criteria = newCriteria; + activeCriteria = newCriteria; + applyActiveCriteria(debounce); + } + + private void applyActiveCriteria(bool debounce) + { Action perform = delegate { FilterTask = null; - root.Filter(criteria); + root.Filter(activeCriteria); updateItems(); ScrollToSelected(false); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d71108a686..d9a3f2faea 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Select { // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). - carousel.FlushPendingFilters(); + carousel.FlushPendingFilterOperations(); if (selectionChangedDebounce?.Completed == false) { @@ -447,7 +447,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { if (Beatmap.Value.BeatmapSetInfo?.DeletePending == false) - carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false); + carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); else carousel.SelectNextRandom(); } From 59dbca26129c49b17f3e166610a261c9980d07c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 16:44:42 +0900 Subject: [PATCH 124/239] Fix ScrollToSelected being called in too many cases --- osu.Game/Screens/Select/BeatmapCarousel.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 40c3ae0fdc..6e43f6fb01 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -149,7 +149,7 @@ namespace osu.Game.Screens.Select root.AddChild(newSet); - applyActiveCriteria(false); + applyActiveCriteria(false, false); //check if we can/need to maintain our current selection. if (hadSelection) @@ -276,7 +276,7 @@ namespace osu.Game.Screens.Select public void FlushPendingFilterOperations() { if (FilterTask?.Completed == false) - applyActiveCriteria(false); + applyActiveCriteria(false, false); } public void Filter(FilterCriteria newCriteria, bool debounce = true) @@ -284,10 +284,10 @@ namespace osu.Game.Screens.Select if (newCriteria != null) activeCriteria = newCriteria; - applyActiveCriteria(debounce); + applyActiveCriteria(debounce, true); } - private void applyActiveCriteria(bool debounce) + private void applyActiveCriteria(bool debounce, bool scroll) { Action perform = delegate { @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Select root.Filter(activeCriteria); updateItems(); - ScrollToSelected(false); + if (scroll) ScrollToSelected(false); }; FilterTask?.Cancel(); @@ -446,7 +446,7 @@ namespace osu.Game.Screens.Select computeYPositions(); // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(delegate(DrawableCarouselItem p) + scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) { float itemPosY = p.Position.Y; bool remove = itemPosY < Current - p.DrawHeight || itemPosY > Current + drawHeight || !p.IsPresent; From bd9056c709f72bd47c10b485a06fd529298df9d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 17:32:21 +0900 Subject: [PATCH 125/239] Better choose new selection when multiple items are removed including current --- .../Visual/TestCaseBeatmapCarousel.cs | 8 ++++- .../Carousel/CarouselGroupEagerSelect.cs | 32 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 973ea71cbf..34d2ceacb1 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -257,9 +257,15 @@ namespace osu.Game.Tests.Visual private void testRemoveAll() { setSelected(2, 1); - AddAssert("Selection is non-null", () => currentSelection != null); + AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet)); + checkSelected(2); + + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); + checkSelected(1); + AddUntilStep(() => { carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 4fca0c69b6..2d79742cc8 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.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; namespace osu.Game.Screens.Select.Carousel @@ -19,14 +20,33 @@ namespace osu.Game.Screens.Select.Carousel }; } + /// + /// We need to keep track of the index for cases where the selection is removed but we want to select a new item based on its old location. + /// private int lastSelectedIndex; + private CarouselItem lastSelected; + public override void Filter(FilterCriteria criteria) { base.Filter(criteria); attemptSelection(); } + public override void RemoveChild(CarouselItem i) + { + base.RemoveChild(i); + + if (i != lastSelected) + updateSelectedIndex(); + } + + public override void AddChild(CarouselItem i) + { + base.AddChild(i); + updateSelectedIndex(); + } + protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) { base.ChildItemStateChanged(item, value); @@ -34,7 +54,7 @@ namespace osu.Game.Screens.Select.Carousel switch (value) { case CarouselItemState.Selected: - lastSelectedIndex = InternalChildren.IndexOf(item); + updateSelected(item); break; case CarouselItemState.NotSelected: attemptSelection(); @@ -56,6 +76,16 @@ namespace osu.Game.Screens.Select.Carousel if (nextToSelect != null) nextToSelect.State.Value = CarouselItemState.Selected; + else + updateSelected(null); } + + private void updateSelected(CarouselItem newSelection) + { + lastSelected = newSelection; + updateSelectedIndex(); + } + + private void updateSelectedIndex() => lastSelectedIndex = Math.Max(0, InternalChildren.IndexOf(lastSelected)); } } From 5aee8f80bb4f56c9e71ad9dd1c83b601210f79f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 17:33:50 +0900 Subject: [PATCH 126/239] Fix incorrect test assumption (affected by random select above) --- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 34d2ceacb1..52db5b4883 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -240,7 +240,7 @@ namespace osu.Game.Tests.Visual checkVisibleItemCount(false, set_count); - checkSelected(set_count, 2); + checkSelected(set_count); } /// From 33f8c8419ab279e1b4331ad76a5d299ff462d130 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 21:25:54 +0900 Subject: [PATCH 127/239] Fix initial beatmap selection potentially being incorrect --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 +------ osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6e43f6fb01..2dba274831 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -160,13 +160,8 @@ namespace osu.Game.Screens.Select public void SelectBeatmap(BeatmapInfo beatmap) { - if (beatmap == null || beatmap.Hidden) - { - SelectNext(); + if (beatmap?.Hidden != false) return; - } - - if (beatmap == SelectedBeatmap) return; foreach (CarouselBeatmapSet group in beatmapSets) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d9a3f2faea..1fea4d0ccb 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -197,8 +197,8 @@ namespace osu.Game.Screens.Select Beatmap.ValueChanged += b => { - if (!IsCurrentScreen) return; - carousel.SelectBeatmap(b?.BeatmapInfo); + if (IsCurrentScreen) + carousel.SelectBeatmap(b?.BeatmapInfo); }; } From da0940ae0b0a7c4743da685a9388af15dbe4dbad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 23:55:55 +0900 Subject: [PATCH 128/239] Only apply criteria if there are items populated in the carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2dba274831..98902b3f6b 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -284,7 +284,9 @@ namespace osu.Game.Screens.Select private void applyActiveCriteria(bool debounce, bool scroll) { - Action perform = delegate + if (root.Children?.Any() != true) return; + + void perform() { FilterTask = null; @@ -292,7 +294,7 @@ namespace osu.Game.Screens.Select updateItems(); if (scroll) ScrollToSelected(false); - }; + } FilterTask?.Cancel(); FilterTask = null; From 29a8ade59f59838796af698c9cb8243d31e771ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Dec 2017 23:56:14 +0900 Subject: [PATCH 129/239] Rename "Hidden" to "Collapsed" --- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 +- osu.Game/Screens/Select/Carousel/CarouselItem.cs | 10 +++++----- .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index cd3fefe8e6..d7e7b1e265 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Select.Carousel public CarouselBeatmap(BeatmapInfo beatmap) { Beatmap = beatmap; - State.Value = CarouselItemState.Hidden; + State.Value = CarouselItemState.Collapsed; } protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 91fe600dc7..12125fe5e0 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select.Carousel /// /// This item is not in a hidden state. /// - public bool Visible => State.Value != CarouselItemState.Hidden && !Filtered; + public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered; public IEnumerable Drawables { @@ -63,14 +63,14 @@ namespace osu.Game.Screens.Select.Carousel switch (v) { - case CarouselItemState.Hidden: + case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: - InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Hidden); + InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; case CarouselItemState.Selected: InternalChildren.ForEach(c => { - if (c.State == CarouselItemState.Hidden) c.State.Value = CarouselItemState.NotSelected; + if (c.State == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; }); break; } @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Select.Carousel public enum CarouselItemState { - Hidden, + Collapsed, NotSelected, Selected, } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index cd6cc55037..92768cd88f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Select.Carousel protected override void ApplyState() { - if (Item.State.Value != CarouselItemState.Hidden && Alpha == 0) + if (Item.State.Value != CarouselItemState.Collapsed && Alpha == 0) starCounter.ReplayAnimation(); base.ApplyState(); From 5fbe7dfd8cde448eb658258100c469106f4f0049 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Sat, 16 Dec 2017 17:53:22 +0100 Subject: [PATCH 130/239] added Deselect All button to ModSelectOverlay --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 68 +++++++++++++--------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 9ff21dfdd4..82e8b58a30 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -17,20 +17,18 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Mods { public class ModSelectOverlay : WaveOverlayContainer { - private const int button_duration = 700; - private const int ranked_multiplier_duration = 700; private const float content_width = 0.8f; private Color4 lowMultiplierColour, highMultiplierColour; - private readonly OsuSpriteText rankedLabel; private readonly OsuSpriteText multiplierLabel; - private readonly FillFlowContainer rankedMultiplerContainer; + private readonly FillFlowContainer footerContainer; private readonly FillFlowContainer modSectionsContainer; @@ -66,14 +64,14 @@ namespace osu.Game.Overlays.Mods { base.PopOut(); - rankedMultiplerContainer.MoveToX(rankedMultiplerContainer.DrawSize.X, APPEAR_DURATION, Easing.InSine); - rankedMultiplerContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); foreach (ModSection section in modSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, APPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(APPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); } } @@ -81,14 +79,14 @@ namespace osu.Game.Overlays.Mods { base.PopIn(); - rankedMultiplerContainer.MoveToX(0, ranked_multiplier_duration, Easing.OutQuint); - rankedMultiplerContainer.FadeIn(ranked_multiplier_duration, Easing.OutQuint); + footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); foreach (ModSection section in modSectionsContainer.Children) { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), button_duration, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, button_duration, Easing.OutQuint); - section.ButtonsContainer.FadeIn(button_duration, Easing.OutQuint); + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); } } @@ -96,6 +94,8 @@ namespace osu.Game.Overlays.Mods { foreach (ModSection section in modSectionsContainer.Children) section.DeselectAll(); + + refreshSelectedMods(); } public void DeselectTypes(Type[] modTypes) @@ -130,8 +130,8 @@ namespace osu.Game.Overlays.Mods // 1.20x multiplierLabel.Text = $"{multiplier:N2}x"; - string rankedString = ranked ? "Ranked" : "Unranked"; - rankedLabel.Text = $@"{rankedString}, Score Multiplier: "; + if (!ranked) + multiplierLabel.Text += " (Unranked)"; if (multiplier > 1.0) multiplierLabel.FadeColour(highMultiplierColour, 200); @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.Mods }, new OsuSpriteText { - Text = @"Others are just for fun", + Text = @"Others are just for fun.", TextSize = 18, Shadow = true, }, @@ -289,7 +289,7 @@ namespace osu.Game.Overlays.Mods Colour = new Color4(172, 20, 116, 255), Alpha = 0.5f, }, - rankedMultiplerContainer = new FillFlowContainer + footerContainer = new FillFlowContainer { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, @@ -299,26 +299,42 @@ namespace osu.Game.Overlays.Mods Direction = FillDirection.Horizontal, Padding = new MarginPadding { - Top = 20, - Bottom = 20, + Vertical = 15 }, Children = new Drawable[] { - rankedLabel = new OsuSpriteText + new TriangleButton { - Text = @"Ranked, Score Multiplier: ", + Width = 180, + Text = "Deselect All", + Action = DeselectAll, + Margin = new MarginPadding + { + Right = 20 + } + }, + new OsuSpriteText + { + Text = @"Score Multiplier: ", TextSize = 30, Shadow = true, + Margin = new MarginPadding + { + Top = 5 + } }, multiplierLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", - Text = @"1.00x", TextSize = 30, Shadow = true, - }, - }, - }, + Margin = new MarginPadding + { + Top = 5 + } + } + } + } }, }, }, From e2710a309cc8a5ccc531fe0d327dbc8fe9be903c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Dec 2017 02:33:01 +0900 Subject: [PATCH 131/239] Fix panel animation and depth --- osu.Game/Screens/Select/BeatmapCarousel.cs | 67 +++++++++++++--------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 98902b3f6b..dac01f788c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -305,11 +305,9 @@ namespace osu.Game.Screens.Select perform(); } - public void ScrollToSelected(bool animated = true) - { - float selectedY = computeYPositions(animated); - ScrollTo(selectedY, animated); - } + private float? scrollTarget; + + public void ScrollToSelected(bool animated = true) => Schedule(() => { if (scrollTarget != null) ScrollTo(scrollTarget.Value, animated); }); private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { @@ -351,39 +349,56 @@ namespace osu.Game.Screens.Select /// Computes the target Y positions for every item in the carousel. /// /// The Y position of the currently selected item. - private float computeYPositions(bool animated = true) + private void computeYPositions(bool animated = true) { yPositions.Clear(); float currentY = DrawHeight / 2; - float selectedY = currentY; + DrawableCarouselBeatmapSet lastSet = null; - float lastSetY = 0; - - var selected = selectedBeatmap; + scrollTarget = null; foreach (DrawableCarouselItem d in Items) { - switch (d) + if (d.IsPresent) { - case DrawableCarouselBeatmapSet set: - set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - lastSetY = set.Position.Y; - break; - case DrawableCarouselBeatmap beatmap: - beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + switch (d) + { + case DrawableCarouselBeatmapSet set: + lastSet = set; - if (beatmap.Item == selected) - selectedY = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; + set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + set.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); + break; + case DrawableCarouselBeatmap beatmap: + if (beatmap.Item.State.Value == CarouselItemState.Selected) + scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; - // on first display we want to begin hidden under our group's header. - if (animated && !beatmap.IsPresent) - beatmap.MoveToY(lastSetY); - break; + void performMove(float y, float? startY = null) + { + if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); + beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + beatmap.MoveToY(y, animated ? 750 : 0, Easing.OutExpo); + } + + Debug.Assert(lastSet != null); + + float? setY = null; + if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. + setY = lastSet.Y + lastSet.DrawHeight + 5; + + if (d.IsLoaded) + performMove(currentY, setY); + else + { + float y = currentY; + d.OnLoadComplete = _ => performMove(y, setY); + } + break; + } } yPositions.Add(currentY); - d.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); if (d.Item.Visible) currentY += d.DrawHeight + 5; @@ -393,8 +408,6 @@ namespace osu.Game.Screens.Select scrollableContent.Height = currentY; yPositionsCache.Validate(); - - return selectedY; } private void select(CarouselItem item) @@ -477,7 +490,7 @@ namespace osu.Game.Screens.Select // Makes sure headers are always _below_ items, // and depth flows downward. - item.Depth = i + (item is DrawableCarouselBeatmapSet ? Items.Count : 0); + item.Depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0); switch (item.LoadState) { From d27047f94dc89cf7c33de049b6ca332ab85684a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Dec 2017 02:56:29 +0900 Subject: [PATCH 132/239] Remove logging --- osu.Game/Screens/Select/Carousel/CarouselItem.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 12125fe5e0..b7c5227414 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using osu.Framework.Configuration; -using osu.Framework.Logging; namespace osu.Game.Screens.Select.Carousel { @@ -57,8 +56,6 @@ namespace osu.Game.Screens.Select.Carousel State.ValueChanged += v => { - Logger.Log($"State of {this} changed to {v}"); - if (InternalChildren == null) return; switch (v) From c02ce16f47db9c7e60ad6d89e76818cdb4e6b5c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Dec 2017 03:04:57 +0900 Subject: [PATCH 133/239] Remove unnecessary capture --- osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index ad91706154..ee4c958c13 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -36,8 +36,8 @@ namespace osu.Game.Screens.Select.Carousel protected DrawableCarouselItem(CarouselItem item) { Item = item; - Item.Filtered.ValueChanged += v => Schedule(ApplyState); - Item.State.ValueChanged += v => Schedule(ApplyState); + Item.Filtered.ValueChanged += _ => Schedule(ApplyState); + Item.State.ValueChanged += _ => Schedule(ApplyState); Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; From 5d7413f19c7a9f888d68fec6acc9086b43d967e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Dec 2017 04:30:56 +0900 Subject: [PATCH 134/239] Improve performance with large numbers of panels visible --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 ++-- .../Select/Carousel/CarouselGroupEagerSelect.cs | 12 ++++++++++++ osu.Game/Screens/Select/Carousel/CarouselItem.cs | 3 ++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index dac01f788c..459d364f73 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -483,11 +483,11 @@ namespace osu.Game.Screens.Select { DrawableCarouselItem item = Items[i]; + if (!item.Item.Visible) continue; + // Only add if we're not already part of the content. if (!scrollableContent.Contains(item)) { - if (!item.Item.Visible) continue; - // Makes sure headers are always _below_ items, // and depth flows downward. item.Depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 2d79742cc8..65282542e6 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -27,9 +27,19 @@ namespace osu.Game.Screens.Select.Carousel private CarouselItem lastSelected; + /// + /// To avoid overhead during filter operations, we don't attempt any selections until after all + /// children have been filtered. This bool will be true during the base + /// operation. + /// + private bool filteringChildren; + public override void Filter(FilterCriteria criteria) { + filteringChildren = true; base.Filter(criteria); + filteringChildren = false; + attemptSelection(); } @@ -64,6 +74,8 @@ namespace osu.Game.Screens.Select.Carousel private void attemptSelection() { + if (filteringChildren) return; + // we only perform eager selection if we are a currently selected group. if (State != CarouselItemState.Selected) return; diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index b7c5227414..c289481d16 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -33,7 +33,8 @@ namespace osu.Game.Screens.Select.Carousel if (InternalChildren != null) foreach (var c in InternalChildren) - items.AddRange(c.Drawables); + // if (!c.Filtered) <- potential optimisation at the cost of no fade out animations. + items.AddRange(c.Drawables); return items; } From 54cc6fadf97be79ff4cd4b3a8bed752853f2068a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Dec 2017 05:53:13 +0900 Subject: [PATCH 135/239] Greatly improve performance when many hidden panels are on-screen --- osu.Game/Screens/Select/BeatmapCarousel.cs | 75 ++++++++++--------- .../Carousel/CarouselGroupEagerSelect.cs | 1 + .../Screens/Select/Carousel/CarouselItem.cs | 7 +- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 459d364f73..63c8aee3f9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select /// public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; - private CarouselBeatmapSet selectedBeatmapSet => beatmapSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); + private CarouselBeatmapSet selectedBeatmapSet; /// /// Raised when the is changed. @@ -73,26 +73,14 @@ namespace osu.Game.Screens.Select Schedule(() => { root = newRoot; - updateItems(); + scrollableContent.Clear(false); + yPositionsCache.Invalidate(); BeatmapSetsChanged?.Invoke(); }); }); } } - /// - /// Call after altering in any way. - /// - private void updateItems() - { - scrollableContent.Clear(false); - Items = root.Drawables.ToList(); - yPositionsCache.Invalidate(); - - if (root.Children == null || root.Children.All(c => c.Filtered)) - SelectionChanged?.Invoke(null); - } - private readonly List yPositions = new List(); private Cached yPositionsCache = new Cached(); @@ -126,7 +114,7 @@ namespace osu.Game.Screens.Select return; root.RemoveChild(existingSet); - updateItems(); + yPositionsCache.Invalidate(); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) @@ -142,7 +130,7 @@ namespace osu.Game.Screens.Select if (newSet == null) { - updateItems(); + yPositionsCache.Invalidate(); SelectNext(); return; } @@ -155,7 +143,7 @@ namespace osu.Game.Screens.Select if (hadSelection) select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); - updateItems(); + yPositionsCache.Invalidate(); } public void SelectBeatmap(BeatmapInfo beatmap) @@ -200,8 +188,10 @@ namespace osu.Game.Screens.Select select(beatmap); return; case CarouselBeatmapSet set: - if (!skipDifficulties) continue; - select(set); + if (skipDifficulties) + select(set); + else + select(direction > 0 ? set.Beatmaps.First() : set.Beatmaps.Last()); return; } } @@ -209,10 +199,7 @@ namespace osu.Game.Screens.Select public void SelectNextRandom() { - if (!beatmapSets.Any()) - return; - - var visible = beatmapSets.Where(select => !select.Filtered).ToList(); + var visible = beatmapSets.Where(s => !s.Filtered).ToList(); if (!visible.Any()) return; @@ -291,7 +278,7 @@ namespace osu.Game.Screens.Select FilterTask = null; root.Filter(activeCriteria); - updateItems(); + yPositionsCache.Invalidate(); if (scroll) ScrollToSelected(false); } @@ -329,6 +316,7 @@ namespace osu.Game.Screens.Select { if (v == CarouselItemState.Selected) { + selectedBeatmapSet = set; SelectionChanged?.Invoke(c.Beatmap); yPositionsCache.Invalidate(); Schedule(() => ScrollToSelected()); @@ -410,6 +398,12 @@ namespace osu.Game.Screens.Select yPositionsCache.Validate(); } + private void noSelection() + { + if (root.Children == null || root.Children.All(c => c.Filtered)) + SelectionChanged?.Invoke(null); + } + private void select(CarouselItem item) { if (item == null) return; @@ -450,11 +444,18 @@ namespace osu.Game.Screens.Select { base.Update(); - float drawHeight = DrawHeight; - + // todo: scheduled scrolls... if (!yPositionsCache.IsValid) + { + Items = root.Drawables.ToList(); computeYPositions(); + if (selectedBeatmapSet != null && beatmapSets.All(s => s.Filtered)) + SelectionChanged?.Invoke(null); + } + + float drawHeight = DrawHeight; + // Remove all items that should no longer be on-screen scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) { @@ -469,21 +470,21 @@ namespace osu.Game.Screens.Select int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); if (firstIndex < 0) firstIndex = ~firstIndex; int lastIndex = yPositions.BinarySearch(Current + drawHeight); - if (lastIndex < 0) - { - lastIndex = ~lastIndex; + if (lastIndex < 0) lastIndex = ~lastIndex; - // Add the first item of the last visible beatmap group to preload its data. - if (lastIndex != 0 && Items[lastIndex - 1] is DrawableCarouselBeatmapSet) - lastIndex++; - } + int notVisibleCount = 0; // Add those items within the previously found index range that should be displayed. for (int i = firstIndex; i < lastIndex; ++i) { DrawableCarouselItem item = Items[i]; - if (!item.Item.Visible) continue; + if (!item.Item.Visible) + { + if (!item.IsPresent) + notVisibleCount++; + continue; + } // Only add if we're not already part of the content. if (!scrollableContent.Contains(item)) @@ -506,6 +507,10 @@ namespace osu.Game.Screens.Select } } + // this is not actually useful right now, but once we have groups may well be. + if (notVisibleCount > 50) + yPositionsCache.Invalidate(); + // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). float halfHeight = drawHeight / 2; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 65282542e6..1e6c809f2c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Select.Carousel updateSelected(item); break; case CarouselItemState.NotSelected: + case CarouselItemState.Collapsed: attemptSelection(); break; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index c289481d16..586e959a74 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -29,12 +29,11 @@ namespace osu.Game.Screens.Select.Carousel List items = new List(); var self = drawableRepresentation.Value; - if (self != null) items.Add(self); + if (self?.IsPresent == true) items.Add(self); if (InternalChildren != null) foreach (var c in InternalChildren) - // if (!c.Filtered) <- potential optimisation at the cost of no fade out animations. - items.AddRange(c.Drawables); + items.AddRange(c.Drawables); return items; } @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Select.Carousel // it's important we do the deselection after removing, so any further actions based on // State.ValueChanged make decisions post-removal. - if (i.State.Value == CarouselItemState.Selected) i.State.Value = CarouselItemState.NotSelected; + i.State.Value = CarouselItemState.Collapsed; } protected CarouselItem() From 19643ba5e675fe98e06e5328e409bb2a41525e4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 02:23:03 +0900 Subject: [PATCH 136/239] Resolve scroll animation/position issues --- osu.Game/Screens/Select/BeatmapCarousel.cs | 62 +++++++++++++--------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 63c8aee3f9..1991fb608c 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -74,7 +74,8 @@ namespace osu.Game.Screens.Select { root = newRoot; scrollableContent.Clear(false); - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); BeatmapSetsChanged?.Invoke(); }); }); @@ -82,7 +83,8 @@ namespace osu.Game.Screens.Select } private readonly List yPositions = new List(); - private Cached yPositionsCache = new Cached(); + private Cached itemsCache = new Cached(); + private Cached scrollPositionCache = new Cached(); private readonly Container scrollableContent; @@ -114,7 +116,7 @@ namespace osu.Game.Screens.Select return; root.RemoveChild(existingSet); - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) @@ -130,7 +132,7 @@ namespace osu.Game.Screens.Select if (newSet == null) { - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); SelectNext(); return; } @@ -143,7 +145,7 @@ namespace osu.Game.Screens.Select if (hadSelection) select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); } public void SelectBeatmap(BeatmapInfo beatmap) @@ -278,9 +280,16 @@ namespace osu.Game.Screens.Select FilterTask = null; root.Filter(activeCriteria); - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); - if (scroll) ScrollToSelected(false); + if (scroll) + { + Schedule(() => + { + updateItems(false); + updateScrollPosition(false); + }); + } } FilterTask?.Cancel(); @@ -294,7 +303,7 @@ namespace osu.Game.Screens.Select private float? scrollTarget; - public void ScrollToSelected(bool animated = true) => Schedule(() => { if (scrollTarget != null) ScrollTo(scrollTarget.Value, animated); }); + public void ScrollToSelected() => scrollPositionCache.Invalidate(); private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) { @@ -318,8 +327,9 @@ namespace osu.Game.Screens.Select { selectedBeatmapSet = set; SelectionChanged?.Invoke(c.Beatmap); - yPositionsCache.Invalidate(); - Schedule(() => ScrollToSelected()); + + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); } }; } @@ -337,8 +347,10 @@ namespace osu.Game.Screens.Select /// Computes the target Y positions for every item in the carousel. /// /// The Y position of the currently selected item. - private void computeYPositions(bool animated = true) + private void updateItems(bool animated = true) { + Items = root.Drawables.ToList(); + yPositions.Clear(); float currentY = DrawHeight / 2; @@ -395,13 +407,19 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - yPositionsCache.Validate(); + if (selectedBeatmapSet?.Filtered.Value == true) + { + selectedBeatmapSet = null; + SelectionChanged?.Invoke(null); + } + + itemsCache.Validate(); } - private void noSelection() + private void updateScrollPosition(bool animated = true) { - if (root.Children == null || root.Children.All(c => c.Filtered)) - SelectionChanged?.Invoke(null); + if (scrollTarget != null) ScrollTo(scrollTarget.Value, animated); + scrollPositionCache.Validate(); } private void select(CarouselItem item) @@ -444,15 +462,11 @@ namespace osu.Game.Screens.Select { base.Update(); - // todo: scheduled scrolls... - if (!yPositionsCache.IsValid) - { - Items = root.Drawables.ToList(); - computeYPositions(); + if (!itemsCache.IsValid) + updateItems(); - if (selectedBeatmapSet != null && beatmapSets.All(s => s.Filtered)) - SelectionChanged?.Invoke(null); - } + if (!scrollPositionCache.IsValid) + updateScrollPosition(); float drawHeight = DrawHeight; @@ -509,7 +523,7 @@ namespace osu.Game.Screens.Select // this is not actually useful right now, but once we have groups may well be. if (notVisibleCount > 50) - yPositionsCache.Invalidate(); + itemsCache.Invalidate(); // Update externally controlled state of currently visible items // (e.g. x-offset and opacity). From 942054a30ff7fc9120a9192c2c70f2b34592108b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 02:26:40 +0900 Subject: [PATCH 137/239] Re-fix null selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 1991fb608c..4ec62877bf 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -407,7 +407,7 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - if (selectedBeatmapSet?.Filtered.Value == true) + if (selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected) { selectedBeatmapSet = null; SelectionChanged?.Invoke(null); From 30a15729ec351e199b37654a5743f90af6cfd36f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 02:46:53 +0900 Subject: [PATCH 138/239] Fix event handling from outside carousel being scheduled at the wrong level Was causing BeatmapSet's Set to run *after* newer events were received. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 62 ++++++++++++---------- osu.Game/Screens/Select/SongSelect.cs | 30 ++--------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 4ec62877bf..c18f917645 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -110,42 +110,48 @@ namespace osu.Game.Screens.Select public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { - var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); + Schedule(() => + { + var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - if (existingSet == null) - return; + if (existingSet == null) + return; - root.RemoveChild(existingSet); - itemsCache.Invalidate(); + root.RemoveChild(existingSet); + itemsCache.Invalidate(); + }); } public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) { - CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - - bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; - - var newSet = createCarouselSet(beatmapSet); - - if (existingSet != null) - root.RemoveChild(existingSet); - - if (newSet == null) + Schedule(() => { + CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); + + bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; + + var newSet = createCarouselSet(beatmapSet); + + if (existingSet != null) + root.RemoveChild(existingSet); + + if (newSet == null) + { + itemsCache.Invalidate(); + SelectNext(); + return; + } + + root.AddChild(newSet); + + applyActiveCriteria(false, false); + + //check if we can/need to maintain our current selection. + if (hadSelection) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); + itemsCache.Invalidate(); - SelectNext(); - return; - } - - root.AddChild(newSet); - - applyActiveCriteria(false, false); - - //check if we can/need to maintain our current selection. - if (hadSelection) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); - - itemsCache.Invalidate(); + }); } public void SelectBeatmap(BeatmapInfo beatmap) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 1fea4d0ccb..b5fba7d88d 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -417,32 +417,10 @@ namespace osu.Game.Screens.Select } } - private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); - - private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => - { - carousel.RemoveBeatmapSet(s); - if (carousel.SelectedBeatmap == null) - Beatmap.SetDefault(); - }); - - private void onBeatmapRestored(BeatmapInfo beatmap) - { - Schedule(() => - { - var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); - carousel.UpdateBeatmapSet(beatmapSet); - }); - } - - private void onBeatmapHidden(BeatmapInfo beatmap) - { - Schedule(() => - { - var beatmapSet = beatmaps.QueryBeatmapSet(s => s.ID == beatmap.BeatmapSetInfoID); - carousel.UpdateBeatmapSet(beatmapSet); - }); - } + private void onBeatmapSetAdded(BeatmapSetInfo s) => carousel.UpdateBeatmapSet(s); + private void onBeatmapSetRemoved(BeatmapSetInfo s) => carousel.RemoveBeatmapSet(s); + private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); + private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void carouselBeatmapsLoaded() { From 3759c39f00444da97d2848536d0a781b2822274d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 02:51:09 +0900 Subject: [PATCH 139/239] Update test case to handle scheduled removal --- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 52db5b4883..5758e893a6 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -268,8 +268,10 @@ namespace osu.Game.Tests.Visual AddUntilStep(() => { + if (!carousel.BeatmapSets.Any()) return true; + carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); - return !carousel.BeatmapSets.Any(); + return false; }, "Remove all"); AddAssert("Selection is null", () => currentSelection == null); From 482941b33321f3fdf360c692e7a98e60790d4697 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 06:59:32 +0900 Subject: [PATCH 140/239] Preload drawables to force asynchronous construction --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c18f917645..7d5a0c859a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -68,6 +68,9 @@ namespace osu.Game.Screens.Select { value.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); newRoot.Filter(activeCriteria); + + // preload drawables as the ctor overhead is quite high currently. + var drawables = newRoot.Drawables; }).ContinueWith(t => { Schedule(() => From c10288541c796667d5da5b1a3e8481a0ec91441d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 07:58:34 +0900 Subject: [PATCH 141/239] Avoid redundant IndexOf calls --- osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 1e6c809f2c..5701760221 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Select.Carousel public override void AddChild(CarouselItem i) { base.AddChild(i); - updateSelectedIndex(); + attemptSelection(); } protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value) @@ -99,6 +99,6 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } - private void updateSelectedIndex() => lastSelectedIndex = Math.Max(0, InternalChildren.IndexOf(lastSelected)); + private void updateSelectedIndex() => lastSelectedIndex = lastSelected == null ? 0 : Math.Max(0, InternalChildren.IndexOf(lastSelected)); } } From dfd7787b1586a65b6b668db6bf9ed69c3cbf9142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 07:58:48 +0900 Subject: [PATCH 142/239] Move more overhead from ctor to BDL --- .../Select/Carousel/DrawableCarouselItem.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index ee4c958c13..cb354b3602 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -26,25 +26,28 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselItem Item; - private readonly Container nestedContainer; - private readonly Container borderContainer; + private Container nestedContainer; + private Container borderContainer; - private readonly Box hoverLayer; + private Box hoverLayer; protected override Container Content => nestedContainer; protected DrawableCarouselItem(CarouselItem item) { Item = item; - Item.Filtered.ValueChanged += _ => Schedule(ApplyState); - Item.State.ValueChanged += _ => Schedule(ApplyState); Height = MAX_HEIGHT; RelativeSizeAxes = Axes.X; - Alpha = 0; + } - AddInternal(borderContainer = new Container + private SampleChannel sampleHover; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colours) + { + InternalChild = borderContainer = new Container { RelativeSizeAxes = Axes.Both, Masking = true, @@ -63,14 +66,8 @@ namespace osu.Game.Screens.Select.Carousel Blending = BlendingMode.Additive, }, } - }); - } + }; - private SampleChannel sampleHover; - - [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuColour colours) - { sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -94,7 +91,10 @@ namespace osu.Game.Screens.Select.Carousel protected override void LoadComplete() { base.LoadComplete(); + ApplyState(); + Item.Filtered.ValueChanged += _ => Schedule(ApplyState); + Item.State.ValueChanged += _ => Schedule(ApplyState); } protected virtual void ApplyState() From 954bc77a71049ba8062e3f7cdc7e332898afdd3a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 08:05:57 +0900 Subject: [PATCH 143/239] Indicate unused variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 7d5a0c859a..21e12c48b9 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -70,18 +70,15 @@ namespace osu.Game.Screens.Select newRoot.Filter(activeCriteria); // preload drawables as the ctor overhead is quite high currently. - var drawables = newRoot.Drawables; - }).ContinueWith(t => + var _ = newRoot.Drawables; + }).ContinueWith(_ => Schedule(() => { - Schedule(() => - { - root = newRoot; - scrollableContent.Clear(false); - itemsCache.Invalidate(); - scrollPositionCache.Invalidate(); - BeatmapSetsChanged?.Invoke(); - }); - }); + root = newRoot; + scrollableContent.Clear(false); + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); + BeatmapSetsChanged?.Invoke(); + })); } } From d6ba53acb3697d8f1d63949c9d5085373d61c62f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 09:47:53 +0900 Subject: [PATCH 144/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index fc6de01ad6..5da6990a8e 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit fc6de01ad6045544991bf278316c9eed8ea01ef1 +Subproject commit 5da6990a8e68dea852495950996e1362a293dbd5 From b21c22085d899a7a2574639e5954b77b51c3147b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:13:16 +0900 Subject: [PATCH 145/239] Make more things private --- .../Carousel/DrawableCarouselBeatmap.cs | 20 +++++++++---------- .../Carousel/DrawableCarouselBeatmapSet.cs | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 92768cd88f..6c0cc341fd 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -28,9 +28,9 @@ namespace osu.Game.Screens.Select.Carousel private Sprite background; - public Action StartRequested; - public Action EditRequested; - public Action HideRequested; + private Action startRequested; + private Action editRequested; + private Action hideRequested; private Triangles triangles; private StarCounter starCounter; @@ -46,12 +46,12 @@ namespace osu.Game.Screens.Select.Carousel { if (songSelect != null) { - StartRequested = songSelect.Start; - EditRequested = songSelect.Edit; + startRequested = songSelect.Start; + editRequested = songSelect.Edit; } if (manager != null) - HideRequested = manager.Hide; + hideRequested = manager.Hide; Children = new Drawable[] { @@ -153,7 +153,7 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnClick(InputState state) { if (Item.State == CarouselItemState.Selected) - StartRequested?.Invoke(beatmap); + startRequested?.Invoke(beatmap); return base.OnClick(state); } @@ -168,9 +168,9 @@ namespace osu.Game.Screens.Select.Carousel public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(beatmap)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(beatmap)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(beatmap)), + new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), }; } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index b5636ae9ed..8abb93950f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -24,8 +24,8 @@ namespace osu.Game.Screens.Select.Carousel { public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu { - public Action DeleteRequested; - public Action RestoreHiddenRequested; + private Action deleteRequested; + private Action restoreHiddenRequested; private readonly BeatmapSetInfo beatmapSet; @@ -43,8 +43,8 @@ namespace osu.Game.Screens.Select.Carousel if (localisation == null) throw new ArgumentNullException(nameof(localisation)); - RestoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); - DeleteRequested = manager.Delete; + restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); + deleteRequested = manager.Delete; Children = new Drawable[] { @@ -97,9 +97,9 @@ namespace osu.Game.Screens.Select.Carousel items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); if (beatmapSet.Beatmaps.Any(b => b.Hidden)) - items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmapSet))); + items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested?.Invoke(beatmapSet))); - items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmapSet))); + items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => deleteRequested?.Invoke(beatmapSet))); return items.ToArray(); } From 5bfb6d1f58ef072f39bc65b0c4586095fe74e857 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:13:51 +0900 Subject: [PATCH 146/239] Remove unused variable --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 21e12c48b9..48bfe01d51 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -60,8 +60,6 @@ namespace osu.Game.Screens.Select get { return beatmapSets.Select(g => g.BeatmapSet); } set { - List newSets = null; - CarouselGroup newRoot = new CarouselGroupEagerSelect(); Task.Run(() => From 4e46565f6e24082c7739f71aba1033584d25e4b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:25:02 +0900 Subject: [PATCH 147/239] Remove todo --- osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 98aa7cd5c5..b8d7ddaf3e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -27,8 +27,6 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) { - // todo: check state of selected item. - // ensure we are the only item selected if (value == CarouselItemState.Selected) { From 4c1f00567bd25e3506bb5dba1c4031a24e5c0845 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:31:38 +0900 Subject: [PATCH 148/239] Fix incorrect flush logic when starting play from non-selected difficulty using context menu --- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index b5fba7d88d..4d5101447a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -214,6 +214,8 @@ namespace osu.Game.Screens.Select // it could change selection (ie. if the ruleset has been changed). carousel.FlushPendingFilterOperations(); + carousel.SelectBeatmap(beatmap); + if (selectionChangedDebounce?.Completed == false) { selectionChangedDebounce.RunTask(); @@ -221,8 +223,6 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - carousel.SelectBeatmap(beatmap); - Start(); } From 7173829896539cba3843cefabed90ffabfc2f82a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:43:10 +0900 Subject: [PATCH 149/239] Add filter checks to difficulty selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 48bfe01d51..e5f3d93a7d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -197,7 +197,7 @@ namespace osu.Game.Screens.Select if (skipDifficulties) select(set); else - select(direction > 0 ? set.Beatmaps.First() : set.Beatmaps.Last()); + select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered) : set.Beatmaps.Last(b => !b.Filtered)); return; } } From b2cd32eb950d208183924c0f5ace1b4e07a80d78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 11:57:13 +0900 Subject: [PATCH 150/239] Move children to CarouselGroup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 4 +- .../Screens/Select/Carousel/CarouselGroup.cs | 52 ++++++++++++++++++- .../Screens/Select/Carousel/CarouselItem.cs | 44 +--------------- 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e5f3d93a7d..c090ea6f0a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select public override bool HandleInput => AllowSelection; - private IEnumerable beatmapSets => root.Children?.OfType() ?? new CarouselBeatmapSet[] { }; + private IEnumerable beatmapSets => root.Children.OfType(); public IEnumerable BeatmapSets { @@ -277,7 +277,7 @@ namespace osu.Game.Screens.Select private void applyActiveCriteria(bool debounce, bool scroll) { - if (root.Children?.Any() != true) return; + if (root.Children.Any() != true) return; void perform() { diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index b8d7ddaf3e..a54eeb562e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -14,15 +14,63 @@ namespace osu.Game.Screens.Select.Carousel protected override DrawableCarouselItem CreateDrawableRepresentation() => null; - public override void AddChild(CarouselItem i) + public IReadOnlyList Children => InternalChildren; + + protected List InternalChildren = new List(); + + public override List Drawables + { + get + { + var drawables = base.Drawables; + foreach (var c in InternalChildren) + drawables.AddRange(c.Drawables); + return drawables; + } + } + + public virtual void RemoveChild(CarouselItem i) + { + InternalChildren.Remove(i); + + // it's important we do the deselection after removing, so any further actions based on + // State.ValueChanged make decisions post-removal. + i.State.Value = CarouselItemState.Collapsed; + } + + public virtual void AddChild(CarouselItem i) { i.State.ValueChanged += v => ChildItemStateChanged(i, v); - base.AddChild(i); + InternalChildren.Add(i); } public CarouselGroup(List items = null) { if (items != null) InternalChildren = items; + + State.ValueChanged += v => + { + switch (v) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + break; + case CarouselItemState.Selected: + InternalChildren.ForEach(c => + { + if (c.State == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; + }); + break; + } + }; + } + + public override void Filter(FilterCriteria criteria) + { + base.Filter(criteria); + InternalChildren.Sort((x, y) => x.CompareTo(criteria, y)); + InternalChildren.ForEach(c => c.Filter(criteria)); } protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value) diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index 586e959a74..7d76aee253 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -13,66 +13,28 @@ namespace osu.Game.Screens.Select.Carousel public readonly Bindable State = new Bindable(CarouselItemState.NotSelected); - public IReadOnlyList Children => InternalChildren; - - protected List InternalChildren { get; set; } - /// /// This item is not in a hidden state. /// public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered; - public IEnumerable Drawables + public virtual List Drawables { get { - List items = new List(); + var items = new List(); var self = drawableRepresentation.Value; if (self?.IsPresent == true) items.Add(self); - if (InternalChildren != null) - foreach (var c in InternalChildren) - items.AddRange(c.Drawables); - return items; } } - public virtual void AddChild(CarouselItem i) => (InternalChildren ?? (InternalChildren = new List())).Add(i); - - public virtual void RemoveChild(CarouselItem i) - { - InternalChildren?.Remove(i); - - // it's important we do the deselection after removing, so any further actions based on - // State.ValueChanged make decisions post-removal. - i.State.Value = CarouselItemState.Collapsed; - } - protected CarouselItem() { drawableRepresentation = new Lazy(CreateDrawableRepresentation); - State.ValueChanged += v => - { - if (InternalChildren == null) return; - - switch (v) - { - case CarouselItemState.Collapsed: - case CarouselItemState.NotSelected: - InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); - break; - case CarouselItemState.Selected: - InternalChildren.ForEach(c => - { - if (c.State == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; - }); - break; - } - }; - Filtered.ValueChanged += v => { if (v && State == CarouselItemState.Selected) @@ -86,8 +48,6 @@ namespace osu.Game.Screens.Select.Carousel public virtual void Filter(FilterCriteria criteria) { - InternalChildren?.Sort((x, y) => x.CompareTo(criteria, y)); - InternalChildren?.ForEach(c => c.Filter(criteria)); } public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => GetHashCode().CompareTo(other.GetHashCode()); From 23e014b52dd7837e397b4625d066604fdfb8e168 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 12:05:49 +0900 Subject: [PATCH 151/239] Simplify drawable removal logic --- osu.Game/Screens/Select/BeatmapCarousel.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c090ea6f0a..6dc7dd8922 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -475,12 +475,7 @@ namespace osu.Game.Screens.Select float drawHeight = DrawHeight; // Remove all items that should no longer be on-screen - scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) - { - float itemPosY = p.Position.Y; - bool remove = itemPosY < Current - p.DrawHeight || itemPosY > Current + drawHeight || !p.IsPresent; - return remove; - }); + scrollableContent.RemoveAll(p => p.Y < Current - p.DrawHeight || p.Y > Current + drawHeight || !p.IsPresent); // Find index range of all items that should be on-screen Trace.Assert(Items.Count == yPositions.Count); From 6121cd3b67f31d0375d32725880a922395e1a112 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 12:30:39 +0900 Subject: [PATCH 152/239] Remove animating skipping and reorder file a bit --- osu.Game/Screens/Select/BeatmapCarousel.cs | 257 ++++++++++----------- 1 file changed, 125 insertions(+), 132 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6dc7dd8922..ff1dd95eac 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -106,6 +106,12 @@ namespace osu.Game.Screens.Select }; } + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); + } + public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) { Schedule(() => @@ -255,6 +261,12 @@ namespace osu.Game.Screens.Select } } + private void select(CarouselItem item) + { + if (item == null) return; + item.State.Value = CarouselItemState.Selected; + } + private FilterCriteria activeCriteria = new FilterCriteria(); protected ScheduledDelegate FilterTask; @@ -285,15 +297,7 @@ namespace osu.Game.Screens.Select root.Filter(activeCriteria); itemsCache.Invalidate(); - - if (scroll) - { - Schedule(() => - { - updateItems(false); - updateScrollPosition(false); - }); - } + if (scroll) scrollPositionCache.Invalidate(); } FilterTask?.Cancel(); @@ -309,129 +313,6 @@ namespace osu.Game.Screens.Select public void ScrollToSelected() => scrollPositionCache.Invalidate(); - private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Beatmaps.All(b => b.Hidden)) - return null; - - // todo: remove the need for this. - foreach (var b in beatmapSet.Beatmaps) - { - if (b.Metadata == null) - b.Metadata = beatmapSet.Metadata; - } - - var set = new CarouselBeatmapSet(beatmapSet); - - foreach (var c in set.Beatmaps) - { - c.State.ValueChanged += v => - { - if (v == CarouselItemState.Selected) - { - selectedBeatmapSet = set; - SelectionChanged?.Invoke(c.Beatmap); - - itemsCache.Invalidate(); - scrollPositionCache.Invalidate(); - } - }; - } - - return set; - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuConfigManager config) - { - config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); - } - - /// - /// Computes the target Y positions for every item in the carousel. - /// - /// The Y position of the currently selected item. - private void updateItems(bool animated = true) - { - Items = root.Drawables.ToList(); - - yPositions.Clear(); - - float currentY = DrawHeight / 2; - DrawableCarouselBeatmapSet lastSet = null; - - scrollTarget = null; - - foreach (DrawableCarouselItem d in Items) - { - if (d.IsPresent) - { - switch (d) - { - case DrawableCarouselBeatmapSet set: - lastSet = set; - - set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); - set.MoveToY(currentY, animated ? 750 : 0, Easing.OutExpo); - break; - case DrawableCarouselBeatmap beatmap: - if (beatmap.Item.State.Value == CarouselItemState.Selected) - scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; - - void performMove(float y, float? startY = null) - { - if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); - beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); - beatmap.MoveToY(y, animated ? 750 : 0, Easing.OutExpo); - } - - Debug.Assert(lastSet != null); - - float? setY = null; - if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. - setY = lastSet.Y + lastSet.DrawHeight + 5; - - if (d.IsLoaded) - performMove(currentY, setY); - else - { - float y = currentY; - d.OnLoadComplete = _ => performMove(y, setY); - } - break; - } - } - - yPositions.Add(currentY); - - if (d.Item.Visible) - currentY += d.DrawHeight + 5; - } - - currentY += DrawHeight / 2; - scrollableContent.Height = currentY; - - if (selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected) - { - selectedBeatmapSet = null; - SelectionChanged?.Invoke(null); - } - - itemsCache.Validate(); - } - - private void updateScrollPosition(bool animated = true) - { - if (scrollTarget != null) ScrollTo(scrollTarget.Value, animated); - scrollPositionCache.Validate(); - } - - private void select(CarouselItem item) - { - if (item == null) return; - item.State.Value = CarouselItemState.Selected; - } - protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { int direction = 0; @@ -531,6 +412,118 @@ namespace osu.Game.Screens.Select updateItem(p, halfHeight); } + private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Beatmaps.All(b => b.Hidden)) + return null; + + // todo: remove the need for this. + foreach (var b in beatmapSet.Beatmaps) + { + if (b.Metadata == null) + b.Metadata = beatmapSet.Metadata; + } + + var set = new CarouselBeatmapSet(beatmapSet); + + foreach (var c in set.Beatmaps) + { + c.State.ValueChanged += v => + { + if (v == CarouselItemState.Selected) + { + selectedBeatmapSet = set; + SelectionChanged?.Invoke(c.Beatmap); + + itemsCache.Invalidate(); + scrollPositionCache.Invalidate(); + } + }; + } + + return set; + } + + /// + /// Computes the target Y positions for every item in the carousel. + /// + /// The Y position of the currently selected item. + private void updateItems() + { + Items = root.Drawables.ToList(); + + yPositions.Clear(); + + float currentY = DrawHeight / 2; + DrawableCarouselBeatmapSet lastSet = null; + + scrollTarget = null; + + foreach (DrawableCarouselItem d in Items) + { + if (d.IsPresent) + { + switch (d) + { + case DrawableCarouselBeatmapSet set: + lastSet = set; + + set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + set.MoveToY(currentY, 750, Easing.OutExpo); + break; + case DrawableCarouselBeatmap beatmap: + if (beatmap.Item.State.Value == CarouselItemState.Selected) + scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; + + void performMove(float y, float? startY = null) + { + if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); + beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + beatmap.MoveToY(y, 750, Easing.OutExpo); + } + + Debug.Assert(lastSet != null); + + float? setY = null; + if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override. + setY = lastSet.Y + lastSet.DrawHeight + 5; + + if (d.IsLoaded) + performMove(currentY, setY); + else + { + float y = currentY; + d.OnLoadComplete = _ => performMove(y, setY); + } + + break; + } + } + + yPositions.Add(currentY); + + if (d.Item.Visible) + currentY += d.DrawHeight + 5; + } + + currentY += DrawHeight / 2; + scrollableContent.Height = currentY; + + if (selectedBeatmapSet != null && selectedBeatmapSet.State.Value != CarouselItemState.Selected) + { + selectedBeatmapSet = null; + SelectionChanged?.Invoke(null); + } + + itemsCache.Validate(); + } + + private void updateScrollPosition() + { + if (scrollTarget != null) ScrollTo(scrollTarget.Value); + scrollPositionCache.Validate(); + } + /// /// Computes the x-offset of currently visible items. Makes the carousel appear round. /// From 17c58678cf195eb95d873d456a07a88eaaa1d7ff Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Mon, 18 Dec 2017 13:59:46 +0900 Subject: [PATCH 153/239] Update SelectionInfo.cs --- osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs index 402bec1706..aec16bd46d 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs @@ -1,3 +1,6 @@ +// 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 osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects.Drawables; From f3a5258c5bdea596fe114418f3c36a9c625f4624 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 14:05:12 +0900 Subject: [PATCH 154/239] Reorder file (ctor + bdl) --- osu.Game/Screens/Play/MenuOverlay.cs | 132 +++++++++++++-------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index 0a8e172e57..6d25c33243 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -32,73 +32,11 @@ namespace osu.Game.Screens.Play protected FillFlowContainer Buttons; - public int Retries - { - set - { - if (retryCounterContainer != null) - { - // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" - - retryCounterContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $"{value:n0}", - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $" time{(value == 1 ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - } - }; - } - } - } - private FillFlowContainer retryCounterContainer; - public override bool HandleInput => State == Visibility.Visible; - - protected override void PopIn() => this.FadeIn(transition_duration, Easing.In); - protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); - - // Don't let mouse down events through the overlay or people can click circles while paused. - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; - - protected override bool OnMouseMove(InputState state) => true; - - protected void AddButton(string text, Color4 colour, Action action) + protected MenuOverlay() { - Buttons.Add(new Button - { - Text = text, - ButtonColour = colour, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Height = button_height, - Action = delegate - { - action?.Invoke(); - Hide(); - } - }); + RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -182,9 +120,71 @@ namespace osu.Game.Screens.Play Retries = 0; } - protected MenuOverlay() + public int Retries { - RelativeSizeAxes = Axes.Both; + set + { + if (retryCounterContainer != null) + { + // "You've retried 1,065 times in this session" + // "You've retried 1 time in this session" + + retryCounterContainer.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $"{value:n0}", + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $" time{(value == 1 ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + } + }; + } + } + } + + public override bool HandleInput => State == Visibility.Visible; + + protected override void PopIn() => this.FadeIn(transition_duration, Easing.In); + protected override void PopOut() => this.FadeOut(transition_duration, Easing.In); + + // Don't let mouse down events through the overlay or people can click circles while paused. + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; + + protected override bool OnMouseMove(InputState state) => true; + + protected void AddButton(string text, Color4 colour, Action action) + { + Buttons.Add(new Button + { + Text = text, + ButtonColour = colour, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Height = button_height, + Action = delegate + { + action?.Invoke(); + Hide(); + } + }); } public class Button : DialogButton From 812181190e36ef71da107d299c99e111f18cb909 Mon Sep 17 00:00:00 2001 From: Dan Balasescu <1329837+smoogipoo@users.noreply.github.com> Date: Mon, 18 Dec 2017 14:10:14 +0900 Subject: [PATCH 155/239] Update SelectionLayer.cs --- osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index af8eac847e..392883daa5 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -1,13 +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.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit.Layers.Selection From 8fdaf6f8f41a6520abc1ffecfb423f5b69aab8d0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 14:47:17 +0900 Subject: [PATCH 156/239] Restructure DialogButton to support selection --- .../Graphics/UserInterface/DialogButton.cs | 251 +++++++++--------- 1 file changed, 132 insertions(+), 119 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index bb62815a7b..3b3cb953de 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; +using osu.Framework.Configuration; namespace osu.Game.Graphics.UserInterface { @@ -22,62 +23,7 @@ namespace osu.Game.Graphics.UserInterface private const float glow_fade_duration = 250; private const float click_duration = 200; - private Color4 buttonColour; - public Color4 ButtonColour - { - get - { - return buttonColour; - } - set - { - buttonColour = value; - updateGlow(); - colourContainer.Colour = value; - } - } - - private Color4 backgroundColour = OsuColour.Gray(34); - public Color4 BackgroundColour - { - get - { - return backgroundColour; - } - set - { - backgroundColour = value; - background.Colour = value; - } - } - - private string text; - public string Text - { - get - { - return text; - } - set - { - text = value; - spriteText.Text = Text; - } - } - - private float textSize = 28; - public float TextSize - { - get - { - return textSize; - } - set - { - textSize = value; - spriteText.TextSize = value; - } - } + public readonly BindableBool Selected = new BindableBool(); private readonly Container backgroundContainer; private readonly Container colourContainer; @@ -91,69 +37,6 @@ namespace osu.Game.Graphics.UserInterface private bool didClick; // Used for making sure that the OnMouseDown animation can call instead of OnHoverLost's when clicking - public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); - - protected override bool OnClick(Framework.Input.InputState state) - { - didClick = true; - colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); - flash(); - - this.Delay(click_duration).Schedule(delegate - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f)); - spriteText.Spacing = Vector2.Zero; - glowContainer.FadeOut(); - }); - - return base.OnClick(state); - } - - protected override bool OnHover(Framework.Input.InputState state) - { - spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - - colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); - glowContainer.FadeIn(glow_fade_duration, Easing.Out); - base.OnHover(state); - return true; - } - - protected override void OnHoverLost(Framework.Input.InputState state) - { - if (!didClick) - { - colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); - spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(glow_fade_duration, Easing.Out); - } - - didClick = false; - } - - private void flash() - { - var flash = new Box - { - RelativeSizeAxes = Axes.Both - }; - - colourContainer.Add(flash); - - flash.Colour = ButtonColour; - flash.Blending = BlendingMode.Additive; - flash.Alpha = 0.3f; - flash.FadeOutFromOne(click_duration); - flash.Expire(); - } - - private void updateGlow() - { - leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); - centerGlow.Colour = ButtonColour; - rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); - } - public DialogButton() { RelativeSizeAxes = Axes.X; @@ -268,6 +151,136 @@ namespace osu.Game.Graphics.UserInterface }; updateGlow(); + + Selected.ValueChanged += selectionChanged; + } + + private Color4 buttonColour; + public Color4 ButtonColour + { + get + { + return buttonColour; + } + set + { + buttonColour = value; + updateGlow(); + colourContainer.Colour = value; + } + } + + private Color4 backgroundColour = OsuColour.Gray(34); + public Color4 BackgroundColour + { + get + { + return backgroundColour; + } + set + { + backgroundColour = value; + background.Colour = value; + } + } + + private string text; + public string Text + { + get + { + return text; + } + set + { + text = value; + spriteText.Text = Text; + } + } + + private float textSize = 28; + public float TextSize + { + get + { + return textSize; + } + set + { + textSize = value; + spriteText.TextSize = value; + } + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); + + protected override bool OnClick(Framework.Input.InputState state) + { + didClick = true; + colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); + flash(); + + this.Delay(click_duration).Schedule(delegate + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f)); + spriteText.Spacing = Vector2.Zero; + glowContainer.FadeOut(); + }); + + return base.OnClick(state); + } + + protected override bool OnHover(Framework.Input.InputState state) + { + base.OnHover(state); + + Selected.Value = true; + return true; + } + + protected override void OnHoverLost(Framework.Input.InputState state) + { + base.OnHoverLost(state); + Selected.Value = false; + } + + private void selectionChanged(bool isSelected) + { + if (isSelected) + { + spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); + colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); + glowContainer.FadeIn(glow_fade_duration, Easing.Out); + } + else if (!didClick) + { + colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); + spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); + glowContainer.FadeOut(glow_fade_duration, Easing.Out); + } + } + + private void flash() + { + var flash = new Box + { + RelativeSizeAxes = Axes.Both + }; + + colourContainer.Add(flash); + + flash.Colour = ButtonColour; + flash.Blending = BlendingMode.Additive; + flash.Alpha = 0.3f; + flash.FadeOutFromOne(click_duration); + flash.Expire(); + } + + private void updateGlow() + { + leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour); + centerGlow.Colour = ButtonColour; + rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f)); } } } From 9fb3d3704a7e0601006233ad20ac30ff5cf51f85 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 15:42:11 +0900 Subject: [PATCH 157/239] TestCaseMenuOverlays -> TestCaseMenuOverlay --- .../{TestCaseMenuOverlays.cs => TestCaseMenuOverlay.cs} | 8 ++++++-- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) rename osu.Game.Tests/Visual/{TestCaseMenuOverlays.cs => TestCaseMenuOverlay.cs} (83%) diff --git a/osu.Game.Tests/Visual/TestCaseMenuOverlays.cs b/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs similarity index 83% rename from osu.Game.Tests/Visual/TestCaseMenuOverlays.cs rename to osu.Game.Tests/Visual/TestCaseMenuOverlay.cs index 94a69f0029..da846a1b39 100644 --- a/osu.Game.Tests/Visual/TestCaseMenuOverlays.cs +++ b/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs @@ -1,6 +1,8 @@ // 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.ComponentModel; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; @@ -9,9 +11,11 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("player pause/fail screens")] - internal class TestCaseMenuOverlays : OsuTestCase + internal class TestCaseMenuOverlay : OsuTestCase { - public TestCaseMenuOverlays() + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer.PauseOverlay) }; + + public TestCaseMenuOverlay() { FailOverlay failOverlay; PauseContainer.PauseOverlay pauseOverlay; diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 184561faa7..cf91ca0c15 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -120,7 +120,7 @@ - + From 1e4cad900dd46cf2ce3d5509f5224695eb9dc32f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:04:51 +0900 Subject: [PATCH 158/239] Fix up incorrect RequiredTypes --- osu.Game.Tests/Visual/TestCaseMenuOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs index da846a1b39..6c1c615ca9 100644 --- a/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual [Description("player pause/fail screens")] internal class TestCaseMenuOverlay : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer.PauseOverlay) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; public TestCaseMenuOverlay() { From 59365bbdce1ec25feec558c6ab4f10ae6e0721d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:29:40 +0900 Subject: [PATCH 159/239] Make MenuOverlay support key selections --- osu.Game/Screens/Play/MenuOverlay.cs | 72 ++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index 6d25c33243..452eaae574 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; +using OpenTK.Input; namespace osu.Game.Screens.Play { @@ -37,6 +38,8 @@ namespace osu.Game.Screens.Play protected MenuOverlay() { RelativeSizeAxes = Axes.Both; + + StateChanged += s => selectionIndex = -1; } [BackgroundDependencyLoader] @@ -172,7 +175,7 @@ namespace osu.Game.Screens.Play protected void AddButton(string text, Color4 colour, Action action) { - Buttons.Add(new Button + var button = new MenuOverlayButton { Text = text, ButtonColour = colour, @@ -184,11 +187,74 @@ namespace osu.Game.Screens.Play action?.Invoke(); Hide(); } - }); + }; + + button.Selected.ValueChanged += s => buttonSelectionChanged(button, s); + + Buttons.Add(button); } - public class Button : DialogButton + private int _selectionIndex = -1; + private int selectionIndex { + get { return _selectionIndex; } + set + { + if (_selectionIndex == value) + return; + + if (_selectionIndex != -1) + Buttons[_selectionIndex].Selected.Value = false; + + _selectionIndex = value; + + if (_selectionIndex != -1) + Buttons[_selectionIndex].Selected.Value = true; + } + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat) + return false; + + switch (args.Key) + { + case Key.Up: + if (selectionIndex == -1 || selectionIndex == 0) + selectionIndex = Buttons.Count - 1; + else + selectionIndex--; + return true; + case Key.Down: + if (selectionIndex == -1 || selectionIndex == Buttons.Count - 1) + selectionIndex = 0; + else + selectionIndex++; + return true; + } + + return false; + } + + private void buttonSelectionChanged(DialogButton button, bool isSelected) + { + if (!isSelected) + selectionIndex = -1; + else + selectionIndex = Buttons.IndexOf(button); + } + + private class MenuOverlayButton : DialogButton + { + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + if (args.Repeat || args.Key != Key.Enter || !Selected) + return false; + + OnClick(state); + return true; + } } } } From 5f538f03eab7c0a70c811f71c2b5323f055c4786 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:34:25 +0900 Subject: [PATCH 160/239] Comments --- osu.Game/Screens/Play/MenuOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/MenuOverlay.cs index 452eaae574..6e3c1a4d44 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/MenuOverlay.cs @@ -203,11 +203,13 @@ namespace osu.Game.Screens.Play if (_selectionIndex == value) return; + // Deselect the previously-selected button if (_selectionIndex != -1) Buttons[_selectionIndex].Selected.Value = false; _selectionIndex = value; + // Select the newly-selected button if (_selectionIndex != -1) Buttons[_selectionIndex].Selected.Value = true; } From 846750a1905315dcb7861edc6d01640221c1ae8d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:36:11 +0900 Subject: [PATCH 161/239] Remove unnecessary flag --- osu.Game/Graphics/UserInterface/DialogButton.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 3b3cb953de..320bf51aaf 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -35,8 +35,6 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteText spriteText; private Vector2 hoverSpacing => new Vector2(3f, 0f); - private bool didClick; // Used for making sure that the OnMouseDown animation can call instead of OnHoverLost's when clicking - public DialogButton() { RelativeSizeAxes = Axes.X; @@ -216,7 +214,6 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnClick(Framework.Input.InputState state) { - didClick = true; colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); flash(); @@ -252,7 +249,7 @@ namespace osu.Game.Graphics.UserInterface colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); glowContainer.FadeIn(glow_fade_duration, Easing.Out); } - else if (!didClick) + else { colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); From cf640d084e50b78b275df22503953b17ab154aec Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:38:15 +0900 Subject: [PATCH 162/239] Use using --- osu.Game/Graphics/UserInterface/DialogButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 320bf51aaf..f07bc4114f 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Configuration; +using osu.Framework.Input; namespace osu.Game.Graphics.UserInterface { @@ -212,7 +213,7 @@ namespace osu.Game.Graphics.UserInterface public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos); - protected override bool OnClick(Framework.Input.InputState state) + protected override bool OnClick(InputState state) { colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In); flash(); @@ -227,7 +228,7 @@ namespace osu.Game.Graphics.UserInterface return base.OnClick(state); } - protected override bool OnHover(Framework.Input.InputState state) + protected override bool OnHover(InputState state) { base.OnHover(state); @@ -235,7 +236,7 @@ namespace osu.Game.Graphics.UserInterface return true; } - protected override void OnHoverLost(Framework.Input.InputState state) + protected override void OnHoverLost(InputState state) { base.OnHoverLost(state); Selected.Value = false; From 918e7c9a4bc8ef7cd4e41dc1f169efc7fa85adb6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 16:40:50 +0900 Subject: [PATCH 163/239] MenuOverlay -> GameplayMenuOverlay --- ...tCaseMenuOverlay.cs => TestCaseGameplayMenuOverlay.cs} | 4 ++-- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/Screens/Play/FailOverlay.cs | 2 +- .../Play/{MenuOverlay.cs => GameplayMenuOverlay.cs} | 8 ++++---- osu.Game/Screens/Play/PauseContainer.cs | 2 +- osu.Game/osu.Game.csproj | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Visual/{TestCaseMenuOverlay.cs => TestCaseGameplayMenuOverlay.cs} (91%) rename osu.Game/Screens/Play/{MenuOverlay.cs => GameplayMenuOverlay.cs} (94%) diff --git a/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseMenuOverlay.cs rename to osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 6c1c615ca9..ecb107085a 100644 --- a/osu.Game.Tests/Visual/TestCaseMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -11,11 +11,11 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("player pause/fail screens")] - internal class TestCaseMenuOverlay : OsuTestCase + internal class TestCaseGameplayMenuOverlay : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; - public TestCaseMenuOverlay() + public TestCaseGameplayMenuOverlay() { FailOverlay failOverlay; PauseContainer.PauseOverlay pauseOverlay; diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index cf91ca0c15..5442ce63dd 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -120,7 +120,7 @@ - + diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 3e31da2348..3242d8bb6e 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -10,7 +10,7 @@ using System.Linq; namespace osu.Game.Screens.Play { - public class FailOverlay : MenuOverlay + public class FailOverlay : GameplayMenuOverlay { public override string Header => "failed"; public override string Description => "you're dead, try again?"; diff --git a/osu.Game/Screens/Play/MenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs similarity index 94% rename from osu.Game/Screens/Play/MenuOverlay.cs rename to osu.Game/Screens/Play/GameplayMenuOverlay.cs index 6e3c1a4d44..996717cbb5 100644 --- a/osu.Game/Screens/Play/MenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -17,7 +17,7 @@ using OpenTK.Input; namespace osu.Game.Screens.Play { - public abstract class MenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition + public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition { private const int transition_duration = 200; private const int button_height = 70; @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play private FillFlowContainer retryCounterContainer; - protected MenuOverlay() + protected GameplayMenuOverlay() { RelativeSizeAxes = Axes.Both; @@ -175,7 +175,7 @@ namespace osu.Game.Screens.Play protected void AddButton(string text, Color4 colour, Action action) { - var button = new MenuOverlayButton + var button = new Button { Text = text, ButtonColour = colour, @@ -247,7 +247,7 @@ namespace osu.Game.Screens.Play selectionIndex = Buttons.IndexOf(button); } - private class MenuOverlayButton : DialogButton + private class Button : DialogButton { protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 5f5eeb63a0..6812ac5fc5 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play base.Update(); } - public class PauseOverlay : MenuOverlay + public class PauseOverlay : GameplayMenuOverlay { public Action OnResume; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c190d988fa..155f08fd66 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -719,7 +719,7 @@ - + From d2b80fdbfc9bc3e85cd32594a25b44a893cb4b27 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 18 Dec 2017 10:55:07 +0100 Subject: [PATCH 164/239] Moved "undelete all" logic to BeatmapManager and added a progress notification --- osu.Game/Beatmaps/BeatmapManager.cs | 30 +++++++++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 6 +--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1acca352a7..8170ec959c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -339,6 +339,36 @@ namespace osu.Game.Beatmaps } } + public void UndeleteAll() + { + var mapSets = QueryBeatmapSets(bs => bs.DeletePending); + + if (mapSets.Count() == 0) return; + + var notification = new ProgressNotification + { + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var bs in mapSets) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Restoring ({i} of {mapSets.Count()})"; + notification.Progress = (float)++i / mapSets.Count(); + Undelete(bs); + } + + notification.State = ProgressNotificationState.Completed; + } + public void Undelete(BeatmapSetInfo beatmapSet) { if (beatmapSet.Protected) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index dcad5ab52c..0b0cc1a17f 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -62,11 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(() => - { - foreach (var bs in beatmaps.QueryBeatmapSets(bs => bs.DeletePending).ToList()) - beatmaps.Undelete(bs); - }).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(() => beatmaps.UndeleteAll()).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, }; From 7c9d11756e566fb15bbc4ec1475680b16880a635 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 18 Dec 2017 10:59:25 +0100 Subject: [PATCH 165/239] Add the ability to change what text is displayed when a ProgressNotification finishes its task. --- osu.Game/Overlays/Notifications/ProgressNotification.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 58aff16de0..12c7fe64ba 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Notifications } } + public string CompletionText { get; set; } = "Task has completed!"; + public float Progress { get { return progressBar.Progress; } @@ -87,7 +89,7 @@ namespace osu.Game.Overlays.Notifications protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification { Activated = CompletionClickAction, - Text = "Task has completed!" + Text = CompletionText }; protected virtual void Completed() From a17b2e4c1801388afb692b957a9aaff01be0c1fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:13:08 +0900 Subject: [PATCH 166/239] Expose buttons for test cases --- osu.Game/Screens/Play/FailOverlay.cs | 2 +- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 18 ++++++++++-------- osu.Game/Screens/Play/PauseContainer.cs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 3242d8bb6e..a6a233058b 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play { if (!args.Repeat && args.Key == Key.Escape) { - Buttons.Children.Last().TriggerOnClick(); + InternalButtons.Children.Last().TriggerOnClick(); return true; } diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 996717cbb5..182c4efe89 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -14,6 +14,7 @@ using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using OpenTK.Input; +using System.Collections.Generic; namespace osu.Game.Screens.Play { @@ -31,7 +32,8 @@ namespace osu.Game.Screens.Play public abstract string Header { get; } public abstract string Description { get; } - protected FillFlowContainer Buttons; + protected internal FillFlowContainer InternalButtons; + public IReadOnlyList Buttons => InternalButtons; private FillFlowContainer retryCounterContainer; @@ -95,7 +97,7 @@ namespace osu.Game.Screens.Play } } }, - Buttons = new FillFlowContainer + InternalButtons = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -191,7 +193,7 @@ namespace osu.Game.Screens.Play button.Selected.ValueChanged += s => buttonSelectionChanged(button, s); - Buttons.Add(button); + InternalButtons.Add(button); } private int _selectionIndex = -1; @@ -205,13 +207,13 @@ namespace osu.Game.Screens.Play // Deselect the previously-selected button if (_selectionIndex != -1) - Buttons[_selectionIndex].Selected.Value = false; + InternalButtons[_selectionIndex].Selected.Value = false; _selectionIndex = value; // Select the newly-selected button if (_selectionIndex != -1) - Buttons[_selectionIndex].Selected.Value = true; + InternalButtons[_selectionIndex].Selected.Value = true; } } @@ -224,12 +226,12 @@ namespace osu.Game.Screens.Play { case Key.Up: if (selectionIndex == -1 || selectionIndex == 0) - selectionIndex = Buttons.Count - 1; + selectionIndex = InternalButtons.Count - 1; else selectionIndex--; return true; case Key.Down: - if (selectionIndex == -1 || selectionIndex == Buttons.Count - 1) + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) selectionIndex = 0; else selectionIndex++; @@ -244,7 +246,7 @@ namespace osu.Game.Screens.Play if (!isSelected) selectionIndex = -1; else - selectionIndex = Buttons.IndexOf(button); + selectionIndex = InternalButtons.IndexOf(button); } private class Button : DialogButton diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 6812ac5fc5..8545c50536 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Play { if (!args.Repeat && args.Key == Key.Escape) { - Buttons.Children.First().TriggerOnClick(); + InternalButtons.Children.First().TriggerOnClick(); return true; } From 5e111e14db9845f48dafa9264d100dce60d0c214 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:13:25 +0900 Subject: [PATCH 167/239] Make it possible to change the overlay actions beyond instantiation --- osu.Game/Screens/Play/FailOverlay.cs | 4 ++-- osu.Game/Screens/Play/PauseContainer.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index a6a233058b..09f2e15c57 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -18,8 +18,8 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton("Retry", colours.YellowDark, OnRetry); - AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index 8545c50536..3bd28511c7 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -140,9 +140,9 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuColour colours) { - AddButton("Continue", colours.Green, OnResume); - AddButton("Retry", colours.YellowDark, OnRetry); - AddButton("Quit", new Color4(170, 27, 39, 255), OnQuit); + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); } } } From f90e3346c1f8f836f1f3d084143c693668828a56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:13:33 +0900 Subject: [PATCH 168/239] Add automated test cases --- .../Visual/TestCaseGameplayMenuOverlay.cs | 247 ++++++++++++++++-- 1 file changed, 222 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index ecb107085a..8389037a71 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -4,7 +4,11 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; +using OpenTK.Input; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Screens.Play; @@ -15,47 +19,240 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; - public TestCaseGameplayMenuOverlay() + private FailOverlay failOverlay; + private PauseContainer.PauseOverlay pauseOverlay; + + [BackgroundDependencyLoader] + private void load() { - FailOverlay failOverlay; - PauseContainer.PauseOverlay pauseOverlay; - - var retryCount = 0; - Add(pauseOverlay = new PauseContainer.PauseOverlay { OnResume = () => Logger.Log(@"Resume"), OnRetry = () => Logger.Log(@"Retry"), OnQuit = () => Logger.Log(@"Quit"), }); + Add(failOverlay = new FailOverlay { OnRetry = () => Logger.Log(@"Retry"), OnQuit = () => Logger.Log(@"Quit"), }); - AddStep(@"Pause", delegate - { - if (failOverlay.State == Visibility.Visible) - { - failOverlay.Hide(); - } - pauseOverlay.Show(); - }); - AddStep("Fail", delegate - { - if (pauseOverlay.State == Visibility.Visible) - { - pauseOverlay.Hide(); - } - failOverlay.Show(); - }); - AddStep("Add Retry", delegate + var retryCount = 0; + + AddStep("Add retry", () => { retryCount++; - pauseOverlay.Retries = retryCount; - failOverlay.Retries = retryCount; + pauseOverlay.Retries = failOverlay.Retries = retryCount; }); + + AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility()); + AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility()); + + testHideResets(); + + testEnterWithoutSelection(); + testKeyUpFromInitial(); + testKeyDownFromInitial(); + testKeyUpWrapping(); + testKeyDownWrapping(); + + testMouseSelectionAfterKeySelection(); + testKeySelectionAfterMouseSelection(); + + testMouseDeselectionResets(); + + testClickSelection(); + testEnterKeySelection(); + } + + /// + /// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected. + /// + private void testHideResets() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnHover(null)); + AddStep("Hide overlay", () => failOverlay.Hide()); + + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); + } + + /// + /// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred. + /// + private void testEnterWithoutSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter })); + AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow from the initial state selects the last button. + /// + private void testKeyUpFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow from the initial state selects the first button. + /// + private void testKeyDownFromInitial() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyUpWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly. + /// + private void testKeyDownWrapping() + { + AddStep("Show overlay", () => failOverlay.Show()); + + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => failOverlay.Hide()); + } + + /// + /// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button. + /// + private void testMouseSelectionAfterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); + AddAssert("Second button selected", () => secondButton.Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button. + /// + private void testKeySelectionAfterMouseSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up })); + AddAssert("Second button not selected", () => !secondButton.Selected); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state. + /// + private void testMouseDeselectionResets() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var secondButton = pauseOverlay.Buttons.Skip(1).First(); + + AddStep("Hover second button", () => secondButton.TriggerOnHover(null)); + AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null)); + AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down })); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition + + AddStep("Hide overlay", () => pauseOverlay.Hide()); + } + + /// + /// Tests that clicking on a button correctly causes a click event for that button. + /// + private void testClickSelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Click retry button", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnClick(); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + } + + /// + /// Tests that pressing the enter key with a button selected correctly causes a click event for that button. + /// + private void testEnterKeySelection() + { + AddStep("Show overlay", () => pauseOverlay.Show()); + + AddStep("Select second button", () => + { + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }); + }); + + var retryButton = pauseOverlay.Buttons.Skip(1).First(); + + bool triggered = false; + AddStep("Press enter", () => + { + var lastAction = pauseOverlay.OnRetry; + pauseOverlay.OnRetry = () => triggered = true; + + retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }); + pauseOverlay.OnRetry = lastAction; + }); + + AddAssert("Action was triggered", () => triggered); + AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); } } } From f89848152363d8558f29e5f730cac579b17689fb Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 18 Dec 2017 11:14:07 +0100 Subject: [PATCH 169/239] Changed existing implementations to have a custom CompletionText --- osu.Desktop/Overlays/VersionManager.cs | 6 +++++- osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs | 12 ++++++++++-- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 9e13003c3f..94c09c2624 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -156,7 +156,11 @@ namespace osu.Desktop.Overlays if (notification == null) { - notification = new UpdateProgressNotification { State = ProgressNotificationState.Active }; + notification = new UpdateProgressNotification + { + CompletionText = "Successfully updated the game!", + State = ProgressNotificationState.Active + }; Schedule(() => notificationOverlay.Post(notification)); } diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 3dca860909..b0141649f2 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -82,7 +82,11 @@ namespace osu.Game.Tests.Visual private void sendProgress2() { - var n = new ProgressNotification { Text = @"Downloading Haitai..." }; + var n = new ProgressNotification + { + Text = @"Downloading Haitai...", + CompletionText = "Downloaded Haitai!", + }; manager.Post(n); progressingNotifications.Add(n); } @@ -91,7 +95,11 @@ namespace osu.Game.Tests.Visual private void sendProgress1() { - var n = new ProgressNotification { Text = @"Uploading to BSS..." }; + var n = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; manager.Post(n); progressingNotifications.Add(n); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c4b2c93d7e..93ceefa74b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -134,6 +134,7 @@ namespace osu.Game.Beatmaps var notification = new ProgressNotification { Text = "Beatmap import is initialising...", + CompletionText = "Import successful!", Progress = 0, State = ProgressNotificationState.Active, }; @@ -245,8 +246,9 @@ namespace osu.Game.Beatmaps return; } - ProgressNotification downloadNotification = new ProgressNotification + var downloadNotification = new ProgressNotification { + CompletionText = $"Installed {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", }; @@ -665,6 +667,7 @@ namespace osu.Game.Beatmaps var notification = new ProgressNotification { Progress = 0, + CompletionText = "Deleted all beatmaps!", State = ProgressNotificationState.Active, }; From ba614883ea9140de83abe64bb5031cea6c87f372 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 18 Dec 2017 11:16:57 +0100 Subject: [PATCH 170/239] used Any() instead of manually checking count == 0 (CI) --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8170ec959c..9933606952 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -343,7 +343,7 @@ namespace osu.Game.Beatmaps { var mapSets = QueryBeatmapSets(bs => bs.DeletePending); - if (mapSets.Count() == 0) return; + if (!mapSets.Any()) return; var notification = new ProgressNotification { From 399994053887fef435980372252f33fbb6ade4f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:16:33 +0900 Subject: [PATCH 171/239] DragSelector -> HitObjectSelectionBox --- ...{DragSelector.cs => HitObjectSelectionBox.cs} | 6 +++--- .../Rulesets/Edit/Layers/Selection/Marker.cs | 2 +- .../Edit/Layers/Selection/SelectionLayer.cs | 16 ++++++++-------- osu.Game/osu.Game.csproj | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Rulesets/Edit/Layers/Selection/{DragSelector.cs => HitObjectSelectionBox.cs} (94%) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs similarity index 94% rename from osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index 2201a661b7..6ea2778a9e 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/DragSelector.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box that represents a drag selection. /// - public class DragSelector : CompositeDrawable + public class HitObjectSelectionBox : CompositeDrawable { public readonly Bindable Selection = new Bindable(); @@ -37,10 +37,10 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private readonly Vector2 startPos; /// - /// Creates a new . + /// Creates a new . /// /// The point at which the drag was initiated, in the parent's coordinates. - public DragSelector(Vector2 startPos) + public HitObjectSelectionBox(Vector2 startPos) { this.startPos = startPos; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs index cee6ac63d5..956415a800 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { /// /// Represents a marker visible on the border of a which exposes - /// properties that are used to resize a . + /// properties that are used to resize a . /// public class Marker : CompositeDrawable { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index 392883daa5..98bcfd0ec8 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -22,39 +22,39 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private DragSelector selector; + private HitObjectSelectionBox selectionBoxBox; protected override bool OnDragStart(InputState state) { // Hide the previous drag box - we won't be working with it any longer - selector?.Hide(); + selectionBoxBox?.Hide(); - AddInternal(selector = new DragSelector(ToLocalSpace(state.Mouse.NativeState.Position)) + AddInternal(selectionBoxBox = new HitObjectSelectionBox(ToLocalSpace(state.Mouse.NativeState.Position)) { CapturableObjects = playfield.HitObjects.Objects, }); - Selection.BindTo(selector.Selection); + Selection.BindTo(selectionBoxBox.Selection); return true; } protected override bool OnDrag(InputState state) { - selector.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); - selector.BeginCapture(); + selectionBoxBox.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); + selectionBoxBox.BeginCapture(); return true; } protected override bool OnDragEnd(InputState state) { - selector.FinishCapture(); + selectionBoxBox.FinishCapture(); return true; } protected override bool OnClick(InputState state) { - selector?.Hide(); + selectionBoxBox?.Hide(); return true; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index fca3f57413..514f91f80a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -308,7 +308,7 @@ - + From 09c51df2bd1da9f76a306bc2383a86c1a034fa0f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:21:26 +0900 Subject: [PATCH 172/239] Marker* -> Handle --- .../Layers/Selection/{Marker.cs => Handle.cs} | 210 +++++++++--------- ...{MarkerContainer.cs => HandleContainer.cs} | 32 +-- .../Layers/Selection/HitObjectSelectionBox.cs | 6 +- .../{CentreMarker.cs => OriginHandle.cs} | 6 +- osu.Game/osu.Game.csproj | 6 +- 5 files changed, 130 insertions(+), 130 deletions(-) rename osu.Game/Rulesets/Edit/Layers/Selection/{Marker.cs => Handle.cs} (88%) rename osu.Game/Rulesets/Edit/Layers/Selection/{MarkerContainer.cs => HandleContainer.cs} (74%) rename osu.Game/Rulesets/Edit/Layers/Selection/{CentreMarker.cs => OriginHandle.cs} (86%) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs similarity index 88% rename from osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs index 956415a800..e93fe886e1 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Marker.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs @@ -1,105 +1,105 @@ -// 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; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// Represents a marker visible on the border of a which exposes - /// properties that are used to resize a . - /// - public class Marker : CompositeDrawable - { - private const float marker_size = 10; - - /// - /// Invoked when this requires the current drag rectangle. - /// - public Func GetDragRectangle; - - /// - /// Invoked when this wants to update the drag rectangle. - /// - public Action UpdateDragRectangle; - - /// - /// Invoked when this has finished updates to the drag rectangle. - /// - public Action FinishCapture; - - private Color4 normalColour; - private Color4 hoverColour; - - public Marker() - { - Size = new Vector2(marker_size); - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = normalColour = colours.Yellow; - hoverColour = colours.YellowDarker; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - var currentRectangle = GetDragRectangle(); - - float left = currentRectangle.Left; - float right = currentRectangle.Right; - float top = currentRectangle.Top; - float bottom = currentRectangle.Bottom; - - // Apply modifications to the capture rectangle - if ((Anchor & Anchor.y0) > 0) - top += state.Mouse.Delta.Y; - else if ((Anchor & Anchor.y2) > 0) - bottom += state.Mouse.Delta.Y; - - if ((Anchor & Anchor.x0) > 0) - left += state.Mouse.Delta.X; - else if ((Anchor & Anchor.x2) > 0) - right += state.Mouse.Delta.X; - - UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - FinishCapture(); - return true; - } - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 100); - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(normalColour, 100); - } - } -} +// 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; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents a marker visible on the border of a which exposes + /// properties that are used to resize a . + /// + public class Handle : CompositeDrawable + { + private const float marker_size = 10; + + /// + /// Invoked when this requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when this wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when this has finished updates to the drag rectangle. + /// + public Action FinishCapture; + + private Color4 normalColour; + private Color4 hoverColour; + + public Handle() + { + Size = new Vector2(marker_size); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = normalColour = colours.Yellow; + hoverColour = colours.YellowDarker; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + var currentRectangle = GetDragRectangle(); + + float left = currentRectangle.Left; + float right = currentRectangle.Right; + float top = currentRectangle.Top; + float bottom = currentRectangle.Bottom; + + // Apply modifications to the capture rectangle + if ((Anchor & Anchor.y0) > 0) + top += state.Mouse.Delta.Y; + else if ((Anchor & Anchor.y2) > 0) + bottom += state.Mouse.Delta.Y; + + if ((Anchor & Anchor.x0) > 0) + left += state.Mouse.Delta.X; + else if ((Anchor & Anchor.x2) > 0) + right += state.Mouse.Delta.X; + + UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + FinishCapture(); + return true; + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(normalColour, 100); + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs similarity index 74% rename from osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs index e222583348..f6777908d7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/MarkerContainer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs @@ -11,77 +11,77 @@ using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.Edit.Layers.Selection { /// - /// A that has s around its border. + /// A that has s around its border. /// - public class MarkerContainer : CompositeDrawable + public class HandleContainer : CompositeDrawable { /// - /// Invoked when a requires the current drag rectangle. + /// Invoked when a requires the current drag rectangle. /// public Func GetDragRectangle; /// - /// Invoked when a wants to update the drag rectangle. + /// Invoked when a wants to update the drag rectangle. /// public Action UpdateDragRectangle; /// - /// Invoked when a has finished updates to the drag rectangle. + /// Invoked when a has finished updates to the drag rectangle. /// public Action FinishCapture; - public MarkerContainer() + public HandleContainer() { InternalChildren = new Drawable[] { - new Marker + new Handle { Anchor = Anchor.TopLeft, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.TopCentre, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.TopRight, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.BottomLeft, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.CentreRight, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.BottomRight, Origin = Anchor.Centre }, - new Marker + new Handle { Anchor = Anchor.BottomCentre, Origin = Anchor.Centre }, - new CentreMarker + new OriginHandle { Anchor = Anchor.Centre, Origin = Anchor.Centre } }; - InternalChildren.OfType().ForEach(m => + InternalChildren.OfType().ForEach(m => { m.GetDragRectangle = () => GetDragRectangle(); m.UpdateDragRectangle = r => UpdateDragRectangle(r); diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index 6ea2778a9e..ce8f8b7ded 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private readonly Container borderMask; private readonly Drawable background; - private readonly MarkerContainer markers; + private readonly HandleContainer handles; private Color4 captureFinishedColour; @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection }, } }, - markers = new MarkerContainer + handles = new HandleContainer { RelativeSizeAxes = Axes.Both, Alpha = 0, @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // Transform into markers to let the user modify the drag selection further. background.Delay(50).FadeOut(200); - markers.FadeIn(200); + handles.FadeIn(200); Selection.Value = new SelectionInfo { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs b/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs similarity index 86% rename from osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs index 0ed7339134..8326ebeeac 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CentreMarker.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs @@ -11,14 +11,14 @@ using OpenTK; namespace osu.Game.Rulesets.Edit.Layers.Selection { /// - /// Represents the centre of a . + /// Represents the origin of a . /// - public class CentreMarker : CompositeDrawable + public class OriginHandle : CompositeDrawable { private const float marker_size = 10; private const float line_width = 2; - public CentreMarker() + public OriginHandle() { Size = new Vector2(marker_size); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 514f91f80a..cf095b17a3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -307,10 +307,10 @@ - + - - + + From 5493493f828ba84235d8e138cecc39bca0cff683 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:24:41 +0900 Subject: [PATCH 173/239] FinishCapture -> FinishDrag from Handles --- osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs | 4 ++-- osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs | 4 ++-- .../Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs index e93fe886e1..1c6ddf4af0 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// Invoked when this has finished updates to the drag rectangle. /// - public Action FinishCapture; + public Action FinishDrag; private Color4 normalColour; private Color4 hoverColour; @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { - FinishCapture(); + FinishDrag(); return true; } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs index f6777908d7..22d993e7cd 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// Invoked when a has finished updates to the drag rectangle. /// - public Action FinishCapture; + public Action FinishDrag; public HandleContainer() { @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { m.GetDragRectangle = () => GetDragRectangle(); m.UpdateDragRectangle = r => UpdateDragRectangle(r); - m.FinishCapture = () => FinishCapture(); + m.FinishDrag = () => FinishDrag(); }); } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index ce8f8b7ded..13405e3054 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection updateDragRectangle(r); BeginCapture(); }, - FinishCapture = FinishCapture + FinishDrag = FinishCapture } }; } From fabf1bf60aa275d4748f894fdef4d18ae132adc4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 18 Dec 2017 19:31:03 +0900 Subject: [PATCH 174/239] Make the captured objects not update for now Since this is not the intended functionality. --- .../Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index 13405e3054..6f73d6b916 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -70,11 +70,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both, Alpha = 0, GetDragRectangle = () => dragRectangle, - UpdateDragRectangle = r => - { - updateDragRectangle(r); - BeginCapture(); - }, + UpdateDragRectangle = updateDragRectangle, FinishDrag = FinishCapture } }; From 051a3c4d473aa7d3716363c3cbde2afbdb6201ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 21:51:00 +0900 Subject: [PATCH 175/239] Fix line endings --- .../Rulesets/Edit/Layers/Selection/Handle.cs | 210 +++++++++--------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs index 1c6ddf4af0..2982a68b3b 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs @@ -1,105 +1,105 @@ -// 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; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; -using osu.Game.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// Represents a marker visible on the border of a which exposes - /// properties that are used to resize a . - /// - public class Handle : CompositeDrawable - { - private const float marker_size = 10; - - /// - /// Invoked when this requires the current drag rectangle. - /// - public Func GetDragRectangle; - - /// - /// Invoked when this wants to update the drag rectangle. - /// - public Action UpdateDragRectangle; - - /// - /// Invoked when this has finished updates to the drag rectangle. - /// - public Action FinishDrag; - - private Color4 normalColour; - private Color4 hoverColour; - - public Handle() - { - Size = new Vector2(marker_size); - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = normalColour = colours.Yellow; - hoverColour = colours.YellowDarker; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - var currentRectangle = GetDragRectangle(); - - float left = currentRectangle.Left; - float right = currentRectangle.Right; - float top = currentRectangle.Top; - float bottom = currentRectangle.Bottom; - - // Apply modifications to the capture rectangle - if ((Anchor & Anchor.y0) > 0) - top += state.Mouse.Delta.Y; - else if ((Anchor & Anchor.y2) > 0) - bottom += state.Mouse.Delta.Y; - - if ((Anchor & Anchor.x0) > 0) - left += state.Mouse.Delta.X; - else if ((Anchor & Anchor.x2) > 0) - right += state.Mouse.Delta.X; - - UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - FinishDrag(); - return true; - } - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 100); - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(normalColour, 100); - } - } -} +// 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; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Graphics; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + /// + /// Represents a marker visible on the border of a which exposes + /// properties that are used to resize a . + /// + public class Handle : CompositeDrawable + { + private const float marker_size = 10; + + /// + /// Invoked when this requires the current drag rectangle. + /// + public Func GetDragRectangle; + + /// + /// Invoked when this wants to update the drag rectangle. + /// + public Action UpdateDragRectangle; + + /// + /// Invoked when this has finished updates to the drag rectangle. + /// + public Action FinishDrag; + + private Color4 normalColour; + private Color4 hoverColour; + + public Handle() + { + Size = new Vector2(marker_size); + + InternalChild = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = normalColour = colours.Yellow; + hoverColour = colours.YellowDarker; + } + + protected override bool OnDragStart(InputState state) => true; + + protected override bool OnDrag(InputState state) + { + var currentRectangle = GetDragRectangle(); + + float left = currentRectangle.Left; + float right = currentRectangle.Right; + float top = currentRectangle.Top; + float bottom = currentRectangle.Bottom; + + // Apply modifications to the capture rectangle + if ((Anchor & Anchor.y0) > 0) + top += state.Mouse.Delta.Y; + else if ((Anchor & Anchor.y2) > 0) + bottom += state.Mouse.Delta.Y; + + if ((Anchor & Anchor.x0) > 0) + left += state.Mouse.Delta.X; + else if ((Anchor & Anchor.x2) > 0) + right += state.Mouse.Delta.X; + + UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); + return true; + } + + protected override bool OnDragEnd(InputState state) + { + FinishDrag(); + return true; + } + + protected override bool OnHover(InputState state) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(InputState state) + { + this.FadeColour(normalColour, 100); + } + } +} From c8afc553cbc5b9d17282389416343578a3db9636 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Dec 2017 23:55:34 +0900 Subject: [PATCH 176/239] Fix profile header not correctly being masked Closes #1592. --- osu.Game/Overlays/Profile/ProfileHeader.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4e19b3153d..4ddd6c498e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -53,6 +53,7 @@ namespace osu.Game.Overlays.Profile { RelativeSizeAxes = Axes.X, Height = cover_height, + Masking = true, Children = new Drawable[] { new Box @@ -324,8 +325,7 @@ namespace osu.Game.Overlays.Profile FillMode = FillMode.Fill, OnLoadComplete = d => d.FadeInFromZero(200), Depth = float.MaxValue, - }, - coverContainer.Add); + }, coverContainer.Add); if (user.IsSupporter) supporterTag.Show(); @@ -514,7 +514,7 @@ namespace osu.Game.Overlays.Profile { set { - if(value != null) + if (value != null) content.Action = () => Process.Start(value); } } From 6ac33e3c007b91e89dfd1f4b5cadb9273a156168 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Mon, 18 Dec 2017 17:04:35 +0100 Subject: [PATCH 177/239] made DeleteAll-button pink --- .../Settings/DangerousSettingsButton.cs | 23 +++++++++++++++++++ .../Sections/Maintenance/GeneralSettings.cs | 2 +- osu.Game/osu.Game.csproj | 3 ++- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Overlays/Settings/DangerousSettingsButton.cs diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs new file mode 100644 index 0000000000..7c324658a3 --- /dev/null +++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs @@ -0,0 +1,23 @@ +// 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.Game.Graphics; + +namespace osu.Game.Overlays.Settings +{ + /// + /// A with pink colours to mark dangerous/destructive actions. + /// + public class DangerousSettingsButton : SettingsButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + + Triangles.ColourDark = colours.PinkDark; + Triangles.ColourLight = colours.PinkLight; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 2c554a7e6b..1f389a22a1 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance .ContinueWith(t => Schedule(() => importButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); } }, - deleteButton = new SettingsButton + deleteButton = new DangerousSettingsButton { Text = "Delete ALL beatmaps", Action = () => diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index adc422857b..1174a1c76d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -270,6 +270,7 @@ + @@ -856,4 +857,4 @@ - + \ No newline at end of file From dfd532ed4409eee59159252d483bc96465c5bb19 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Mon, 18 Dec 2017 17:28:55 +0100 Subject: [PATCH 178/239] Updated submodule osu-framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 797a351db2..5da6990a8e 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 797a351db2e852fef5296453641ffbf6b2f6dc11 +Subproject commit 5da6990a8e68dea852495950996e1362a293dbd5 From 3644eda9a989c9dfab62311d56d64ec321a16c5b Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 19 Dec 2017 11:34:23 +0100 Subject: [PATCH 179/239] Changed notification from "installed" to "imported" on beatmap download --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 93ceefa74b..a1ad708304 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -248,7 +248,7 @@ namespace osu.Game.Beatmaps var downloadNotification = new ProgressNotification { - CompletionText = $"Installed {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", + CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", }; From b91090a4e16670cd97bac454aa01c5fadb029927 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 19 Dec 2017 16:51:36 +0100 Subject: [PATCH 180/239] Reverted update notification to original implementation --- osu.Desktop/Overlays/VersionManager.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index 94c09c2624..9e13003c3f 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -156,11 +156,7 @@ namespace osu.Desktop.Overlays if (notification == null) { - notification = new UpdateProgressNotification - { - CompletionText = "Successfully updated the game!", - State = ProgressNotificationState.Active - }; + notification = new UpdateProgressNotification { State = ProgressNotificationState.Active }; Schedule(() => notificationOverlay.Post(notification)); } From 63dce59c8c6f2ac2273d385cc0a20f981ceb2e44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 20:25:57 +0900 Subject: [PATCH 181/239] Throw an exception if we try and retrieve local scores online --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 4379daa47b..c32d8045ab 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -36,6 +36,9 @@ namespace osu.Game.Online.API.Requests if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (scope == LeaderboardScope.Local) + throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); + this.beatmap = beatmap; this.scope = scope; this.ruleset = ruleset; From 1b91f24044807d7be61d80a39f4e67e9cb061fb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 20:33:16 +0900 Subject: [PATCH 182/239] Simplify scope logic --- .../Online/API/Requests/GetScoresRequest.cs | 17 +---------------- .../Screens/Select/Leaderboards/Leaderboard.cs | 2 +- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index c32d8045ab..0d9812374a 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -56,22 +56,7 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - switch(scope) - { - default: - case LeaderboardScope.Global: - req.AddParameter(@"type", @"global"); - break; - - case LeaderboardScope.Friends: - req.AddParameter(@"type", @"friend"); - break; - - case LeaderboardScope.Country: - req.AddParameter(@"type", @"country"); - break; - } - + req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); req.AddParameter(@"mode", ruleset?.ShortName ?? @"osu"); return req; diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 36dc254792..decd966fa1 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -355,6 +355,6 @@ namespace osu.Game.Screens.Select.Leaderboards Local, Country, Global, - Friends, + Friend, } } From c871a25dfa53dbd1a6f6814efba1c4e4bcb2d4f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 20:41:01 +0900 Subject: [PATCH 183/239] Remove unnecessary constructure and make ruleset required --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 14 ++------------ osu.Game/Tests/Visual/TestCasePerformancePoints.cs | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 0d9812374a..ebd00e8505 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -21,16 +21,6 @@ namespace osu.Game.Online.API.Requests private readonly LeaderboardScope scope; private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap) - { - if (!beatmap.OnlineBeatmapID.HasValue) - throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); - - this.beatmap = beatmap; - - Success += onSuccess; - } - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) @@ -41,7 +31,7 @@ namespace osu.Game.Online.API.Requests this.beatmap = beatmap; this.scope = scope; - this.ruleset = ruleset; + this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); Success += onSuccess; } @@ -57,7 +47,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); - req.AddParameter(@"mode", ruleset?.ShortName ?? @"osu"); + req.AddParameter(@"mode", ruleset.ShortName); return req; } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 6da14e9b12..f71bece279 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -244,7 +244,7 @@ namespace osu.Game.Tests.Visual if (!api.IsLoggedIn) return; - lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo); + lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset); lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap))); api.Queue(lastRequest); } From 3292ef33fd72f3b3ae81bf8bd4487a9cc951efc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 21:06:33 +0900 Subject: [PATCH 184/239] Fix test edge cases --- osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 2 +- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 52daf95810..62aa5a79b3 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual private void networkFailure() { - leaderboard.Beatmap = new BeatmapInfo(); + leaderboard.Beatmap = new BeatmapInfo { Ruleset = rulesets.GetRuleset(0) }; } private class FailableLeaderboard : Leaderboard diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index decd966fa1..2d9cec4cc0 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -215,7 +215,7 @@ namespace osu.Game.Screens.Select.Leaderboards return; } - getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value, Scope); + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest.Success += r => { Scores = r.Scores; From 020d272636ef3aaac631746dace25c4ce2a27a8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 21:47:45 +0900 Subject: [PATCH 185/239] Make all TestCases public --- osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs | 2 +- osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs | 2 +- osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs | 2 +- osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs | 2 +- osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs | 2 +- osu.Game.Tests/Visual/TestCaseBreakOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseButtonSystem.cs | 2 +- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 2 +- osu.Game.Tests/Visual/TestCaseContextMenu.cs | 2 +- osu.Game.Tests/Visual/TestCaseDialogOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseDrawableRoom.cs | 2 +- osu.Game.Tests/Visual/TestCaseDrawings.cs | 2 +- osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs | 2 +- osu.Game.Tests/Visual/TestCaseGamefield.cs | 2 +- osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseGraph.cs | 2 +- osu.Game.Tests/Visual/TestCaseHistoricalSection.cs | 2 +- osu.Game.Tests/Visual/TestCaseKeyCounter.cs | 2 +- osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 2 +- osu.Game.Tests/Visual/TestCaseMedalOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseMods.cs | 2 +- osu.Game.Tests/Visual/TestCaseMusicController.cs | 2 +- osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs | 2 +- osu.Game.Tests/Visual/TestCasePlaySongSelect.cs | 2 +- osu.Game.Tests/Visual/TestCaseReplay.cs | 2 +- osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs | 2 +- osu.Game.Tests/Visual/TestCaseResults.cs | 2 +- osu.Game.Tests/Visual/TestCaseRoomInspector.cs | 2 +- osu.Game.Tests/Visual/TestCaseScoreCounter.cs | 2 +- osu.Game.Tests/Visual/TestCaseSettings.cs | 2 +- osu.Game.Tests/Visual/TestCaseSkipButton.cs | 2 +- osu.Game.Tests/Visual/TestCaseSongProgress.cs | 2 +- osu.Game.Tests/Visual/TestCaseStoryboard.cs | 2 +- osu.Game.Tests/Visual/TestCaseTextAwesome.cs | 2 +- osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs | 2 +- osu.Game.Tests/Visual/TestCaseUserPanel.cs | 2 +- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 2 +- osu.Game.Tests/Visual/TestCaseUserRanks.cs | 2 +- osu.Game.Tests/Visual/TestCaseWaveform.cs | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs index daa3e12800..8217265e3d 100644 --- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseCatcherArea : OsuTestCase + public class TestCaseCatcherArea : OsuTestCase { private RulesetInfo catchRuleset; private TestCatcherArea catcherArea; diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs index 484ce77a16..03886f5784 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseManiaHitObjects : OsuTestCase + public class TestCaseManiaHitObjects : OsuTestCase { public TestCaseManiaHitObjects() { diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs index aab784f177..1932038411 100644 --- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseManiaPlayfield : OsuTestCase + public class TestCaseManiaPlayfield : OsuTestCase { private const double start_time = 500; private const double duration = 500; diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs index 99526b64ee..c4932d7803 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitObjects.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseHitObjects : OsuTestCase + public class TestCaseHitObjects : OsuTestCase { private FramedClock framedClock; diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs index 555f9bb0da..bca4806108 100644 --- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] [Ignore("getting CI working")] - internal class TestCaseTaikoPlayfield : OsuTestCase + public class TestCaseTaikoPlayfield : OsuTestCase { private const double default_duration = 1000; private const float scroll_time = 1000; diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index 18555574ba..566359b56b 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs @@ -17,7 +17,7 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatSyncedContainer : OsuTestCase + public class TestCaseBeatSyncedContainer : OsuTestCase { private readonly MusicController mc; diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 5758e893a6..66af72dad9 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -17,7 +17,7 @@ using osu.Game.Screens.Select.Filter; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatmapCarousel : OsuTestCase + public class TestCaseBeatmapCarousel : OsuTestCase { private TestBeatmapCarousel carousel; diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs index 1a64994d0e..a3c1ad307f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual { [TestFixture] [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - internal class TestCaseBeatmapDetailArea : OsuTestCase + public class TestCaseBeatmapDetailArea : OsuTestCase { public TestCaseBeatmapDetailArea() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs index 248ec6d43d..62aced7423 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect beatmap details")] - internal class TestCaseBeatmapDetails : OsuTestCase + public class TestCaseBeatmapDetails : OsuTestCase { public TestCaseBeatmapDetails() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 4ac7469a5a..0168cedc86 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -12,7 +12,7 @@ using osu.Game.Screens.Select; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatmapInfoWedge : OsuTestCase + public class TestCaseBeatmapInfoWedge : OsuTestCase { private BeatmapManager beatmaps; private readonly Random random; diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs index e114fac96e..12f1cf29ee 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs @@ -10,7 +10,7 @@ using OpenTK.Input; namespace osu.Game.Tests.Visual { [Description("bottom beatmap details")] - internal class TestCaseBeatmapOptionsOverlay : OsuTestCase + public class TestCaseBeatmapOptionsOverlay : OsuTestCase { public TestCaseBeatmapOptionsOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs index c24e13b7fb..6a2161809f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseBeatmapSetOverlay : OsuTestCase + public class TestCaseBeatmapSetOverlay : OsuTestCase { private readonly BeatmapSetOverlay overlay; diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs index 50abd11e79..7760bffdaf 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs @@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual { - internal class TestCaseBreadcrumbs : OsuTestCase + public class TestCaseBreadcrumbs : OsuTestCase { public TestCaseBreadcrumbs() { diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs index dcb3b74654..328bbaedba 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; namespace osu.Game.Tests.Visual { - internal class TestCaseBreakOverlay : OsuTestCase + public class TestCaseBreakOverlay : OsuTestCase { private readonly BreakOverlay breakOverlay; diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs index d260de69f1..9d796617de 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs @@ -9,7 +9,7 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { - internal class TestCaseButtonSystem : OsuTestCase + public class TestCaseButtonSystem : OsuTestCase { public TestCaseButtonSystem() { diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 85ee224a5e..5bfa6c1de4 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -8,7 +8,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { [Description("Testing chat api and overlay")] - internal class TestCaseChatDisplay : OsuTestCase + public class TestCaseChatDisplay : OsuTestCase { public TestCaseChatDisplay() { diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs index 6f5cb398d7..45e6464149 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs @@ -13,7 +13,7 @@ using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { - internal class TestCaseContextMenu : OsuTestCase + public class TestCaseContextMenu : OsuTestCase { private const int start_time = 0; private const int duration = 1000; diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs index f1aba908f0..6dd84b204f 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Tests.Visual { - internal class TestCaseDialogOverlay : OsuTestCase + public class TestCaseDialogOverlay : OsuTestCase { public TestCaseDialogOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs index a51cf8ca95..3bbe6de921 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs @@ -12,7 +12,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseDrawableRoom : OsuTestCase + public class TestCaseDrawableRoom : OsuTestCase { private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs index e5692b29de..a6ef3b309a 100644 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ b/osu.Game.Tests/Visual/TestCaseDrawings.cs @@ -9,7 +9,7 @@ using osu.Game.Screens.Tournament.Teams; namespace osu.Game.Tests.Visual { [Description("for tournament use")] - internal class TestCaseDrawings : OsuTestCase + public class TestCaseDrawings : OsuTestCase { public TestCaseDrawings() { diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index c35355aefd..01371ad78c 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -14,7 +14,7 @@ using osu.Framework.Configuration; namespace osu.Game.Tests.Visual { - internal class TestCaseEditorSummaryTimeline : OsuTestCase + public class TestCaseEditorSummaryTimeline : OsuTestCase { private const int length = 60000; private readonly Random random; diff --git a/osu.Game.Tests/Visual/TestCaseGamefield.cs b/osu.Game.Tests/Visual/TestCaseGamefield.cs index 0d8f4cb5f7..9dce0cfe01 100644 --- a/osu.Game.Tests/Visual/TestCaseGamefield.cs +++ b/osu.Game.Tests/Visual/TestCaseGamefield.cs @@ -5,7 +5,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Tests.Visual { - internal class TestCaseGamefield : OsuTestCase + public class TestCaseGamefield : OsuTestCase { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs index 8389037a71..bd5772d3bb 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs @@ -15,7 +15,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { [Description("player pause/fail screens")] - internal class TestCaseGameplayMenuOverlay : OsuTestCase + public class TestCaseGameplayMenuOverlay : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/TestCaseGraph.cs index fb1a3ef3f6..4d3fd267db 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseGraph.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseGraph : OsuTestCase + public class TestCaseGraph : OsuTestCase { public TestCaseGraph() { diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs index 17d45748ff..c60b21fa20 100644 --- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs @@ -13,7 +13,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseHistoricalSection : OsuTestCase + public class TestCaseHistoricalSection : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs index df122b7132..7fddc92ee6 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs @@ -8,7 +8,7 @@ using OpenTK.Input; namespace osu.Game.Tests.Visual { - internal class TestCaseKeyCounter : OsuTestCase + public class TestCaseKeyCounter : OsuTestCase { public TestCaseKeyCounter() { diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 9d6fb3a4ec..cc8923976c 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -11,7 +11,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { [Description("PlaySongSelect leaderboard")] - internal class TestCaseLeaderboard : OsuTestCase + public class TestCaseLeaderboard : OsuTestCase { private readonly Leaderboard leaderboard; diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs index fbee27668c..bd69719f10 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs @@ -9,7 +9,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseMedalOverlay : OsuTestCase + public class TestCaseMedalOverlay : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 5270ac0dc9..042f69f676 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -12,7 +12,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { [Description("mod select and icon display")] - internal class TestCaseMods : OsuTestCase + public class TestCaseMods : OsuTestCase { private ModSelectOverlay modSelect; private ModDisplay modDisplay; diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs index 3c544bb968..16f2fab321 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseMusicController : OsuTestCase + public class TestCaseMusicController : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 3dca860909..f8079c2f2a 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -13,7 +13,7 @@ using osu.Game.Overlays.Notifications; namespace osu.Game.Tests.Visual { [TestFixture] - internal class TestCaseNotificationOverlay : OsuTestCase + public class TestCaseNotificationOverlay : OsuTestCase { private readonly NotificationOverlay manager; diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs index c3a755f3ca..179f17ab50 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs @@ -7,7 +7,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseOnScreenDisplay : OsuTestCase + public class TestCaseOnScreenDisplay : OsuTestCase { private FrameworkConfigManager config; private Bindable frameSyncMode; diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 7421546c43..6435df7c2c 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -19,7 +19,7 @@ using osu.Game.Tests.Platform; namespace osu.Game.Tests.Visual { - internal class TestCasePlaySongSelect : OsuTestCase + public class TestCasePlaySongSelect : OsuTestCase { private BeatmapManager manager; diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 62c8a64916..8e92696e8d 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseReplay : TestCasePlayer + public class TestCaseReplay : TestCasePlayer { protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) { diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs index badb98e6b7..d795b51d34 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Play.ReplaySettings; namespace osu.Game.Tests.Visual { - internal class TestCaseReplaySettingsOverlay : OsuTestCase + public class TestCaseReplaySettingsOverlay : OsuTestCase { public TestCaseReplaySettingsOverlay() { diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index f1bbb8fed6..d0c5aa4939 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseResults : OsuTestCase + public class TestCaseResults : OsuTestCase { private BeatmapManager beatmaps; diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs index 51b6ae8e50..4f167329a0 100644 --- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs +++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseRoomInspector : OsuTestCase + public class TestCaseRoomInspector : OsuTestCase { private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs index 5a04000900..8f82c5e7a9 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs @@ -10,7 +10,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseScoreCounter : OsuTestCase + public class TestCaseScoreCounter : OsuTestCase { public TestCaseScoreCounter() { diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 63d798cd53..13698b645f 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -5,7 +5,7 @@ using osu.Game.Overlays; namespace osu.Game.Tests.Visual { - internal class TestCaseSettings : OsuTestCase + public class TestCaseSettings : OsuTestCase { private readonly SettingsOverlay settings; diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index 40c8baaac8..296c10a980 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -5,7 +5,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseSkipButton : OsuTestCase + public class TestCaseSkipButton : OsuTestCase { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs index 1e6886cda9..fe534bc679 100644 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs @@ -10,7 +10,7 @@ using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual { - internal class TestCaseSongProgress : OsuTestCase + public class TestCaseSongProgress : OsuTestCase { private readonly SongProgress progress; private readonly SongProgressGraph graph; diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index 0a158f5662..aa9618b208 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -14,7 +14,7 @@ using OpenTK.Graphics; namespace osu.Game.Tests.Visual { - internal class TestCaseStoryboard : OsuTestCase + public class TestCaseStoryboard : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs index 37905a1883..e2b4914558 100644 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs @@ -10,7 +10,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseTextAwesome : OsuTestCase + public class TestCaseTextAwesome : OsuTestCase { public TestCaseTextAwesome() { diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs index bd5c10d147..866d1f56ef 100644 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs @@ -7,7 +7,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual { [Description("mostly back button")] - internal class TestCaseTwoLayerButton : OsuTestCase + public class TestCaseTwoLayerButton : OsuTestCase { public TestCaseTwoLayerButton() { diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs index 8d94a0c90f..31f9789093 100644 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs @@ -8,7 +8,7 @@ using OpenTK; namespace osu.Game.Tests.Visual { - internal class TestCaseUserPanel : OsuTestCase + public class TestCaseUserPanel : OsuTestCase { public TestCaseUserPanel() { diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 90daf1e996..697b84941c 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -8,7 +8,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual { - internal class TestCaseUserProfile : OsuTestCase + public class TestCaseUserProfile : OsuTestCase { public TestCaseUserProfile() { diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/TestCaseUserRanks.cs index c0c488673b..e7b8047882 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/TestCaseUserRanks.cs @@ -13,7 +13,7 @@ using System.Collections.Generic; namespace osu.Game.Tests.Visual { - internal class TestCaseUserRanks : OsuTestCase + public class TestCaseUserRanks : OsuTestCase { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index fc21b86c5d..4c6efd439f 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -15,7 +15,7 @@ using osu.Game.Screens.Edit.Screens.Compose.Timeline; namespace osu.Game.Tests.Visual { - internal class TestCaseWaveform : OsuTestCase + public class TestCaseWaveform : OsuTestCase { private readonly Bindable beatmapBacking = new Bindable(); From 6d471da459e63376f212aa3a17c1b3f408936775 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 21:50:45 +0900 Subject: [PATCH 186/239] Remove unnecessary workaround --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 2d9cec4cc0..68e135e5a4 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -52,8 +52,8 @@ namespace osu.Game.Screens.Select.Leaderboards scores = value; placeholderContainer.FadeOut(fade_duration); + scrollFlow?.FadeOut(fade_duration).Expire(); - scrollContainer.Clear(true); // scores stick around in scrollFlow in VisualTests without this for some reason scrollFlow = null; loading.Hide(); From 23f479984029ee5f1f2f91dcd486c833753edf29 Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 20 Dec 2017 20:00:52 +0530 Subject: [PATCH 187/239] Create placeholder classes instead of changing Children. - Add MessagePlaceholder - Use MessagePlacholder for when API is offline/user isn't a supporter - Remove unnecessary placeholderFlow field - Hook into API state changes --- .../Select/Leaderboards/Leaderboard.cs | 139 ++++++++++++------ 1 file changed, 97 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 68e135e5a4..44d4d2ff52 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -34,7 +34,6 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly ScrollContainer scrollContainer; private readonly Container placeholderContainer; - private readonly FillFlowContainer placeholderFlow; private FillFlowContainer scrollFlow; @@ -63,22 +62,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (!scores.Any()) { - placeholderFlow.Children = new Drawable[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_exclamation_circle, - Size = new Vector2(26), - Margin = new MarginPadding { Right = 10 }, - }, - new OsuSpriteText - { - Text = @"No records yet!", - TextSize = 22, - }, - }; - - placeholderContainer.FadeIn(fade_duration); + replacePlaceholder(new MessagePlaceholder(@"No records yet!")); return; } @@ -135,17 +119,9 @@ namespace osu.Game.Screens.Select.Leaderboards placeholderContainer = new Container { Alpha = 0, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - placeholderFlow = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - }, - }, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }, }; } @@ -181,6 +157,9 @@ namespace osu.Game.Screens.Select.Leaderboards if (osuGame != null) osuGame.Ruleset.ValueChanged += handleRulesetChange; + + if (api != null) + api.OnStateChange += handleApiStateChange; } protected override void Dispose(bool isDisposing) @@ -195,6 +174,23 @@ namespace osu.Game.Screens.Select.Leaderboards private void handleRulesetChange(RulesetInfo ruleset) => UpdateScores(); + private void handleApiStateChange(APIState oldState, APIState newState) + { + if (Scope == LeaderboardScope.Local) + // No need to respond to API state change while current scope is local + return; + + if (newState == APIState.Offline) + { + Scores = null; + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + return; + } + + if (newState == APIState.Online) + UpdateScores(); + } + protected virtual void UpdateScores() { if (!IsLoaded) return; @@ -215,6 +211,20 @@ namespace osu.Game.Screens.Select.Leaderboards return; } + if (!api.IsLoggedIn) + { + loading.Hide(); + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + return; + } + + if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + loading.Hide(); + replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); + return; + } + getScoresRequest = new GetScoresRequest(Beatmap, osuGame?.Ruleset.Value ?? Beatmap.Ruleset, Scope); getScoresRequest.Success += r => { @@ -230,24 +240,21 @@ namespace osu.Game.Screens.Select.Leaderboards if (e is OperationCanceledException) return; Scores = null; - placeholderFlow.Children = new Drawable[] + replacePlaceholder(new RetrievalFailurePlaceholder { - new RetryButton - { - Action = UpdateScores, - Margin = new MarginPadding { Right = 10 }, - }, - new OsuSpriteText - { - Anchor = Anchor.TopLeft, - Text = @"Couldn't retrieve scores!", - TextSize = 22, - }, - }; - placeholderContainer.FadeIn(fade_duration); + OnRetry = UpdateScores, + }); Logger.Error(e, @"Couldn't fetch beatmap scores!"); } + private void replacePlaceholder(Drawable placeholder) + { + placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint); + placeholderContainer.Clear(true); + placeholderContainer.Child = placeholder; + placeholderContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + } + protected override void Update() { base.Update(); @@ -277,6 +284,54 @@ namespace osu.Game.Screens.Select.Leaderboards } } + private class MessagePlaceholder : FillFlowContainer + { + public MessagePlaceholder(string message) + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new SpriteIcon + { + Icon = FontAwesome.fa_exclamation_circle, + Size = new Vector2(26), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Text = message, + TextSize = 22, + }, + }; + } + } + + private class RetrievalFailurePlaceholder : FillFlowContainer + { + public Action OnRetry; + + public RetrievalFailurePlaceholder() + { + Direction = FillDirection.Horizontal; + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new RetryButton + { + Action = () => OnRetry?.Invoke(), + Margin = new MarginPadding { Right = 10 }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopLeft, + Text = @"Couldn't retrieve scores!", + TextSize = 22, + }, + }; + } + } + private class RetryButton : BeatSyncedContainer { private readonly SpriteIcon icon; From c8c8b6810adc3fc7b1feb5435e1364a31bede4a7 Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 20 Dec 2017 20:11:48 +0530 Subject: [PATCH 188/239] Kill ugly retry button bounce. --- .../Select/Leaderboards/Leaderboard.cs | 40 ++----------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 44d4d2ff52..97e694a4b1 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -332,14 +332,12 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private class RetryButton : BeatSyncedContainer + private class RetryButton : OsuHoverContainer { private readonly SpriteIcon icon; public Action Action; - private OsuColour colours; - public RetryButton() { Height = 26; @@ -359,47 +357,15 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) - { - var duration = timingPoint.BeatLength / 2; - - icon.Animate( - i => i.MoveToY(-3, duration, Easing.Out), - i => i.ScaleTo(IsHovered ? 1.3f : 1.1f, duration, Easing.Out) - ).Then( - i => i.MoveToY(0, duration, Easing.In), - i => i.ScaleTo(IsHovered ? 1.4f : 1f, duration, Easing.In) - ); - } - - protected override bool OnHover(InputState state) - { - icon.ScaleTo(1.4f, 400, Easing.OutQuint); - return base.OnHover(state); - } - - protected override void OnHoverLost(InputState state) - { - icon.ClearTransforms(); - icon.ScaleTo(1f, 400, Easing.OutQuint); - base.OnHoverLost(state); - } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - icon.FadeColour(colours.Yellow, 400); + icon.ScaleTo(0.8f, 400, Easing.OutQuint); return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - icon.FadeColour(Color4.White, 400); + icon.ScaleTo(1, 400, Easing.OutElastic); return base.OnMouseUp(state, args); } } From 8d24a046410bfdf1d8003d61e3a04f86ad7e34db Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 20 Dec 2017 20:26:59 +0530 Subject: [PATCH 189/239] Remove unused usings. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 97e694a4b1..73b6cb0c53 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -23,8 +23,6 @@ using osu.Game.Graphics; using osu.Framework.Logging; using osu.Game.Rulesets; using osu.Framework.Input; -using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio.Track; namespace osu.Game.Screens.Select.Leaderboards { From 85dee3abac6b563ee647edb7bc0ddbfb85d6784d Mon Sep 17 00:00:00 2001 From: naoey Date: Wed, 20 Dec 2017 20:48:30 +0530 Subject: [PATCH 190/239] Increase GetScoresRequest timeout & leave existing scores when API dies. --- osu.Game/Online/API/Requests/GetScoresRequest.cs | 1 + osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index ebd00e8505..065c770738 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -46,6 +46,7 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); + req.Timeout = 30000; req.AddParameter(@"type", scope.ToString().ToLowerInvariant()); req.AddParameter(@"mode", ruleset.ShortName); diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 73b6cb0c53..1c14d1ad33 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -178,13 +178,6 @@ namespace osu.Game.Screens.Select.Leaderboards // No need to respond to API state change while current scope is local return; - if (newState == APIState.Offline) - { - Scores = null; - replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); - return; - } - if (newState == APIState.Online) UpdateScores(); } From ebc2ad55f8cebf1f30af4d24accaa0c8b52cd653 Mon Sep 17 00:00:00 2001 From: Aergwyn Date: Wed, 20 Dec 2017 19:05:23 +0100 Subject: [PATCH 191/239] greatly expanded tests for ModSelectOverlay --- osu.Game.Tests/Visual/TestCaseMods.cs | 167 ++++++++++++++++++++- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 40 ++--- 2 files changed, 181 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index 5270ac0dc9..6b308f766a 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -8,17 +8,25 @@ using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Screens.Play.HUD; using OpenTK; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using System.Linq; +using System.Collections.Generic; +using osu.Game.Rulesets.Osu; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using OpenTK.Graphics; namespace osu.Game.Tests.Visual { [Description("mod select and icon display")] internal class TestCaseMods : OsuTestCase { - private ModSelectOverlay modSelect; - private ModDisplay modDisplay; + private const string unranked_suffix = " (Unranked)"; private RulesetStore rulesets; - + private ModDisplay modDisplay; + private TestModSelectOverlay modSelect; [BackgroundDependencyLoader] private void load(RulesetStore rulesets) @@ -30,7 +38,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(modSelect = new ModSelectOverlay + Add(modSelect = new TestModSelectOverlay { RelativeSizeAxes = Axes.X, Origin = Anchor.BottomCentre, @@ -48,9 +56,156 @@ namespace osu.Game.Tests.Visual modDisplay.Current.BindTo(modSelect.SelectedMods); AddStep("Toggle", modSelect.ToggleVisibility); + AddStep("Hide", modSelect.Hide); + AddStep("Show", modSelect.Show); - foreach (var ruleset in rulesets.AvailableRulesets) - AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); + foreach (var rulesetInfo in rulesets.AvailableRulesets) + { + Ruleset ruleset = rulesetInfo.CreateInstance(); + AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo); + + switch (ruleset) { + case OsuRuleset or: + testOsuMods(or); + break; + } + } + } + + private void testOsuMods(OsuRuleset ruleset) + { + var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction); + var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease); + var assistMods = ruleset.GetModsFor(ModType.Special); + + var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail); + var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden); + var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime)); + var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot); + + testSingleMod(noFailMod); + testMultiMod(doubleTimeMod); + testIncompatibleMods(noFailMod, autoPilotMod); + testDeselectAll(easierMods.Where(m => !(m is MultiMod))); + testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour); + testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour); + testMultiplierTextUnranked(autoPilotMod); + } + + private void testSingleMod(Mod mod) + { + selectNext(mod); + checkSelected(mod); + + selectPrevious(mod); + checkNotSelected(mod); + + selectNext(mod); + selectNext(mod); + checkNotSelected(mod); + + selectPrevious(mod); + selectPrevious(mod); + checkNotSelected(mod); + } + + private void testMultiMod(MultiMod multiMod) + { + foreach (var mod in multiMod.Mods) + { + selectNext(mod); + checkSelected(mod); + } + + for (int index = multiMod.Mods.Length - 1; index >= 0; index--) + selectPrevious(multiMod.Mods[index]); + + foreach (var mod in multiMod.Mods) + checkNotSelected(mod); + } + + private void testIncompatibleMods(Mod modA, Mod modB) + { + selectNext(modA); + checkSelected(modA); + checkNotSelected(modB); + + selectNext(modB); + checkSelected(modB); + checkNotSelected(modA); + + selectPrevious(modB); + checkNotSelected(modA); + checkNotSelected(modB); + } + + private void testDeselectAll(IEnumerable mods) + { + foreach (var mod in mods) + selectNext(mod); + + AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any()); + AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke); + AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any()); + } + + private void testMultiplierTextColour(Mod mod, Color4 colour) + { + checkLabelColor(Color4.White); + selectNext(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(colour); + selectPrevious(mod); + AddWaitStep(1, "wait for changing colour"); + checkLabelColor(Color4.White); + } + + private void testMultiplierTextUnranked(Mod mod) + { + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectNext(mod); + AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + selectPrevious(mod); + AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + } + + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext()); + + private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious()); + + private void checkSelected(Mod mod) + { + AddAssert($"check {mod.Name} is selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected; + }); + } + + private void checkNotSelected(Mod mod) + { + AddAssert($"check {mod.Name} is not selected", () => + { + var button = modSelect.GetModButton(mod); + return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType(); + }); + } + + private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color); + + private class TestModSelectOverlay : ModSelectOverlay + { + public ModButton GetModButton(Mod mod) + { + var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type); + return section.ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())); + } + + public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; + public new TriangleButton DeselectAllButton => base.DeselectAllButton; + + public new Color4 LowMultiplierColour => base.LowMultiplierColour; + public new Color4 HighMultiplierColour => base.HighMultiplierColour; } } } diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 82e8b58a30..e3be23ebc6 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -25,12 +25,13 @@ namespace osu.Game.Overlays.Mods { private const float content_width = 0.8f; - private Color4 lowMultiplierColour, highMultiplierColour; + protected Color4 LowMultiplierColour, HighMultiplierColour; - private readonly OsuSpriteText multiplierLabel; + protected readonly TriangleButton DeselectAllButton; + protected readonly OsuSpriteText MultiplierLabel; private readonly FillFlowContainer footerContainer; - private readonly FillFlowContainer modSectionsContainer; + protected readonly FillFlowContainer ModSectionsContainer; public readonly Bindable> SelectedMods = new Bindable>(); @@ -40,7 +41,7 @@ namespace osu.Game.Overlays.Mods { var instance = newRuleset.CreateInstance(); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.Mods = instance.GetModsFor(section.ModType); refreshSelectedMods(); } @@ -48,8 +49,8 @@ namespace osu.Game.Overlays.Mods [BackgroundDependencyLoader(permitNulls: true)] private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) { - lowMultiplierColour = colours.Red; - highMultiplierColour = colours.Green; + LowMultiplierColour = colours.Red; + HighMultiplierColour = colours.Green; if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -67,7 +68,7 @@ namespace osu.Game.Overlays.Mods footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine); footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine); section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine); @@ -82,7 +83,7 @@ namespace osu.Game.Overlays.Mods footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint); - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) { section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint); section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint); @@ -92,16 +93,15 @@ namespace osu.Game.Overlays.Mods public void DeselectAll() { - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectAll(); - refreshSelectedMods(); } public void DeselectTypes(Type[] modTypes) { if (modTypes.Length == 0) return; - foreach (ModSection section in modSectionsContainer.Children) + foreach (ModSection section in ModSectionsContainer.Children) section.DeselectTypes(modTypes); } @@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Mods private void refreshSelectedMods() { - SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); double multiplier = 1.0; bool ranked = true; @@ -129,16 +129,16 @@ namespace osu.Game.Overlays.Mods // 1.05x // 1.20x - multiplierLabel.Text = $"{multiplier:N2}x"; + MultiplierLabel.Text = $"{multiplier:N2}x"; if (!ranked) - multiplierLabel.Text += " (Unranked)"; + MultiplierLabel.Text += " (Unranked)"; if (multiplier > 1.0) - multiplierLabel.FadeColour(highMultiplierColour, 200); + MultiplierLabel.FadeColour(HighMultiplierColour, 200); else if (multiplier < 1.0) - multiplierLabel.FadeColour(lowMultiplierColour, 200); + MultiplierLabel.FadeColour(LowMultiplierColour, 200); else - multiplierLabel.FadeColour(Color4.White, 200); + MultiplierLabel.FadeColour(Color4.White, 200); } public ModSelectOverlay() @@ -241,7 +241,7 @@ namespace osu.Game.Overlays.Mods }, }, // Body - modSectionsContainer = new FillFlowContainer + ModSectionsContainer = new FillFlowContainer { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, @@ -303,7 +303,7 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - new TriangleButton + DeselectAllButton = new TriangleButton { Width = 180, Text = "Deselect All", @@ -323,7 +323,7 @@ namespace osu.Game.Overlays.Mods Top = 5 } }, - multiplierLabel = new OsuSpriteText + MultiplierLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", TextSize = 30, From 2ff351c6cb3f050a7d3c76000193eb350faa1c89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 03:10:09 +0900 Subject: [PATCH 192/239] Show retrieval failure when OnlineBeatmapID is missing --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 1c14d1ad33..792ee8211a 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -191,7 +191,14 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; - if (api == null || Beatmap?.OnlineBeatmapID == null) return; + if (api == null || Beatmap?.OnlineBeatmapID == null) + { + replacePlaceholder(new RetrievalFailurePlaceholder + { + OnRetry = UpdateScores, + }); + return; + } loading.Show(); From afcb9912e4fe28cbe85ceac71770587447434524 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 03:11:44 +0900 Subject: [PATCH 193/239] Reorder API / logged in checks to make more sense --- .../Select/Leaderboards/Leaderboard.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 792ee8211a..9a6e88b0a6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -191,7 +191,20 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; - if (api == null || Beatmap?.OnlineBeatmapID == null) + if (Scope == LeaderboardScope.Local) + { + // TODO: get local scores from wherever here. + Scores = Enumerable.Empty(); + return; + } + + if (api?.IsLoggedIn != true) + { + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + return; + } + + if (Beatmap?.OnlineBeatmapID == null) { replacePlaceholder(new RetrievalFailurePlaceholder { @@ -202,20 +215,6 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Show(); - if (Scope == LeaderboardScope.Local) - { - // TODO: get local scores from wherever here. - Scores = Enumerable.Empty(); - return; - } - - if (!api.IsLoggedIn) - { - loading.Hide(); - replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); - return; - } - if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) { loading.Hide(); From 048bfd7cf09a8fe416172210581d83d452337539 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 13:32:27 +0900 Subject: [PATCH 194/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 5da6990a8e..28fbd0711c 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 5da6990a8e68dea852495950996e1362a293dbd5 +Subproject commit 28fbd0711c09d3b06b51fc728b025f83ded2f0f8 From f6552da406e7f0c56d2e84fcf7e310ee33e62853 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 13:44:18 +0900 Subject: [PATCH 195/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 9cd6968a8c..28fbd0711c 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 9cd6968a8c4d27415808f5e770d24fec5a44485f +Subproject commit 28fbd0711c09d3b06b51fc728b025f83ded2f0f8 From bfa4f1a2c3ad203ef377d165091ce73bced1de68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 13:59:03 +0900 Subject: [PATCH 196/239] Apply changes in line with master changes --- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 4 ++-- osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs | 23 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 5a66a7047d..7886383725 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource(filename)) using (var sr = new StreamReader(stream)) { - var legacyDecoded = new OsuLegacyDecoder().Decode(sr); + var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr); using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return (legacyDecoded, new OsuJsonDecoder().Decode(sr2)); + return (legacyDecoded, new OsuJsonDecoder().DecodeBeatmap(sr2)); } } } diff --git a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs index d00cbbb8fb..e2d7414013 100644 --- a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs @@ -3,34 +3,33 @@ using System.IO; using osu.Game.IO.Serialization; +using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class OsuJsonDecoder : BeatmapDecoder + public class OsuJsonDecoder : Decoder { public static void Register() { AddDecoder("{"); } - protected override void ParseFile(StreamReader stream, Beatmap beatmap) + public override Decoder GetStoryboardDecoder() => this; + + protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap) { stream.BaseStream.Position = 0; stream.DiscardBufferedData(); - try - { - string fullText = stream.ReadToEnd(); - fullText.DeserializeInto(beatmap); - } - catch - { - // Temporary because storyboards are deserialized into beatmaps at the moment - // This try-catch shouldn't exist in the future - } + stream.ReadToEnd().DeserializeInto(beatmap); foreach (var hitObject in beatmap.HitObjects) hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); } + + protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard) + { + // throw new System.NotImplementedException(); + } } } From e8e5e8270bbf244719866f0af6bd26862344eb78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 14:19:25 +0900 Subject: [PATCH 197/239] Rename decoder --- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 6 +++--- .../Formats/{OsuJsonDecoder.cs => JsonBeatmapDecoder.cs} | 4 ++-- osu.Game/osu.Game.csproj | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game/Beatmaps/Formats/{OsuJsonDecoder.cs => JsonBeatmapDecoder.cs} (88%) diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 7886383725..8fd7645287 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Beatmaps.Formats /// /// Reads a .osu file first with a , serializes the resulting to JSON - /// and then deserializes the result back into a through an . + /// and then deserializes the result back into a through an . /// /// The .osu file to decode. /// The after being decoded by an . @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Beatmaps.Formats /// /// Reads a .osu file first with a , serializes the resulting to JSON - /// and then deserializes the result back into a through an . + /// and then deserializes the result back into a through an . /// /// The .osu file to decode. /// The after being decoded by an . @@ -168,7 +168,7 @@ namespace osu.Game.Tests.Beatmaps.Formats sw.Flush(); ms.Position = 0; - return (legacyDecoded, new OsuJsonDecoder().DecodeBeatmap(sr2)); + return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2)); } } } diff --git a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs similarity index 88% rename from osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs rename to osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index e2d7414013..ce6037b43e 100644 --- a/osu.Game/Beatmaps/Formats/OsuJsonDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -7,11 +7,11 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps.Formats { - public class OsuJsonDecoder : Decoder + public class JsonBeatmapDecoder : Decoder { public static void Register() { - AddDecoder("{"); + AddDecoder("{"); } public override Decoder GetStoryboardDecoder() => this; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5440cdc8f4..223dd77b04 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -263,7 +263,7 @@ - + From 22f8853f49de80e604e25c3684061a23cbe68e1b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 14:44:51 +0900 Subject: [PATCH 198/239] Serialize star difficulty for now --- osu.Game/Beatmaps/BeatmapInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8139aff6ef..1da3dc8a54 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -123,7 +123,6 @@ namespace osu.Game.Beatmaps [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } - public bool ShouldSerializeStarDifficulty() => false; public override string ToString() => $"{Metadata} [{Version}]"; From 58859f2ffb12b62396a7e909f2c22ed457fa994f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 15:34:00 +0900 Subject: [PATCH 199/239] Rework registration/instantiation of decoders to not rely on reflection --- osu.Game/Beatmaps/Formats/Decoder.cs | 16 +++++++------ .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 24 +++++++++---------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index e157150651..a6e2699262 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -10,11 +10,12 @@ namespace osu.Game.Beatmaps.Formats { public abstract class Decoder { - private static readonly Dictionary decoders = new Dictionary(); + private static readonly Dictionary> decoders = new Dictionary>(); static Decoder() { LegacyDecoder.Register(); + JsonBeatmapDecoder.Register(); } /// @@ -33,17 +34,18 @@ namespace osu.Game.Beatmaps.Formats if (line == null || !decoders.ContainsKey(line)) throw new IOException(@"Unknown file format"); - return (Decoder)Activator.CreateInstance(decoders[line], line); + + return decoders[line](line); } /// - /// Adds the to the list of and decoder. + /// Registers an instantiation function for a . /// - /// Type to decode a with. - /// A string representation of the version. - protected static void AddDecoder(string version) where T : Decoder + /// A string in the file which triggers this decoder to be used. + /// A function which constructs the given . + protected static void AddDecoder(string magic, Func constructor) { - decoders[version] = typeof(T); + decoders[magic] = constructor; } /// diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index ce6037b43e..29e7cee336 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.Formats { public static void Register() { - AddDecoder("{"); + AddDecoder("{", m => new JsonBeatmapDecoder()); } public override Decoder GetStoryboardDecoder() => this; diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 96747a870d..e5ced5f772 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -13,18 +13,18 @@ namespace osu.Game.Beatmaps.Formats { public static void Register() { - AddDecoder(@"osu file format v14"); - AddDecoder(@"osu file format v13"); - AddDecoder(@"osu file format v12"); - AddDecoder(@"osu file format v11"); - AddDecoder(@"osu file format v10"); - AddDecoder(@"osu file format v9"); - AddDecoder(@"osu file format v8"); - AddDecoder(@"osu file format v7"); - AddDecoder(@"osu file format v6"); - AddDecoder(@"osu file format v5"); - AddDecoder(@"osu file format v4"); - AddDecoder(@"osu file format v3"); + AddDecoder(@"osu file format v14", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v13", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v12", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v11", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v10", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v9", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v8", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v7", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v6", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v5", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v4", m => new LegacyBeatmapDecoder(m)); + AddDecoder(@"osu file format v3", m => new LegacyBeatmapDecoder(m)); // TODO: differences between versions } From cb7e192aff65d73fba3f8c762f1a3df4b14fbed0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 16:02:33 +0900 Subject: [PATCH 200/239] Determine SampleInfo defaults in DrawableHitObject --- .../Objects/JuiceStream.cs | 8 ++++++ osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 14 ++++++++-- .../Objects/Drawables/DrawableSlider.cs | 1 + osu.Game.Rulesets.Osu/Objects/Slider.cs | 19 +++++++++++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 14 +++++++--- osu.Game/Audio/SampleInfo.cs | 27 +++---------------- .../ControlPoints/SoundControlPoint.cs | 6 +++-- .../Objects/Drawables/DrawableHitObject.cs | 15 +++++++++-- osu.Game/Rulesets/Objects/HitObject.cs | 11 +++----- 9 files changed, 73 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index bf9f0bd44b..4de8cd897f 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using OpenTK; using osu.Framework.Lists; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Catch.Objects { @@ -29,9 +30,14 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; + private ControlPointInfo controlPointInfo; + private BeatmapDifficulty difficulty; + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaults(controlPointInfo, difficulty); + this.controlPointInfo = controlPointInfo; + this.difficulty = difficulty; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -124,6 +130,8 @@ namespace osu.Game.Rulesets.Catch.Objects }); } + ticks.ForEach(t => t.ApplyDefaults(controlPointInfo, difficulty)); + return ticks; } } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index fc1331551e..2b68dcf62c 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -63,10 +63,16 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; + private ControlPointInfo controlPointInfo; + private BeatmapDifficulty difficulty; + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaults(controlPointInfo, difficulty); + this.controlPointInfo = controlPointInfo; + this.difficulty = difficulty; + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; @@ -89,11 +95,15 @@ namespace osu.Game.Rulesets.Mania.Objects for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) { - ret.Add(new HoldNoteTick + var tick = new HoldNoteTick { StartTime = t, Column = Column - }); + }; + + tick.ApplyDefaults(controlPointInfo, difficulty); + + ret.Add(tick); } return ret; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 42f54af801..a91d846aa7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Scale = s.Scale, ComboColour = s.ComboColour, Samples = s.Samples, + SoundControlPoint = s.SoundControlPoint }) }; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 39ec753fe1..ec1b146328 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -10,6 +10,7 @@ using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Osu.Objects { @@ -74,10 +75,16 @@ namespace osu.Game.Rulesets.Osu.Objects public double Velocity; public double TickDistance; + private ControlPointInfo controlPointInfo; + private BeatmapDifficulty difficulty; + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaults(controlPointInfo, difficulty); + this.controlPointInfo = controlPointInfo; + this.difficulty = difficulty; + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -124,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects var distanceProgress = d / length; var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - yield return new SliderTick + var ret = new SliderTick { RepeatIndex = repeat, StartTime = repeatStartTime + timeProgress * repeatDuration, @@ -139,6 +146,10 @@ namespace osu.Game.Rulesets.Osu.Objects Volume = s.Volume })) }; + + ret.ApplyDefaults(controlPointInfo, difficulty); + + yield return ret; } } } @@ -158,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Objects var repeatStartTime = StartTime + repeat * repeatDuration; var distanceProgress = d / length; - yield return new RepeatPoint + var ret = new RepeatPoint { RepeatIndex = repeat, StartTime = repeatStartTime, @@ -167,6 +178,10 @@ namespace osu.Game.Rulesets.Osu.Objects Scale = Scale, ComboColour = ComboColour, }; + + ret.ApplyDefaults(controlPointInfo, difficulty); + + yield return ret; } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 526d23f51a..a2503b8060 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -55,9 +55,14 @@ namespace osu.Game.Rulesets.Taiko.Objects /// private double tickSpacing = 100; + private ControlPointInfo controlPointInfo; + private BeatmapDifficulty difficulty; + public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaults(controlPointInfo, difficulty); + this.controlPointInfo = controlPointInfo; + this.difficulty = difficulty; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); @@ -77,7 +82,7 @@ namespace osu.Game.Rulesets.Taiko.Objects bool first = true; for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { - ret.Add(new DrumRollTick + var tick = new DrumRollTick { FirstTick = first, TickSpacing = tickSpacing, @@ -89,12 +94,15 @@ namespace osu.Game.Rulesets.Taiko.Objects Name = @"slidertick", Volume = s.Volume })) - }); + }; + tick.ApplyDefaults(controlPointInfo, difficulty); + + ret.Add(tick); first = false; } return ret; } } -} \ No newline at end of file +} diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 3c3a64dbb2..2cde4e01ad 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -15,40 +15,19 @@ namespace osu.Game.Audio public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; - /// - /// The that is used for and - /// if the values have not already been provided by the hitobject. - /// - [JsonIgnore] - public SoundControlPoint ControlPoint; - - private string bank; /// /// The bank to load the sample from. /// - public string Bank - { - get { return string.IsNullOrEmpty(bank) ? (ControlPoint?.SampleBank ?? "normal") : bank; } - set { bank = value; } - } - - public bool ShouldSerializeBank() => Bank != ControlPoint.SampleBank; + public string Bank; /// /// The name of the sample to load. /// - public string Name { get; set; } + public string Name; - private int volume; /// /// The sample volume. /// - public int Volume - { - get { return volume == 0 ? (ControlPoint?.SampleVolume ?? 0) : volume; } - set { volume = value; } - } - - public bool ShouldSerializeVolume() => Volume != ControlPoint.SampleVolume; + public int Volume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs index 8084229382..b73a03b95a 100644 --- a/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SoundControlPoint.cs @@ -5,14 +5,16 @@ namespace osu.Game.Beatmaps.ControlPoints { public class SoundControlPoint : ControlPoint { + public const string DEFAULT_BANK = "normal"; + /// /// The default sample bank at this control point. /// - public string SampleBank; + public string SampleBank = DEFAULT_BANK; /// /// The default sample volume at this control point. /// public int SampleVolume; } -} \ No newline at end of file +} diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 57db36fda5..4f5f8898f8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -86,12 +86,23 @@ namespace osu.Game.Rulesets.Objects.Drawables { foreach (SampleInfo sample in HitObject.Samples) { - SampleChannel channel = audio.Sample.Get($@"Gameplay/{sample.Bank}-{sample.Name}"); + if (HitObject.SoundControlPoint == null) + throw new ArgumentNullException(nameof(HitObject.SoundControlPoint), $"{nameof(HitObject)} must always have an attached {nameof(HitObject.SoundControlPoint)}."); + + var bank = sample.Bank; + if (string.IsNullOrEmpty(bank)) + bank = HitObject.SoundControlPoint.SampleBank; + + int volume = sample.Volume; + if (volume == 0) + volume = HitObject.SoundControlPoint.SampleVolume; + + SampleChannel channel = audio.Sample.Get($@"Gameplay/{bank}-{sample.Name}"); if (channel == null) continue; - channel.Volume.Value = sample.Volume; + channel.Volume.Value = volume; Samples.Add(channel); } } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 0772e7707e..66e34eac32 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Objects /// public SampleInfoList Samples = new SampleInfoList(); + [JsonIgnore] + public SoundControlPoint SoundControlPoint; + /// /// Whether this is in Kiai time. /// @@ -48,13 +51,7 @@ namespace osu.Game.Rulesets.Objects EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); Kiai = effectPoint.KiaiMode; - - // Initialize first sample - Samples.ForEach(s => s.ControlPoint = soundPoint); - - // Initialize any repeat samples - var repeatData = this as IHasRepeats; - repeatData?.RepeatSamples?.ForEach(r => r.ForEach(s => s.ControlPoint = soundPoint)); + SoundControlPoint = soundPoint; } } } From 01c4b1e544577462ebd708d56173ace5df8f2bd1 Mon Sep 17 00:00:00 2001 From: naoey Date: Thu, 21 Dec 2017 15:17:37 +0530 Subject: [PATCH 201/239] Maintain a placeholder state and add tests showing all the states. - Also don't replace placeholder if new one is same as old --- osu.Game.Tests/Visual/TestCaseLeaderboard.cs | 19 +- .../Select/Leaderboards/Leaderboard.cs | 185 ++++++++++++------ 2 files changed, 127 insertions(+), 77 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index 62aa5a79b3..d1d030ced8 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -9,7 +9,6 @@ using osu.Game.Users; using osu.Framework.Allocation; using OpenTK; using System.Linq; -using System.Net; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -33,8 +32,10 @@ namespace osu.Game.Tests.Visual }); AddStep(@"New Scores", newScores); - AddStep(@"Empty Scores", () => leaderboard.Scores = Enumerable.Empty()); - AddStep(@"Network failure", networkFailure); + AddStep(@"Empty Scores", () => leaderboard.SetRetrievalState(PlaceholderState.NoScores)); + AddStep(@"Network failure", () => leaderboard.SetRetrievalState(PlaceholderState.NetworkFailure)); + AddStep(@"No supporter", () => leaderboard.SetRetrievalState(PlaceholderState.NotSupporter)); + AddStep(@"Not logged in", () => leaderboard.SetRetrievalState(PlaceholderState.NotLoggedIn)); AddStep(@"Real beatmap", realBeatmap); } @@ -265,19 +266,11 @@ namespace osu.Game.Tests.Visual }; } - private void networkFailure() - { - leaderboard.Beatmap = new BeatmapInfo { Ruleset = rulesets.GetRuleset(0) }; - } - private class FailableLeaderboard : Leaderboard { - protected override void UpdateScores() + public void SetRetrievalState(PlaceholderState state) { - if (Beatmap?.OnlineBeatmapID == null) - OnUpdateFailed(new WebException()); - else - base.UpdateScores(); + PlaceholderState = state; } } } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 9a6e88b0a6..3636115227 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -48,21 +48,16 @@ namespace osu.Game.Screens.Select.Leaderboards { scores = value; - placeholderContainer.FadeOut(fade_duration); - scrollFlow?.FadeOut(fade_duration).Expire(); scrollFlow = null; loading.Hide(); - if (scores == null) + if (scores == null || !scores.Any()) return; - if (!scores.Any()) - { - replacePlaceholder(new MessagePlaceholder(@"No records yet!")); - return; - } + // ensure placeholder is hidden when displaying scores + PlaceholderState = PlaceholderState.Successful; // schedule because we may not be loaded yet (LoadComponentAsync complains). Schedule(() => @@ -100,7 +95,43 @@ namespace osu.Game.Screens.Select.Leaderboards if (value == scope) return; scope = value; - UpdateScores(); + updateScores(); + } + } + + private PlaceholderState placeholderState; + protected PlaceholderState PlaceholderState + { + get { return placeholderState; } + set + { + if (value == placeholderState) return; + + switch (placeholderState = value) + { + case PlaceholderState.NetworkFailure: + replacePlaceholder(new RetrievalFailurePlaceholder + { + OnRetry = updateScores, + }); + break; + + case PlaceholderState.NoScores: + replacePlaceholder(new MessagePlaceholder(@"No records yet!")); + break; + + case PlaceholderState.NotLoggedIn: + replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + break; + + case PlaceholderState.NotSupporter: + replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); + break; + + default: + replacePlaceholder(null); + break; + } } } @@ -143,7 +174,7 @@ namespace osu.Game.Screens.Select.Leaderboards Scores = null; pendingBeatmapSwitch?.Cancel(); - pendingBeatmapSwitch = Schedule(UpdateScores); + pendingBeatmapSwitch = Schedule(updateScores); } } @@ -170,7 +201,7 @@ namespace osu.Game.Screens.Select.Leaderboards private GetScoresRequest getScoresRequest; - private void handleRulesetChange(RulesetInfo ruleset) => UpdateScores(); + private void handleRulesetChange(RulesetInfo ruleset) => updateScores(); private void handleApiStateChange(APIState oldState, APIState newState) { @@ -179,46 +210,43 @@ namespace osu.Game.Screens.Select.Leaderboards return; if (newState == APIState.Online) - UpdateScores(); + updateScores(); } - protected virtual void UpdateScores() + private void updateScores() { if (!IsLoaded) return; getScoresRequest?.Cancel(); getScoresRequest = null; - Scores = null; if (Scope == LeaderboardScope.Local) { // TODO: get local scores from wherever here. - Scores = Enumerable.Empty(); + PlaceholderState = PlaceholderState.NoScores; return; } if (api?.IsLoggedIn != true) { - replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + PlaceholderState = PlaceholderState.NotLoggedIn; return; } if (Beatmap?.OnlineBeatmapID == null) { - replacePlaceholder(new RetrievalFailurePlaceholder - { - OnRetry = UpdateScores, - }); + PlaceholderState = PlaceholderState.NetworkFailure; return; } + PlaceholderState = PlaceholderState.Retrieving; loading.Show(); if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) { loading.Hide(); - replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); + PlaceholderState = PlaceholderState.NotSupporter; return; } @@ -226,27 +254,37 @@ namespace osu.Game.Screens.Select.Leaderboards getScoresRequest.Success += r => { Scores = r.Scores; + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; }; - getScoresRequest.Failure += OnUpdateFailed; + getScoresRequest.Failure += onUpdateFailed; api.Queue(getScoresRequest); } - protected void OnUpdateFailed(Exception e) + private void onUpdateFailed(Exception e) { if (e is OperationCanceledException) return; - Scores = null; - replacePlaceholder(new RetrievalFailurePlaceholder - { - OnRetry = UpdateScores, - }); + PlaceholderState = PlaceholderState.NetworkFailure; Logger.Error(e, @"Couldn't fetch beatmap scores!"); } - private void replacePlaceholder(Drawable placeholder) + private void replacePlaceholder(Placeholder placeholder) { - placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint); + if (placeholder == null) + { + placeholderContainer.FadeOutFromOne(fade_duration, Easing.OutQuint); + placeholderContainer.Clear(true); + return; + } + + var existingPlaceholder = placeholderContainer.Children.FirstOrDefault() as Placeholder; + + if (placeholder.Equals(existingPlaceholder)) + return; + + Scores = null; + placeholderContainer.Clear(true); placeholderContainer.Child = placeholder; placeholderContainer.FadeInFromZero(fade_duration, Easing.OutQuint); @@ -281,8 +319,15 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private class MessagePlaceholder : FillFlowContainer + private abstract class Placeholder : FillFlowContainer, IEquatable { + public virtual bool Equals(Placeholder other) => GetType() == other?.GetType(); + } + + private class MessagePlaceholder : Placeholder + { + private readonly string message; + public MessagePlaceholder(string message) { Direction = FillDirection.Horizontal; @@ -297,14 +342,16 @@ namespace osu.Game.Screens.Select.Leaderboards }, new OsuSpriteText { - Text = message, + Text = this.message = message, TextSize = 22, }, }; } + + public override bool Equals(Placeholder other) => (other as MessagePlaceholder)?.message == message; } - private class RetrievalFailurePlaceholder : FillFlowContainer + private class RetrievalFailurePlaceholder : Placeholder { public Action OnRetry; @@ -327,43 +374,43 @@ namespace osu.Game.Screens.Select.Leaderboards }, }; } - } - private class RetryButton : OsuHoverContainer - { - private readonly SpriteIcon icon; - - public Action Action; - - public RetryButton() + private class RetryButton : OsuHoverContainer { - Height = 26; - Width = 26; - Child = new OsuClickableContainer + private readonly SpriteIcon icon; + + public Action Action; + + public RetryButton() { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = () => Action?.Invoke(), - Child = icon = new SpriteIcon + Height = 26; + Width = 26; + Child = new OsuClickableContainer { - Icon = FontAwesome.fa_refresh, - Size = new Vector2(26), - Shadow = true, - }, - }; - } + AutoSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => Action?.Invoke(), + Child = icon = new SpriteIcon + { + Icon = FontAwesome.fa_refresh, + Size = new Vector2(26), + Shadow = true, + }, + }; + } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) - { - icon.ScaleTo(0.8f, 400, Easing.OutQuint); - return base.OnMouseDown(state, args); - } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + icon.ScaleTo(0.8f, 400, Easing.OutQuint); + return base.OnMouseDown(state, args); + } - protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) - { - icon.ScaleTo(1, 400, Easing.OutElastic); - return base.OnMouseUp(state, args); + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + icon.ScaleTo(1, 400, Easing.OutElastic); + return base.OnMouseUp(state, args); + } } } } @@ -375,4 +422,14 @@ namespace osu.Game.Screens.Select.Leaderboards Global, Friend, } + + public enum PlaceholderState + { + Successful, + Retrieving, + NetworkFailure, + NoScores, + NotLoggedIn, + NotSupporter, + } } From b6fd5b0f17ca5e3f0c6fd8bbf35359eb171ccc60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 13:58:24 +0900 Subject: [PATCH 202/239] Fix keyboard and mouse input not properly getting blocked by GameplayMenuOverlay --- .../Bindings/GlobalKeyBindingInputManager.cs | 3 +- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 37 ++++++++++--------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index 759396e195..745508ec9e 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Input.Bindings; namespace osu.Game.Input.Bindings { - public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager + public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager, IHandleGlobalInput { private readonly Drawable handler; diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 182c4efe89..e49ebb5590 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Play protected override bool BlockPassThroughKeyboard => true; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public Action OnRetry; public Action OnQuit; @@ -197,6 +199,7 @@ namespace osu.Game.Screens.Play } private int _selectionIndex = -1; + private int selectionIndex { get { return _selectionIndex; } @@ -219,26 +222,26 @@ namespace osu.Game.Screens.Play protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (args.Repeat) - return false; - - switch (args.Key) + if (!args.Repeat) { - case Key.Up: - if (selectionIndex == -1 || selectionIndex == 0) - selectionIndex = InternalButtons.Count - 1; - else - selectionIndex--; - return true; - case Key.Down: - if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - selectionIndex = 0; - else - selectionIndex++; - return true; + switch (args.Key) + { + case Key.Up: + if (selectionIndex == -1 || selectionIndex == 0) + selectionIndex = InternalButtons.Count - 1; + else + selectionIndex--; + return true; + case Key.Down: + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) + selectionIndex = 0; + else + selectionIndex++; + return true; + } } - return false; + return base.OnKeyDown(state, args); } private void buttonSelectionChanged(DialogButton button, bool isSelected) From 1895b16d6791a552cf3bab66ca52238fa0023ca1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 19:34:20 +0900 Subject: [PATCH 203/239] Correctly make fullscreen overlays block keyboard input from drawables behind them - [ ] Depends on #1711 to get correct global binding handling. --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 2 ++ osu.Game/Overlays/WaveOverlayContainer.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index e3be23ebc6..9639907914 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -31,6 +31,8 @@ namespace osu.Game.Overlays.Mods protected readonly OsuSpriteText MultiplierLabel; private readonly FillFlowContainer footerContainer; + protected override bool BlockPassThroughKeyboard => false; + protected readonly FillFlowContainer ModSectionsContainer; public readonly Bindable> SelectedMods = new Bindable>(); diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 50150b6660..3f9703a523 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -28,6 +28,8 @@ namespace osu.Game.Overlays private readonly Container contentContainer; + protected override bool BlockPassThroughKeyboard => true; + protected override Container Content => contentContainer; protected Color4 FirstWaveColour From a3fcc0b60cb4c7113b0195c846a2008165836651 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 19:40:41 +0900 Subject: [PATCH 204/239] Back to using SortedLists --- osu-framework | 2 +- .../UI/ManiaRulesetContainer.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 1 - .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 8 ++++---- .../Visual/TestCaseBeatSyncedContainer.cs | 4 ++-- osu.Game/Audio/SampleInfo.cs | 2 -- .../ControlPoints/ControlPointInfo.cs | 19 ++++++++++--------- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/osu-framework b/osu-framework index 28fbd0711c..d997b34da4 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 28fbd0711c09d3b06b51fc728b025f83ded2f0f8 +Subproject commit d997b34da4556e4fcfc2de5413ded92c6ee9bec1 diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 83306187bd..61446a31b6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - List timingPoints = Beatmap.ControlPointInfo.TimingPoints; + var timingPoints = Beatmap.ControlPointInfo.TimingPoints; var barLines = new List(); for (int i = 0; i < timingPoints.Count; i++) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index ec1b146328..5449466f2e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -10,7 +10,6 @@ using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 8fd7645287..7143550ee2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -140,19 +140,19 @@ namespace osu.Game.Tests.Beatmaps.Formats } /// - /// Reads a .osu file first with a , serializes the resulting to JSON + /// Reads a .osu file first with a , serializes the resulting to JSON /// and then deserializes the result back into a through an . /// /// The .osu file to decode. - /// The after being decoded by an . + /// The after being decoded by an . private Beatmap decodeAsJson(string filename) => decode(filename).jsonDecoded; /// - /// Reads a .osu file first with a , serializes the resulting to JSON + /// Reads a .osu file first with a , serializes the resulting to JSON /// and then deserializes the result back into a through an . /// /// The .osu file to decode. - /// The after being decoded by an . + /// The after being decoded by an . private (Beatmap legacyDecoded, Beatmap jsonDecoded) decode(string filename) { using (var stream = Resource.OpenResource(filename)) diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs index d99485f3a2..771148827d 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.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 osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using OpenTK.Graphics; +using osu.Framework.Lists; namespace osu.Game.Tests.Visual { @@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual }; } - private List timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { if (timingPoints[timingPoints.Count - 1] == current) diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 2cde4e01ad..8242ee14df 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -2,8 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using Newtonsoft.Json; -using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Audio { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 652ae42979..64b9b837b6 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,35 +5,36 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints { [Serializable] public class ControlPointInfo { - [JsonProperty] /// /// All timing points. /// - public List TimingPoints { get; private set; } = new List(); - [JsonProperty] + public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); + /// /// All difficulty points. /// - public List DifficultyPoints { get; private set; } = new List(); - [JsonProperty] + public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); + /// /// All sound points. /// - public List SoundPoints { get; private set; } = new List(); - [JsonProperty] + public SortedList SoundPoints { get; private set; } = new SortedList(Comparer.Default); + /// /// All effect points. /// - public List EffectPoints { get; private set; } = new List(); + [JsonProperty] + public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); /// /// Finds the difficulty control point that is active at . @@ -91,7 +92,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. If null, a new control point will be constructed. /// The active control point at . - private T binarySearch(List list, double time, T prePoint = null) + private T binarySearch(SortedList list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) From 052badc1bd50f1f1db8e23fdba83829ad46103df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 19:42:44 +0900 Subject: [PATCH 205/239] Add a right-click context option to carousel panels to view online beatmap details --- .../Select/Carousel/DrawableCarouselBeatmapSet.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8abb93950f..4b999f9b87 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; using OpenTK; using OpenTK.Graphics; @@ -26,6 +27,7 @@ namespace osu.Game.Screens.Select.Carousel { private Action deleteRequested; private Action restoreHiddenRequested; + private Action viewDetails; private readonly BeatmapSetInfo beatmapSet; @@ -37,14 +39,16 @@ namespace osu.Game.Screens.Select.Carousel beatmapSet = set.BeatmapSet; } - [BackgroundDependencyLoader] - private void load(LocalisationEngine localisation, BeatmapManager manager) + [BackgroundDependencyLoader(true)] + private void load(LocalisationEngine localisation, BeatmapManager manager, BeatmapSetOverlay beatmapOverlay) { if (localisation == null) throw new ArgumentNullException(nameof(localisation)); restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore); deleteRequested = manager.Delete; + if (beatmapOverlay != null) + viewDetails = beatmapOverlay.ShowBeatmapSet; Children = new Drawable[] { @@ -96,6 +100,9 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); + if (beatmapSet.OnlineBeatmapSetID != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails?.Invoke(beatmapSet.OnlineBeatmapSetID.Value))); + if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested?.Invoke(beatmapSet))); From b2c0b013aa06b4d9bc0bf4c9f088764fc9f7f1f2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 19:56:12 +0900 Subject: [PATCH 206/239] Remove migration setting in favour of export option in the editor --- osu.Game/Beatmaps/BeatmapManager.cs | 70 ------------------- osu.Game/Beatmaps/WorkingBeatmap.cs | 15 ++++ .../Sections/Maintenance/GeneralSettings.cs | 12 ---- osu.Game/Screens/Edit/Editor.cs | 6 ++ 4 files changed, 21 insertions(+), 82 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 40f1545b77..ca1a056992 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -553,76 +553,6 @@ namespace osu.Game.Beatmaps return beatmapSet; } - public void UpdateContent(BeatmapInfo beatmapInfo, Stream newData) - { - var context = createContext(); - - using (var transaction = context.BeginTransaction()) - { - var setInfo = beatmapInfo.BeatmapSet; - var existingSetFileInfo = setInfo.Files.First(f => f.FileInfo.Hash == beatmapInfo.Hash); - var existingFileInfo = existingSetFileInfo.FileInfo; - - existingSetFileInfo.FileInfo = files.Add(newData); - files.Dereference(existingFileInfo); - - beatmapInfo.Hash = newData.ComputeSHA2Hash(); - beatmapInfo.MD5Hash = newData.ComputeMD5Hash(); - - context.Update(existingSetFileInfo); - context.Update(beatmapInfo); - - context.SaveChanges(transaction); - } - } - - public void MigrateAllToNewFormat() - { - var usableSets = GetAllUsableBeatmapSets(); - - if (usableSets.Count == 0) - return; - - var notification = new ProgressNotification - { - Progress = 0, - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - int i = 1; - foreach (var set in usableSets) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Migrating ({i} of {usableSets.Count})"; - notification.Progress = (float)i++ / usableSets.Count; - - foreach (var beatmap in set.Beatmaps) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - var working = GetWorkingBeatmap(beatmap); - using (var ms = new MemoryStream()) - using (var sw = new StreamWriter(ms)) - { - sw.Write(working.Beatmap.Serialize()); - sw.Flush(); - - ms.Position = 0; - UpdateContent(beatmap, ms); - } - } - } - - notification.State = ProgressNotificationState.Completed; - } - /// /// Returns a list of all usable s. /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 736cc2a0b0..8c8cf212c1 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -10,6 +10,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Game.Storyboards; +using osu.Framework.IO.File; +using System.IO; +using osu.Game.IO.Serialization; +using System.Diagnostics; namespace osu.Game.Beatmaps { @@ -38,6 +42,17 @@ namespace osu.Game.Beatmaps storyboard = new AsyncLazy(populateStoryboard); } + /// + /// Saves the . + /// + public void Save() + { + var path = FileSafety.GetTempPath(Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json"); + using (var sw = new StreamWriter(path)) + sw.WriteLine(Beatmap.Serialize()); + Process.Start(path); + } + protected abstract Beatmap GetBeatmap(); protected abstract Texture GetBackground(); protected abstract Track GetTrack(); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 2b40ade895..a648ebd64c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -58,18 +58,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } }; - if (!osuGame.IsDeployedBuild) - { - Add(migrateButton = new SettingsButton - { - Text = "Migrate all beatmaps to the new format", - Action = () => - { - migrateButton.Enabled.Value = false; - Task.Factory.StartNew(beatmaps.MigrateAllToNewFormat).ContinueWith(t => Schedule(() => migrateButton.Enabled.Value = true), TaskContinuationOptions.LongRunning); - } - }); - } } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 607ff792d8..5ff48f1adf 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -67,6 +67,7 @@ namespace osu.Game.Screens.Edit { Items = new[] { + new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), new EditorMenuItem("Exit", MenuItemType.Standard, Exit) } } @@ -136,6 +137,11 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } + private void exportBeatmap() + { + Beatmap.Value.Save(); + } + private void onModeChanged(EditorScreenMode mode) { currentScreen?.Exit(); From 2fb8895e663ac6adba47bdb0de38b2ab48fb1353 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 19:57:09 +0900 Subject: [PATCH 207/239] Add spacer to menu --- osu.Game/Screens/Edit/Editor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 5ff48f1adf..19d00f3477 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Edit Items = new[] { new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), + new EditorMenuItemSpacer(), new EditorMenuItem("Exit", MenuItemType.Standard, Exit) } } From eb382dc23f0ce817782931a2c43e5bdb272beab8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 19:58:41 +0900 Subject: [PATCH 208/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index d997b34da4..e41fa9e90a 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit d997b34da4556e4fcfc2de5413ded92c6ee9bec1 +Subproject commit e41fa9e90ae8261afa2962ee9fddb5b68491212c From fdd218c64189e25a8eb68d89cfee136431a1d8e0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 20:11:00 +0900 Subject: [PATCH 209/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 28fbd0711c..f4fde31f8c 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 28fbd0711c09d3b06b51fc728b025f83ded2f0f8 +Subproject commit f4fde31f8c09305d2130064da2f7bae995be8286 From 6fbd06f96735179655a8d260866d38b131c7a107 Mon Sep 17 00:00:00 2001 From: Seokho Song <0xdevssh@gmail.com> Date: Tue, 19 Dec 2017 00:53:17 +0900 Subject: [PATCH 210/239] Fix Not update retry counter on PauseOverlay I've find "You've retried xx time(s)" message that something weird. That is not displayed pause overlay and only see count on FailOverlay I change code that PauseContainer.Retries property can be set call-back function. Signed-off-by: Seokho Song <0xdevssh@gmail.com> --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 70 +++++++++++--------- osu.Game/Screens/Play/Player.cs | 2 +- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 182c4efe89..cf3eba00a8 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -122,44 +122,20 @@ namespace osu.Game.Screens.Play }, }; - Retries = 0; + updateRetryCount(); } + private int retries; public int Retries { set { - if (retryCounterContainer != null) - { - // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" + if (value == retries) + return; - retryCounterContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $"{value:n0}", - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $" time{(value == 1 ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - } - }; - } + retries = value; + if (retryCounterContainer != null) + updateRetryCount(); } } @@ -249,6 +225,38 @@ namespace osu.Game.Screens.Play selectionIndex = InternalButtons.IndexOf(button); } + private void updateRetryCount() + { + // "You've retried 1,065 times in this session" + // "You've retried 1 time in this session" + + retryCounterContainer.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $"{retries:n0}", + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $" time{(retries == 1 ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + } + }; + } + private class Button : DialogButton { protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b5b09504da..e9992f2df7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,8 +161,8 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - Retries = RestartCount, OnPause = () => { + pauseContainer.Retries = RestartCount; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; }, OnResume = () => { From e4ead365446526371a51d5d172be61db12fd05bb Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 21 Dec 2017 13:01:14 +0100 Subject: [PATCH 211/239] Added completion text --- 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 e0285e7ae0..1544d4fc01 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -349,6 +349,7 @@ namespace osu.Game.Beatmaps var notification = new ProgressNotification { + CompletionText = "Restored all deleted beatmaps!", Progress = 0, State = ProgressNotificationState.Active, }; From 644aaa81679d00b0292386898835f1356976185b Mon Sep 17 00:00:00 2001 From: naoey Date: Thu, 21 Dec 2017 18:00:10 +0530 Subject: [PATCH 212/239] Unsubscribe from API state change event. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3636115227..52b152b42f 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -197,6 +197,9 @@ namespace osu.Game.Screens.Select.Leaderboards if (osuGame != null) osuGame.Ruleset.ValueChanged -= handleRulesetChange; + + if (api != null) + api.OnStateChange -= handleApiStateChange; } private GetScoresRequest getScoresRequest; From abe465358c2a2994808833a7430c0358a9ac7f22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 21:46:57 +0900 Subject: [PATCH 213/239] Fix formatting --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 45 ++++++++++---------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index ac355b82ca..6969cd915b 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -128,6 +128,7 @@ namespace osu.Game.Screens.Play } private int retries; + public int Retries { set @@ -235,28 +236,28 @@ namespace osu.Game.Screens.Play retryCounterContainer.Children = new Drawable[] { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $"{retries:n0}", - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $" time{(retries == 1 ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - } + new OsuSpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $"{retries:n0}", + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $" time{(retries == 1 ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + } }; } From 4bd2c7e95fe17544c1ac29f90f87a4d3f4ec26d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 21:54:46 +0900 Subject: [PATCH 214/239] Fix minor formatting issue --- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 790a8421a2..4403d412fc 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); - Leaderboard.Scope = (LeaderboardScope) tab - 1; + Leaderboard.Scope = (LeaderboardScope)tab - 1; Leaderboard.Show(); break; } From 866d1c6e0f584829072211705a4d31b62fca5ee0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 22:13:18 +0900 Subject: [PATCH 215/239] Remove now unused references --- osu.Game/Beatmaps/BeatmapManager.cs | 1 - .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index ca1a056992..a1ad708304 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -20,7 +20,6 @@ using osu.Game.Beatmaps.IO; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.IO; -using osu.Game.IO.Serialization; using osu.Game.IPC; using osu.Game.Online.API; using osu.Game.Online.API.Requests; diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index a648ebd64c..953e9a54ae 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance protected override string Header => "General"; [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, BeatmapManager beatmaps) + private void load(BeatmapManager beatmaps) { Children = new Drawable[] { @@ -57,7 +57,6 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance } } }; - } } } From 790aa8be2a51b0d6a07f3750e0beb085c7f8da7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 22:13:53 +0900 Subject: [PATCH 216/239] Merge master into beatmap-serialization --- osu-framework | 2 +- .../Bindings/GlobalKeyBindingInputManager.cs | 3 +- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 108 ++++++++++-------- osu.Game/Screens/Play/Player.cs | 2 +- 4 files changed, 64 insertions(+), 51 deletions(-) diff --git a/osu-framework b/osu-framework index e41fa9e90a..f4fde31f8c 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit e41fa9e90ae8261afa2962ee9fddb5b68491212c +Subproject commit f4fde31f8c09305d2130064da2f7bae995be8286 diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index 759396e195..745508ec9e 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -5,11 +5,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Framework.Input.Bindings; namespace osu.Game.Input.Bindings { - public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager + public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager, IHandleGlobalInput { private readonly Drawable handler; diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 182c4efe89..6969cd915b 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Play protected override bool BlockPassThroughKeyboard => true; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public Action OnRetry; public Action OnQuit; @@ -122,44 +124,21 @@ namespace osu.Game.Screens.Play }, }; - Retries = 0; + updateRetryCount(); } + private int retries; + public int Retries { set { - if (retryCounterContainer != null) - { - // "You've retried 1,065 times in this session" - // "You've retried 1 time in this session" + if (value == retries) + return; - retryCounterContainer.Children = new Drawable[] - { - new OsuSpriteText - { - Text = "You've retried ", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $"{value:n0}", - Font = @"Exo2.0-Bold", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - }, - new OsuSpriteText - { - Text = $" time{(value == 1 ? "" : "s")} in this session", - Shadow = true, - ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 - } - }; - } + retries = value; + if (retryCounterContainer != null) + updateRetryCount(); } } @@ -197,6 +176,7 @@ namespace osu.Game.Screens.Play } private int _selectionIndex = -1; + private int selectionIndex { get { return _selectionIndex; } @@ -219,26 +199,26 @@ namespace osu.Game.Screens.Play protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (args.Repeat) - return false; - - switch (args.Key) + if (!args.Repeat) { - case Key.Up: - if (selectionIndex == -1 || selectionIndex == 0) - selectionIndex = InternalButtons.Count - 1; - else - selectionIndex--; - return true; - case Key.Down: - if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) - selectionIndex = 0; - else - selectionIndex++; - return true; + switch (args.Key) + { + case Key.Up: + if (selectionIndex == -1 || selectionIndex == 0) + selectionIndex = InternalButtons.Count - 1; + else + selectionIndex--; + return true; + case Key.Down: + if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) + selectionIndex = 0; + else + selectionIndex++; + return true; + } } - return false; + return base.OnKeyDown(state, args); } private void buttonSelectionChanged(DialogButton button, bool isSelected) @@ -249,6 +229,38 @@ namespace osu.Game.Screens.Play selectionIndex = InternalButtons.IndexOf(button); } + private void updateRetryCount() + { + // "You've retried 1,065 times in this session" + // "You've retried 1 time in this session" + + retryCounterContainer.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "You've retried ", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $"{retries:n0}", + Font = @"Exo2.0-Bold", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + }, + new OsuSpriteText + { + Text = $" time{(retries == 1 ? "" : "s")} in this session", + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f), + TextSize = 18 + } + }; + } + private class Button : DialogButton { protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b5b09504da..e9992f2df7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,8 +161,8 @@ namespace osu.Game.Screens.Play OnRetry = Restart, OnQuit = Exit, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - Retries = RestartCount, OnPause = () => { + pauseContainer.Retries = RestartCount; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; }, OnResume = () => { From 87e790080b25a371c6251a9ea43f5d96d890127c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 22:22:28 +0900 Subject: [PATCH 217/239] Remove manual audio thread synchronisation logic No longer required as calls are blocking. --- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 ------ osu.Game/Screens/Play/Player.cs | 5 ----- 2 files changed, 11 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 23bec53014..d913895159 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -13,7 +13,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using OpenTK; using OpenTK.Graphics; -using System.Threading; namespace osu.Game.Overlays.Music { @@ -153,11 +152,6 @@ namespace osu.Game.Overlays.Music var track = beatmapBacking.Value.Track; track.Restart(); - - // this is temporary until we have blocking (async.Wait()) audio component methods. - // then we can call RestartAsync().Wait() or the blocking version above. - while (!track.IsRunning) - Thread.Sleep(1); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9992f2df7..c7111e57f2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -327,11 +327,6 @@ namespace osu.Game.Screens.Play { adjustableSourceClock.Reset(); - // this is temporary until we have blocking (async.Wait()) audio component methods. - // then we can call ResetAsync().Wait() or the blocking version above. - while (adjustableSourceClock.IsRunning) - Thread.Sleep(1); - Schedule(() => { decoupledClock.ChangeSource(adjustableSourceClock); From 620e9737c33a9d02626d4cbf8fcf579f267e3471 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 22:33:16 +0900 Subject: [PATCH 218/239] Avoid many many unnecessary enumerations --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1544d4fc01..0325785016 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -343,9 +343,9 @@ namespace osu.Game.Beatmaps public void UndeleteAll() { - var mapSets = QueryBeatmapSets(bs => bs.DeletePending); + var deleteMaps = QueryBeatmapSets(bs => bs.DeletePending).ToList(); - if (!mapSets.Any()) return; + if (!deleteMaps.Any()) return; var notification = new ProgressNotification { @@ -358,14 +358,14 @@ namespace osu.Game.Beatmaps int i = 0; - foreach (var bs in mapSets) + foreach (var bs in deleteMaps) { if (notification.State == ProgressNotificationState.Cancelled) // user requested abort return; - notification.Text = $"Restoring ({i} of {mapSets.Count()})"; - notification.Progress = (float)++i / mapSets.Count(); + notification.Text = $"Restoring ({i} of {deleteMaps.Count})"; + notification.Progress = (float)++i / deleteMaps.Count; Undelete(bs); } From ac1d27e925e80f95d939bf30d72faa563afe5a29 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 Dec 2017 23:02:46 +0900 Subject: [PATCH 219/239] Fix possible nullref exceptions --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 3 ++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 3 ++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 4de8cd897f..b0540b2a4d 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -130,7 +130,8 @@ namespace osu.Game.Rulesets.Catch.Objects }); } - ticks.ForEach(t => t.ApplyDefaults(controlPointInfo, difficulty)); + if (controlPointInfo != null && difficulty != null) + ticks.ForEach(t => t.ApplyDefaults(controlPointInfo, difficulty)); return ticks; } diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 2b68dcf62c..2a62eaa2ef 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -101,7 +101,8 @@ namespace osu.Game.Rulesets.Mania.Objects Column = Column }; - tick.ApplyDefaults(controlPointInfo, difficulty); + if (controlPointInfo != null && difficulty != null) + tick.ApplyDefaults(controlPointInfo, difficulty); ret.Add(tick); } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5449466f2e..af50667d42 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -146,7 +146,8 @@ namespace osu.Game.Rulesets.Osu.Objects })) }; - ret.ApplyDefaults(controlPointInfo, difficulty); + if (controlPointInfo != null && difficulty != null) + ret.ApplyDefaults(controlPointInfo, difficulty); yield return ret; } @@ -178,7 +179,8 @@ namespace osu.Game.Rulesets.Osu.Objects ComboColour = ComboColour, }; - ret.ApplyDefaults(controlPointInfo, difficulty); + if (controlPointInfo != null && difficulty != null) + ret.ApplyDefaults(controlPointInfo, difficulty); yield return ret; } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index a2503b8060..ff20a16c7b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -96,7 +96,8 @@ namespace osu.Game.Rulesets.Taiko.Objects })) }; - tick.ApplyDefaults(controlPointInfo, difficulty); + if (controlPointInfo != null && difficulty != null) + tick.ApplyDefaults(controlPointInfo, difficulty); ret.Add(tick); first = false; From 0121692919333f959fb9bbf3d4290cfdea20e978 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 23:14:42 +0900 Subject: [PATCH 220/239] Ignore bugged inspectcode inspection --- .../OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 972677a6f1..89821a10fb 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { + // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value; float dist = diff.Length; From 13fee5402a54d160160f98603a69d7e94a4fe89d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 23:20:11 +0900 Subject: [PATCH 221/239] Remove unnecessary using visual studio doesn't save --- osu.Game/Screens/Play/Player.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c7111e57f2..340fc39d52 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.UI; using osu.Game.Screens.Backgrounds; using System; using System.Linq; -using System.Threading; using System.Threading.Tasks; using osu.Framework.Threading; using osu.Game.Rulesets.Mods; From 65e6206d06858c6de53182ffb51aefd662217dba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 23:48:35 +0900 Subject: [PATCH 222/239] Use local bindable --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 52b152b42f..50c16ed04d 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; +using osu.Framework.Configuration; using osu.Game.Graphics.Sprites; using osu.Game.Graphics; using osu.Framework.Logging; @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Select.Leaderboards private FillFlowContainer scrollFlow; + private readonly Bindable ruleset = new Bindable(); + public Action ScoreSelected; private readonly LoadingAnimation loading; @@ -185,7 +188,9 @@ namespace osu.Game.Screens.Select.Leaderboards this.osuGame = osuGame; if (osuGame != null) - osuGame.Ruleset.ValueChanged += handleRulesetChange; + ruleset.BindTo(osuGame.Ruleset); + + ruleset.ValueChanged += r => updateScores(); if (api != null) api.OnStateChange += handleApiStateChange; @@ -195,17 +200,12 @@ namespace osu.Game.Screens.Select.Leaderboards { base.Dispose(isDisposing); - if (osuGame != null) - osuGame.Ruleset.ValueChanged -= handleRulesetChange; - if (api != null) api.OnStateChange -= handleApiStateChange; } private GetScoresRequest getScoresRequest; - private void handleRulesetChange(RulesetInfo ruleset) => updateScores(); - private void handleApiStateChange(APIState oldState, APIState newState) { if (Scope == LeaderboardScope.Local) From 57fdbda16d82dc4f03d167f55a7f1db7dfd8e282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Dec 2017 23:48:48 +0900 Subject: [PATCH 223/239] Remove unnecessary IsLoaded check We are always loaded at this point. --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 50c16ed04d..3cf4166e2d 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -218,8 +218,6 @@ namespace osu.Game.Screens.Select.Leaderboards private void updateScores() { - if (!IsLoaded) return; - getScoresRequest?.Cancel(); getScoresRequest = null; Scores = null; From 5c9d4843957ddd83d6ee42ab096097f4fc7bb73b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 00:50:32 +0900 Subject: [PATCH 224/239] Adjust debounce a bit --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4d5101447a..68ee08e721 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Select if (beatmap == Beatmap.Value.BeatmapInfo) performLoad(); else - selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 100); + selectionChangedDebounce = Scheduler.AddDelayed(performLoad, 200); } } From aa388885b7e0f2dcc7500466014dab50700572c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 00:53:34 +0900 Subject: [PATCH 225/239] Adjust animation slightly --- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 3cf4166e2d..b3f2649ab6 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -403,13 +403,13 @@ namespace osu.Game.Screens.Select.Leaderboards protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - icon.ScaleTo(0.8f, 400, Easing.OutQuint); + icon.ScaleTo(0.8f, 4000, Easing.OutQuint); return base.OnMouseDown(state, args); } protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) { - icon.ScaleTo(1, 400, Easing.OutElastic); + icon.ScaleTo(1, 1000, Easing.OutElastic); return base.OnMouseUp(state, args); } } From 6ef575c005c6aa889ffda6606c382f7d41909fed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 15:46:45 +0900 Subject: [PATCH 226/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index f4fde31f8c..675745afa1 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit f4fde31f8c09305d2130064da2f7bae995be8286 +Subproject commit 675745afa19c0ceaa96a43edf76ee295ba394c61 From f3c93c894b273d1373e5e44c302522b46e41becb Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 21 Nov 2017 10:15:16 +0300 Subject: [PATCH 227/239] Rank graph improvements --- osu.Game/Overlays/Profile/ProfileHeader.cs | 13 ++- osu.Game/Overlays/Profile/RankChart.cs | 125 +++++++++++++-------- 2 files changed, 88 insertions(+), 50 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4ddd6c498e..f0b441affb 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Profile private readonly OsuTextFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; + protected readonly RankChart rankGraph; private readonly Container coverContainer, chartContainer, supporterTag; private readonly Sprite levelBadge; @@ -285,6 +286,10 @@ namespace osu.Game.Overlays.Profile { Colour = Color4.Black.Opacity(0.25f), RelativeSizeAxes = Axes.Both + }, + rankGraph = new RankChart + { + RelativeSizeAxes = Axes.Both } } } @@ -303,11 +308,7 @@ namespace osu.Game.Overlays.Profile public User User { - get - { - return user; - } - + get { return user; } set { user = value; @@ -420,7 +421,7 @@ namespace osu.Game.Overlays.Profile gradeSPlus.DisplayCount = 0; gradeSSPlus.DisplayCount = 0; - chartContainer.Add(new RankChart(user) { RelativeSizeAxes = Axes.Both }); + rankGraph.Redraw(user); } } diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 8190ef3c62..3981939bf8 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -14,30 +14,40 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile { public class RankChart : Container { + private const float primary_textsize = 25; + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int rankedDays = 88; + private readonly SpriteText rankText, performanceText, relativeText; private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; - private readonly int[] ranks; + private KeyValuePair[] ranks; + private User user; - private const float primary_textsize = 25, secondary_textsize = 13, padding = 10; - - private readonly User user; - - public RankChart(User user) + public RankChart() { - this.user = user; - - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; - ranks = userRanks.SkipWhile(x => x == 0).ToArray(); - Padding = new MarginPadding { Vertical = padding }; Children = new Drawable[] { + placeholder = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Text = "No recent plays", + TextSize = 14, + Font = @"Exo2.0-RegularItalic", + Alpha = 0, + Padding = new MarginPadding { Bottom = padding } + }, rankText = new OsuSpriteText { Anchor = Anchor.TopCentre, @@ -60,50 +70,73 @@ namespace osu.Game.Overlays.Profile Font = @"Exo2.0-RegularItalic", TextSize = secondary_textsize }, - }; - - if (ranks.Length > 0) - { - Add(graph = new RankChartLineGraph + graph = new RankChartLineGraph { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, Y = -secondary_textsize, - DefaultValueCount = ranks.Length, - }); + Alpha = 0, + } + }; - graph.OnBallMove += showHistoryRankTexts; + graph.OnBallMove += showHistoryRankTexts; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.Colour = colours.Yellow; + } + + public void Redraw(User user) + { + if (this.user == null && user != null) + placeholder.FadeOut(fade_duration, Easing.Out); + + this.user = user; + + if (user == null) + { + rankText.Text = string.Empty; + performanceText.Text = string.Empty; + relativeText.Text = string.Empty; + graph.FadeOut(fade_duration, Easing.Out); + placeholder.FadeIn(fade_duration, Easing.Out); + ranks = null; + return; } + + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + var tempRanks = userRanks.Select((x, index) => new KeyValuePair(index, x)).SkipWhile(x => x.Value == 0).ToArray(); + ranks = tempRanks.Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + graph.SetStaticBallPosition(); + graph.FadeIn(fade_duration, Easing.Out); + } + else + { + graph.FadeOut(fade_duration, Easing.Out); + } + + updateRankTexts(); } private void updateRankTexts() { rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank"; performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - relativeText.Text = user.CountryRank > 0 ? $"{user.Country?.FullName} #{user.CountryRank:#,0}" : $"{user.Country?.FullName}"; + relativeText.Text = $"{user.Country?.FullName} #{user.CountryRank:#,0}"; } private void showHistoryRankTexts(int dayIndex) { - rankText.Text = ranks[dayIndex] > 0 ? $"#{ranks[dayIndex]:#,0}" : "no rank"; - dayIndex++; - relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago"; - //plural should be handled in a general way - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - if (graph != null) - { - graph.Colour = colours.Yellow; - // use logarithmic coordinates - graph.Values = ranks.Select(x => x == 0 ? float.MinValue : -(float)Math.Log(x)); - graph.SetStaticBallPosition(); - } - - updateRankTexts(); + rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; + relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{rankedDays - ranks[dayIndex].Key} days ago"; } public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) @@ -118,31 +151,35 @@ namespace osu.Game.Overlays.Profile protected override bool OnHover(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); - graph?.ShowBall(); + if (ranks != null && ranks.Length > 1) + { + graph.UpdateBallPosition(state.Mouse.Position.X); + graph.ShowBall(); + } return base.OnHover(state); } protected override bool OnMouseMove(InputState state) { - graph?.UpdateBallPosition(state.Mouse.Position.X); + if (ranks != null && ranks.Length > 1) + graph.UpdateBallPosition(state.Mouse.Position.X); + return base.OnMouseMove(state); } protected override void OnHoverLost(InputState state) { - if (graph != null) + if (ranks != null && ranks.Length > 1) { graph.HideBall(); updateRankTexts(); } + base.OnHoverLost(state); } private class RankChartLineGraph : LineGraph { - private const double fade_duration = 200; - private readonly CircularContainer staticBall; private readonly CircularContainer movingBall; From 5ffdaf711e59f1dd909f4ed1cf758fa54e4877ce Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 21 Nov 2017 10:30:12 +0300 Subject: [PATCH 228/239] Remove useless array --- osu.Game/Overlays/Profile/RankChart.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 3981939bf8..6547b0aa07 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -108,8 +108,7 @@ namespace osu.Game.Overlays.Profile } int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; - var tempRanks = userRanks.Select((x, index) => new KeyValuePair(index, x)).SkipWhile(x => x.Value == 0).ToArray(); - ranks = tempRanks.Where(x => x.Value != 0).ToArray(); + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); if (ranks.Length > 1) { From e48c515d5291ac8d045fa8433834a8731fee63aa Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 21 Nov 2017 10:36:53 +0300 Subject: [PATCH 229/239] CI fixes --- osu.Game/Overlays/Profile/ProfileHeader.cs | 6 +++--- osu.Game/Overlays/Profile/RankChart.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f0b441affb..40843e5b98 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -27,9 +27,9 @@ namespace osu.Game.Overlays.Profile private readonly OsuTextFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; - protected readonly RankChart rankGraph; + private readonly RankChart rankGraph; - private readonly Container coverContainer, chartContainer, supporterTag; + private readonly Container coverContainer, supporterTag; private readonly Sprite levelBadge; private readonly SpriteText levelText; private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; @@ -274,7 +274,7 @@ namespace osu.Game.Overlays.Profile } } }, - chartContainer = new Container + new Container { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomCentre, diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 6547b0aa07..c7a1c88818 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile private const float secondary_textsize = 13; private const float padding = 10; private const float fade_duration = 150; - private const int rankedDays = 88; + private const int ranked_days = 88; private readonly SpriteText rankText, performanceText, relativeText; private readonly RankChartLineGraph graph; @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Profile private void showHistoryRankTexts(int dayIndex) { rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{rankedDays - ranks[dayIndex].Key} days ago"; + relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) From 94a974a397973d26569372aaa7be95f0f4eb760d Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 21 Nov 2017 13:33:34 +0300 Subject: [PATCH 230/239] Use bindable for a user --- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Overlays/Profile/RankChart.cs | 30 ++++++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 40843e5b98..509e05329b 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -421,7 +421,7 @@ namespace osu.Game.Overlays.Profile gradeSPlus.DisplayCount = 0; gradeSSPlus.DisplayCount = 0; - rankGraph.Redraw(user); + rankGraph.User.Value = user; } } diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index c7a1c88818..0ff7914fa9 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Users; using System.Collections.Generic; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile { @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Profile private readonly OsuSpriteText placeholder; private KeyValuePair[] ranks; - private User user; + public Bindable User = new Bindable(); public RankChart() { @@ -81,6 +82,8 @@ namespace osu.Game.Overlays.Profile }; graph.OnBallMove += showHistoryRankTexts; + + User.ValueChanged += userChanged; } [BackgroundDependencyLoader] @@ -89,25 +92,21 @@ namespace osu.Game.Overlays.Profile graph.Colour = colours.Yellow; } - public void Redraw(User user) + private void userChanged(User newUser) { - if (this.user == null && user != null) - placeholder.FadeOut(fade_duration, Easing.Out); + placeholder.FadeTo(newUser == null ? 1 : 0, fade_duration, Easing.Out); - this.user = user; - - if (user == null) + if (newUser == null) { rankText.Text = string.Empty; performanceText.Text = string.Empty; relativeText.Text = string.Empty; graph.FadeOut(fade_duration, Easing.Out); - placeholder.FadeIn(fade_duration, Easing.Out); ranks = null; return; } - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; + int[] userRanks = newUser.RankHistory?.Data ?? new[] { newUser.Statistics.Rank }; ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); if (ranks.Length > 1) @@ -115,21 +114,18 @@ namespace osu.Game.Overlays.Profile graph.DefaultValueCount = ranks.Length; graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); graph.SetStaticBallPosition(); - graph.FadeIn(fade_duration, Easing.Out); - } - else - { - graph.FadeOut(fade_duration, Easing.Out); } + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + updateRankTexts(); } private void updateRankTexts() { - rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank"; - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - relativeText.Text = $"{user.Country?.FullName} #{user.CountryRank:#,0}"; + rankText.Text = User.Value.Statistics.Rank > 0 ? $"#{User.Value.Statistics.Rank:#,0}" : "no rank"; + performanceText.Text = User.Value.Statistics.PP != null ? $"{User.Value.Statistics.PP:#,0}pp" : string.Empty; + relativeText.Text = $"{User.Value.Country?.FullName} #{User.Value.CountryRank:#,0}"; } private void showHistoryRankTexts(int dayIndex) From 938c5feea4fddc4eae862cb3d9171b1fabdbe1d8 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Tue, 21 Nov 2017 13:42:42 +0300 Subject: [PATCH 231/239] Simplify interaction condition --- osu.Game/Overlays/Profile/RankChart.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 0ff7914fa9..92655f2993 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -146,7 +146,7 @@ namespace osu.Game.Overlays.Profile protected override bool OnHover(InputState state) { - if (ranks != null && ranks.Length > 1) + if (ranks?.Length > 1) { graph.UpdateBallPosition(state.Mouse.Position.X); graph.ShowBall(); @@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Profile protected override bool OnMouseMove(InputState state) { - if (ranks != null && ranks.Length > 1) + if (ranks?.Length > 1) graph.UpdateBallPosition(state.Mouse.Position.X); return base.OnMouseMove(state); @@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Profile protected override void OnHoverLost(InputState state) { - if (ranks != null && ranks.Length > 1) + if (ranks?.Length > 1) { graph.HideBall(); updateRankTexts(); From 8c50fa0b84d31ac19f05a90e6c1a71a3a7e13047 Mon Sep 17 00:00:00 2001 From: EVAST9919 Date: Wed, 22 Nov 2017 07:04:54 +0300 Subject: [PATCH 232/239] Add testcase --- osu.Game.Tests/Visual/TestCaseRankGraph.cs | 118 +++++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + osu.Game/Overlays/Profile/RankChart.cs | 1 - 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/TestCaseRankGraph.cs diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs new file mode 100644 index 0000000000..5cdb6269f7 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Overlays.Profile; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using OpenTK; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using System.Collections.Generic; +using System; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseRankGraph : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(RankChart) }; + + public TestCaseRankGraph() + { + RankChart graph; + + var data = new int[89]; + var dataWithZeros = new int[89]; + var smallData = new int[89]; + + for(int i = 0; i < 89; i++) + data[i] = dataWithZeros[i] = (i + 1) * 1000; + + for (int i = 20; i < 60; i++) + dataWithZeros[i] = 0; + + for (int i = 79; i < 89; i++) + smallData[i] = 100000 - i * 1000; + + Add(new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 150), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f) + }, + graph = new RankChart + { + RelativeSizeAxes = Axes.Both, + } + } + }); + + AddStep("null user", () => graph.User.Value = null); + AddStep("rank only", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 123456, + PP = 12345, + } + }; + }); + + AddStep("with rank history", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = data, + } + }; + }); + + AddStep("with zero values", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 89000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = dataWithZeros, + } + }; + }); + + AddStep("small amount of data", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Rank = 12000, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = smallData, + } + }; + }); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index e78b9d5c0e..31915b44cc 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -130,6 +130,7 @@ + diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankChart.cs index 92655f2993..73030acadd 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankChart.cs @@ -46,7 +46,6 @@ namespace osu.Game.Overlays.Profile Text = "No recent plays", TextSize = 14, Font = @"Exo2.0-RegularItalic", - Alpha = 0, Padding = new MarginPadding { Bottom = padding } }, rankText = new OsuSpriteText From 1a318c5c2b87f4fa4161552ac717c5846fe54999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 18:58:35 +0900 Subject: [PATCH 233/239] Improve LineGraph invalidation logic --- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 11 ++++++ osu.Game/Graphics/UserInterface/LineGraph.cs | 38 ++++++++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 697b84941c..7f2e7779d9 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -2,14 +2,25 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Overlays.Profile; using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseUserProfile : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(UserProfileOverlay), + typeof(RankChart), + typeof(LineGraph), + }; + public TestCaseUserProfile() { var profile = new UserProfileOverlay(); diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index aa9256e576..ea74c67f6c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Caching; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -47,7 +48,16 @@ namespace osu.Game.Graphics.UserInterface set { values = value.ToArray(); - applyPath(); + + float max = values.Max(), min = values.Min(); + if (MaxValue > max) max = MaxValue.Value; + if (MinValue < min) min = MinValue.Value; + + ActualMaxValue = max; + ActualMinValue = min; + + pathCached.Invalidate(); + maskingContainer.Width = 0; maskingContainer.ResizeWidthTo(1, transform_duration, Easing.OutQuint); } @@ -63,13 +73,28 @@ namespace osu.Game.Graphics.UserInterface }); } + private bool pending; + public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { - if ((invalidation & Invalidation.DrawSize) != 0) - applyPath(); + if ((invalidation & Invalidation.DrawSize) > 0) + pathCached.Invalidate(); + return base.Invalidate(invalidation, source, shallPropagate); } + private Cached pathCached = new Cached(); + + protected override void Update() + { + base.Update(); + if (!pathCached.IsValid) + { + applyPath(); + pathCached.Validate(); + } + } + private void applyPath() { path.ClearVertices(); @@ -77,13 +102,6 @@ namespace osu.Game.Graphics.UserInterface int count = Math.Max(values.Length, DefaultValueCount); - float max = values.Max(), min = values.Min(); - if (MaxValue > max) max = MaxValue.Value; - if (MinValue < min) min = MinValue.Value; - - ActualMaxValue = max; - ActualMinValue = min; - for (int i = 0; i < values.Length; i++) { float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; From 728d1cb7f65110cb3f49459e776eff311f647dfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 19:24:54 +0900 Subject: [PATCH 234/239] Formatting and more dynamically testable references --- osu.Game.Tests/Visual/TestCaseRankGraph.cs | 13 +++++++++---- osu.Game.Tests/Visual/TestCaseUserProfile.cs | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs index 5cdb6269f7..489fba6501 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs @@ -9,23 +9,28 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using System.Collections.Generic; using System; +using osu.Game.Graphics.UserInterface; using osu.Game.Users; namespace osu.Game.Tests.Visual { public class TestCaseRankGraph : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(RankChart) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RankGraph), + typeof(LineGraph) + }; public TestCaseRankGraph() { - RankChart graph; + RankGraph graph; var data = new int[89]; var dataWithZeros = new int[89]; var smallData = new int[89]; - for(int i = 0; i < 89; i++) + for (int i = 0; i < 89; i++) data[i] = dataWithZeros[i] = (i + 1) * 1000; for (int i = 20; i < 60; i++) @@ -46,7 +51,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - graph = new RankChart + graph = new RankGraph { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs index 7f2e7779d9..38d59f03b5 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual { typeof(ProfileHeader), typeof(UserProfileOverlay), - typeof(RankChart), + typeof(RankGraph), typeof(LineGraph), }; From 82909f6585eb372a91e37d3479d448300e7f217d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 19:25:18 +0900 Subject: [PATCH 235/239] RankChart -> RankGraph --- osu.Game/Overlays/Profile/ProfileHeader.cs | 4 ++-- .../Profile/{RankChart.cs => RankGraph.cs} | 24 +++++++------------ osu.Game/osu.Game.csproj | 2 +- 3 files changed, 11 insertions(+), 19 deletions(-) rename osu.Game/Overlays/Profile/{RankChart.cs => RankGraph.cs} (88%) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 509e05329b..a706799664 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Profile private readonly OsuTextFlowContainer infoTextLeft; private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; - private readonly RankChart rankGraph; + private readonly RankGraph rankGraph; private readonly Container coverContainer, supporterTag; private readonly Sprite levelBadge; @@ -287,7 +287,7 @@ namespace osu.Game.Overlays.Profile Colour = Color4.Black.Opacity(0.25f), RelativeSizeAxes = Axes.Both }, - rankGraph = new RankChart + rankGraph = new RankGraph { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game/Overlays/Profile/RankChart.cs b/osu.Game/Overlays/Profile/RankGraph.cs similarity index 88% rename from osu.Game/Overlays/Profile/RankChart.cs rename to osu.Game/Overlays/Profile/RankGraph.cs index 73030acadd..b5933d6fec 100644 --- a/osu.Game/Overlays/Profile/RankChart.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -19,7 +19,7 @@ using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile { - public class RankChart : Container + public class RankGraph : Container { private const float primary_textsize = 25; private const float secondary_textsize = 13; @@ -34,19 +34,18 @@ namespace osu.Game.Overlays.Profile private KeyValuePair[] ranks; public Bindable User = new Bindable(); - public RankChart() + public RankGraph() { Padding = new MarginPadding { Vertical = padding }; Children = new Drawable[] { placeholder = new OsuSpriteText { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Text = "No recent plays", TextSize = 14, Font = @"Exo2.0-RegularItalic", - Padding = new MarginPadding { Bottom = padding } }, rankText = new OsuSpriteText { @@ -75,6 +74,7 @@ namespace osu.Game.Overlays.Profile Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, + Height = 75, Y = -secondary_textsize, Alpha = 0, } @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Profile private void userChanged(User newUser) { - placeholder.FadeTo(newUser == null ? 1 : 0, fade_duration, Easing.Out); + placeholder.FadeIn(fade_duration, Easing.Out); if (newUser == null) { @@ -110,6 +110,8 @@ namespace osu.Game.Overlays.Profile if (ranks.Length > 1) { + placeholder.FadeOut(fade_duration, Easing.Out); + graph.DefaultValueCount = ranks.Length; graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); graph.SetStaticBallPosition(); @@ -133,16 +135,6 @@ namespace osu.Game.Overlays.Profile relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; } - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.DrawSize) != 0) - { - graph.Height = DrawHeight - padding * 2 - primary_textsize - secondary_textsize * 2; - } - - return base.Invalidate(invalidation, source, shallPropagate); - } - protected override bool OnHover(InputState state) { if (ranks?.Length > 1) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 199d51a8c1..1004d9dc95 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -499,7 +499,7 @@ - + From e5faced9bab698b14bcaf91b8eb1e73eb643650b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Dec 2017 19:25:59 +0900 Subject: [PATCH 236/239] Better variable name --- osu.Game/Overlays/Profile/RankGraph.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/RankGraph.cs b/osu.Game/Overlays/Profile/RankGraph.cs index b5933d6fec..d216789b1a 100644 --- a/osu.Game/Overlays/Profile/RankGraph.cs +++ b/osu.Game/Overlays/Profile/RankGraph.cs @@ -91,11 +91,11 @@ namespace osu.Game.Overlays.Profile graph.Colour = colours.Yellow; } - private void userChanged(User newUser) + private void userChanged(User user) { placeholder.FadeIn(fade_duration, Easing.Out); - if (newUser == null) + if (user == null) { rankText.Text = string.Empty; performanceText.Text = string.Empty; @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.Profile return; } - int[] userRanks = newUser.RankHistory?.Data ?? new[] { newUser.Statistics.Rank }; + int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Rank }; ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); if (ranks.Length > 1) From 6a29f6020af7f6bf6397d0bbc3e3ba981ee33acf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Dec 2017 21:42:54 +0900 Subject: [PATCH 237/239] Make HitObjects construct nested hit objects --- .../Objects/CatchHitObject.cs | 4 +- .../Objects/Drawable/DrawableJuiceStream.cs | 3 +- .../Objects/JuiceStream.cs | 156 +++++++++--------- .../Scoring/CatchScoreProcessor.cs | 3 +- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 42 ++--- osu.Game.Rulesets.Mania/Objects/Note.cs | 4 +- .../Scoring/ManiaScoreProcessor.cs | 2 +- .../Objects/Drawables/DrawableSlider.cs | 4 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 4 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 133 +++++++-------- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 +- .../Preprocessing/OsuDifficultyHitObject.cs | 2 +- .../Scoring/OsuPerformanceCalculator.cs | 2 +- .../Scoring/OsuScoreProcessor.cs | 5 +- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 47 ++---- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 4 +- .../Replays/TaikoAutoGenerator.cs | 3 +- .../Scoring/TaikoScoreProcessor.cs | 3 +- osu.Game/Rulesets/Objects/HitObject.cs | 25 ++- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 4 +- 22 files changed, 216 insertions(+), 242 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 38757d4928..9952e85c70 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -37,9 +37,9 @@ namespace osu.Game.Rulesets.Catch.Objects /// public CatchHitObject HyperDashTarget; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index bfb674d1b4..db0632a07d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.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.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using OpenTK; @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable RelativeChildSize = new Vector2(1, (float)HitObject.Duration) }; - foreach (CatchHitObject tick in s.Ticks) + foreach (CatchHitObject tick in s.NestedHitObjects.OfType()) { TinyDroplet tiny = tick as TinyDroplet; if (tiny != null) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index b0540b2a4d..8e496c3b0c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -11,8 +11,6 @@ using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using OpenTK; -using osu.Framework.Lists; -using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Rulesets.Catch.Objects { @@ -30,14 +28,9 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; - private ControlPointInfo controlPointInfo; - private BeatmapDifficulty difficulty; - - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); - this.controlPointInfo = controlPointInfo; - this.difficulty = difficulty; + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -48,95 +41,94 @@ namespace osu.Game.Rulesets.Catch.Objects TickDistance = scoringDistance / difficulty.SliderTickRate; } - public IEnumerable Ticks + protected override void CreateNestedHitObjects() { - get + base.CreateNestedHitObjects(); + + createTicks(); + } + + private void createTicks() + { + if (TickDistance == 0) + return; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + AddNested(new Fruit { - SortedList ticks = new SortedList((a, b) => a.StartTime.CompareTo(b.StartTime)); + Samples = Samples, + ComboColour = ComboColour, + StartTime = StartTime, + X = X + }); - if (TickDistance == 0) - return ticks; + for (var repeat = 0; repeat < RepeatCount; repeat++) + { + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; - var length = Curve.Distance; - var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - ticks.Add(new Fruit + for (var d = tickDistance; d <= length; d += tickDistance) { - Samples = Samples, - ComboColour = ComboColour, - StartTime = StartTime, - X = X - }); + if (d > length - minDistanceFromEnd) + break; - for (var repeat = 0; repeat < RepeatCount; repeat++) - { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + var timeProgress = d / length; + var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - for (var d = tickDistance; d <= length; d += tickDistance) + var lastTickTime = repeatStartTime + timeProgress * repeatDuration; + AddNested(new Droplet { - if (d > length - minDistanceFromEnd) - break; - - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; - - var lastTickTime = repeatStartTime + timeProgress * repeatDuration; - ticks.Add(new Droplet - { - StartTime = lastTickTime, - ComboColour = ComboColour, - X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - double tinyTickInterval = tickDistance / length * repeatDuration; - while (tinyTickInterval > 100) - tinyTickInterval /= 2; - - for (double t = 0; t < repeatDuration; t += tinyTickInterval) - { - double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; - - ticks.Add(new TinyDroplet - { - StartTime = repeatStartTime + t, - ComboColour = ComboColour, - X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - ticks.Add(new Fruit - { - Samples = Samples, + StartTime = lastTickTime, ComboColour = ComboColour, - StartTime = repeatStartTime + repeatDuration, - X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) }); } - if (controlPointInfo != null && difficulty != null) - ticks.ForEach(t => t.ApplyDefaults(controlPointInfo, difficulty)); + double tinyTickInterval = tickDistance / length * repeatDuration; + while (tinyTickInterval > 100) + tinyTickInterval /= 2; - return ticks; + for (double t = 0; t < repeatDuration; t += tinyTickInterval) + { + double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration; + + AddNested(new TinyDroplet + { + StartTime = repeatStartTime + t, + ComboColour = ComboColour, + X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo + { + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); + } + + AddNested(new Fruit + { + Samples = Samples, + ComboColour = ComboColour, + StartTime = repeatStartTime + repeatDuration, + X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + }); } + } + public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity; public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 0806c4b29d..3826fd1129 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Scoring AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); - foreach (var unused in stream.Ticks) + foreach (var unused in stream.NestedHitObjects.OfType()) AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); continue; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 237df72480..7b207ca229 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } }); - foreach (var tick in HitObject.Ticks) + foreach (var tick in HitObject.NestedHitObjects.OfType()) { var drawableTick = new DrawableHoldNoteTick(tick) { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 2a62eaa2ef..f72bed3142 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.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.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Types; @@ -63,15 +62,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; - private ControlPointInfo controlPointInfo; - private BeatmapDifficulty difficulty; - - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); - - this.controlPointInfo = controlPointInfo; - this.difficulty = difficulty; + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; @@ -80,34 +73,27 @@ namespace osu.Game.Rulesets.Mania.Objects Tail.ApplyDefaults(controlPointInfo, difficulty); } - /// - /// The scoring scoring ticks of the hold note. - /// - public IEnumerable Ticks => ticks ?? (ticks = createTicks()); - private List ticks; - - private List createTicks() + protected override void CreateNestedHitObjects() { - var ret = new List(); + base.CreateNestedHitObjects(); + createTicks(); + } + + private void createTicks() + { if (tickSpacing == 0) - return ret; + return; for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) { - var tick = new HoldNoteTick + AddNested(new HoldNoteTick { StartTime = t, Column = Column - }; - - if (controlPointInfo != null && difficulty != null) - tick.ApplyDefaults(controlPointInfo, difficulty); - - ret.Add(tick); + }); } - return ret; } /// @@ -121,9 +107,9 @@ namespace osu.Game.Rulesets.Mania.Objects /// private const double release_window_lenience = 1.5; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindows *= release_window_lenience; } diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index c4d5a13352..51a9a18afa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Mania.Objects [JsonIgnore] public HitWindows HitWindows { get; protected set; } = new HitWindows(); - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindows = new HitWindows(difficulty.OverallDifficulty); } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 9b8ebe0070..012137f555 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Scoring AddJudgement(new ManiaJudgement { Result = HitResult.Perfect }); // Ticks - int tickCount = holdNote.Ticks.Count(); + int tickCount = holdNote.NestedHitObjects.OfType().Count(); for (int i = 0; i < tickCount; i++) AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a91d846aa7..57c1ffab00 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(initialCircle); var repeatDuration = s.Curve.Distance / s.Velocity; - foreach (var tick in s.Ticks) + foreach (var tick in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration; var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(drawableTick); } - foreach (var repeatPoint in s.RepeatPoints) + foreach (var repeatPoint in s.NestedHitObjects.OfType()) { var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration; var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index e6bfd8a277..7532387aa2 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Osu.Objects return HitResult.Miss; } - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index af50667d42..24398c2235 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -74,15 +74,9 @@ namespace osu.Game.Rulesets.Osu.Objects public double Velocity; public double TickDistance; - private ControlPointInfo controlPointInfo; - private BeatmapDifficulty difficulty; - - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); - - this.controlPointInfo = controlPointInfo; - this.difficulty = difficulty; + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); @@ -105,85 +99,78 @@ namespace osu.Game.Rulesets.Osu.Objects public int RepeatAt(double progress) => (int)(progress * RepeatCount); - public IEnumerable Ticks + protected override void CreateNestedHitObjects() { - get + base.CreateNestedHitObjects(); + + createTicks(); + createRepeatPoints(); + } + + private void createTicks() + { + if (TickDistance == 0) return; + + var length = Curve.Distance; + var tickDistance = Math.Min(TickDistance, length); + var repeatDuration = length / Velocity; + + var minDistanceFromEnd = Velocity * 0.01; + + for (var repeat = 0; repeat < RepeatCount; repeat++) { - if (TickDistance == 0) yield break; + var repeatStartTime = StartTime + repeat * repeatDuration; + var reversed = repeat % 2 == 1; - var length = Curve.Distance; - var tickDistance = Math.Min(TickDistance, length); - var repeatDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - for (var repeat = 0; repeat < RepeatCount; repeat++) + for (var d = tickDistance; d <= length; d += tickDistance) { - var repeatStartTime = StartTime + repeat * repeatDuration; - var reversed = repeat % 2 == 1; + if (d > length - minDistanceFromEnd) + break; - for (var d = tickDistance; d <= length; d += tickDistance) + var distanceProgress = d / length; + var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; + + AddNested(new SliderTick { - if (d > length - minDistanceFromEnd) - break; - - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - var ret = new SliderTick + RepeatIndex = repeat, + StartTime = repeatStartTime + timeProgress * repeatDuration, + Position = Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + ComboColour = ComboColour, + Samples = new SampleInfoList(Samples.Select(s => new SampleInfo { - RepeatIndex = repeat, - StartTime = repeatStartTime + timeProgress * repeatDuration, - Position = Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - ComboColour = ComboColour, - Samples = new SampleInfoList(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }; - - if (controlPointInfo != null && difficulty != null) - ret.ApplyDefaults(controlPointInfo, difficulty); - - yield return ret; - } + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + })) + }); } } } - public IEnumerable RepeatPoints + + private void createRepeatPoints() { - get + var length = Curve.Distance; + var repeatPointDistance = Math.Min(Distance, length); + var repeatDuration = length / Velocity; + + for (var repeat = 1; repeat < RepeatCount; repeat++) { - var length = Curve.Distance; - var repeatPointDistance = Math.Min(Distance, length); - var repeatDuration = length / Velocity; - - for (var repeat = 1; repeat < RepeatCount; repeat++) + for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) { - for (var d = repeatPointDistance; d <= length; d += repeatPointDistance) + var repeatStartTime = StartTime + repeat * repeatDuration; + var distanceProgress = d / length; + + AddNested(new RepeatPoint { - var repeatStartTime = StartTime + repeat * repeatDuration; - var distanceProgress = d / length; - - var ret = new RepeatPoint - { - RepeatIndex = repeat, - StartTime = repeatStartTime, - Position = Curve.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - ComboColour = ComboColour, - }; - - if (controlPointInfo != null && difficulty != null) - ret.ApplyDefaults(controlPointInfo, difficulty); - - yield return ret; - } + RepeatIndex = repeat, + StartTime = repeatStartTime, + Position = Curve.PositionAt(distanceProgress), + StackHeight = StackHeight, + Scale = Scale, + ComboColour = ComboColour, + }); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index c4f5dfe97a..7d1bd9239d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -19,9 +19,9 @@ namespace osu.Game.Rulesets.Osu.Objects public override bool NewCombo => true; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5)); diff --git a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs index 89821a10fb..11c0df0c8c 100644 --- a/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/OsuDifficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing } }); - var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t); + var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime); foreach (var time in scoringTimes) computeVertex(time); computeVertex(slider.EndTime); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs index cd6b6c5e27..2cf321da50 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuPerformanceCalculator.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Scoring countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); beatmapMaxCombo = Beatmap.HitObjects.Count; - beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.RepeatCount + s.Ticks.Count()); + beatmapMaxCombo += Beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count) + 1; } public override double Calculate(Dictionary categoryRatings = null) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 6e5dd18c46..ad9737af52 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.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.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; @@ -39,11 +40,11 @@ namespace osu.Game.Rulesets.Osu.Scoring AddJudgement(new OsuJudgement { Result = HitResult.Great }); // Ticks - foreach (var unused in slider.Ticks) + foreach (var unused in slider.NestedHitObjects.OfType()) AddJudgement(new OsuJudgement { Result = HitResult.Great }); //Repeats - foreach (var unused in slider.RepeatPoints) + foreach (var unused in slider.NestedHitObjects.OfType()) AddJudgement(new OsuJudgement { Result = HitResult.Great }); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2396f3bf91..75e988ced6 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeChildSize = new Vector2((float)HitObject.Duration, 1) }); - foreach (var tick in drumRoll.Ticks) + foreach (var tick in drumRoll.NestedHitObjects.OfType()) { var newTick = new DrawableDrumRollTick(tick); newTick.OnJudgement += onTickJudgement; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index ff20a16c7b..4104b59979 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,7 +3,6 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Collections.Generic; using System.Linq; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -37,52 +36,40 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double RequiredGreatHits { get; protected set; } - /// - /// Total number of drum roll ticks. - /// - public int TotalTicks => Ticks.Count(); - - /// - /// Initializes the drum roll ticks if not initialized and returns them. - /// - public IEnumerable Ticks => ticks ?? (ticks = createTicks()); - - private List ticks; - /// /// The length (in milliseconds) between ticks of this drumroll. /// Half of this value is the hit window of the ticks. /// private double tickSpacing = 100; - private ControlPointInfo controlPointInfo; - private BeatmapDifficulty difficulty; - - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); - this.controlPointInfo = controlPointInfo; - this.difficulty = difficulty; + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); tickSpacing = timingPoint.BeatLength / TickRate; - RequiredGoodHits = TotalTicks * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); - RequiredGreatHits = TotalTicks * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); + RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * difficulty.OverallDifficulty); + RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * difficulty.OverallDifficulty); } - private List createTicks() + protected override void CreateNestedHitObjects() { - var ret = new List(); + base.CreateNestedHitObjects(); + createTicks(); + } + + private void createTicks() + { if (tickSpacing == 0) - return ret; + return; bool first = true; for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { - var tick = new DrumRollTick + AddNested(new DrumRollTick { FirstTick = first, TickSpacing = tickSpacing, @@ -94,16 +81,10 @@ namespace osu.Game.Rulesets.Taiko.Objects Name = @"slidertick", Volume = s.Volume })) - }; + }); - if (controlPointInfo != null && difficulty != null) - tick.ApplyDefaults(controlPointInfo, difficulty); - - ret.Add(tick); first = false; } - - return ret; } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 03b9be4157..1b9b44fd99 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -23,9 +23,9 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public double HitWindowMiss = 95; - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20); HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50); diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index c3e5597f43..df1a19267f 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; @@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Taiko.Replays } else if (drumRoll != null) { - foreach (var tick in drumRoll.Ticks) + foreach (var tick in drumRoll.NestedHitObjects.OfType()) { Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2)); hitButton = !hitButton; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 0048566b15..df9ce5e2eb 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.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.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; @@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring } else if (obj is DrumRoll) { - for (int i = 0; i < ((DrumRoll)obj).TotalTicks; i++) + for (int i = 0; i < ((DrumRoll)obj).NestedHitObjects.OfType().Count(); i++) { AddJudgement(new TaikoDrumRollTickJudgement { Result = HitResult.Great }); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 66e34eac32..9e331dfea9 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.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.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Lists; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -40,12 +43,26 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public bool Kiai { get; private set; } + private readonly SortedList nestedHitObjects = new SortedList((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); + + [JsonIgnore] + public IReadOnlyList NestedHitObjects => nestedHitObjects; + /// /// Applies default values to this HitObject. /// /// The control points. /// The difficulty settings to use. - public virtual void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + ApplyDefaultsToSelf(controlPointInfo, difficulty); + + nestedHitObjects.Clear(); + CreateNestedHitObjects(); + nestedHitObjects.ForEach(h => h.ApplyDefaults(controlPointInfo, difficulty)); + } + + protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { SoundControlPoint soundPoint = controlPointInfo.SoundPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); @@ -53,5 +70,11 @@ namespace osu.Game.Rulesets.Objects Kiai = effectPoint.KiaiMode; SoundControlPoint = soundPoint; } + + protected virtual void CreateNestedHitObjects() + { + } + + protected void AddNested(HitObject hitObject) => nestedHitObjects.Add(hitObject); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 479cacc52b..698b74cc28 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -46,9 +46,9 @@ namespace osu.Game.Rulesets.Objects.Legacy throw new NotImplementedException(); } - public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - base.ApplyDefaults(controlPointInfo, difficulty); + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); From d54b74e13a8ed2d8d5230f611870b2853dea7607 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Dec 2017 21:44:12 +0900 Subject: [PATCH 238/239] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 675745afa1..08f85f9bf9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 675745afa19c0ceaa96a43edf76ee295ba394c61 +Subproject commit 08f85f9bf9a7376aec8dfcde8c7c96d267d8c295 From def2e5bd179857d2c56a41a056c2a23a385a8437 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 Dec 2017 22:15:22 +0900 Subject: [PATCH 239/239] Make editor discard approach circles Temporary solution for now. --- osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs | 2 ++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs index d5fc1b606b..0d7fa6c334 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditPlayfield.cs @@ -9,5 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit public class OsuEditPlayfield : OsuPlayfield { protected override CursorContainer CreateCursor() => null; + + protected override bool ProxyApproachCircles => false; } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f03acb2fa0..0dc6feee75 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -25,6 +25,10 @@ namespace osu.Game.Rulesets.Osu.UI public override bool ProvidingUserCursor => true; + // Todo: This should not be a thing, but is currently required for the editor + // https://github.com/ppy/osu-framework/issues/1283 + protected virtual bool ProxyApproachCircles => true; + public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); public override Vector2 Size @@ -80,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI h.Depth = (float)h.HitObject.StartTime; var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null) + if (c != null && ProxyApproachCircles) approachCircles.Add(c.ProxiedLayer.CreateProxy()); base.Add(h);