From cd7c03c13a0519c069b531b4c7c127539b85871f Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 11 Jul 2019 16:44:48 +0300 Subject: [PATCH 001/162] Add genre and language sections to beatmapset overlay --- .../Online/TestSceneBeatmapSetOverlay.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs | 20 ++++++++++++++++++ osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 10 +++++++++ osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs | 21 +++++++++++++++++++ .../API/Requests/Responses/APIBeatmapSet.cs | 8 +++++++ osu.Game/Overlays/BeatmapSet/Info.cs | 21 ++++++++++++++++++- 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs create mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index daee419b52..d87d9910c3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Online HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), + Language = BeatmapSetOnlineLanguage.English, + Genre = BeatmapSetOnlineGenre.Rock, }, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs new file mode 100644 index 0000000000..cea9d94987 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Beatmaps +{ + public enum BeatmapSetOnlineGenre + { + Any = 0, + Unspecified = 1, + VideoGame = 2, + Anime = 3, + Rock = 4, + Pop = 5, + Other = 6, + Novelty = 7, + // genre_id 8 doesnt exist + HipHop = 9, + Electronic = 10 + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index ea3f0b61b9..2d2f06a57c 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -70,6 +70,16 @@ namespace osu.Game.Beatmaps /// The availability of this beatmap set. /// public BeatmapSetOnlineAvailability Availability { get; set; } + + /// + /// Beatmap set's song genre. + /// + public BeatmapSetOnlineGenre Genre { get; set; } + + /// + /// Beatmap set's song language. + /// + public BeatmapSetOnlineLanguage Language { get; set; } } public class BeatmapSetOnlineCovers diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs new file mode 100644 index 0000000000..0fb0dec904 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Beatmaps +{ + public enum BeatmapSetOnlineLanguage + { + Any = 0, + Other = 1, + English = 2, + Japanese = 3, + Chinese = 4, + Instrumental = 5, + Korean = 6, + French = 7, + German = 8, + Swedish = 9, + Spanish = 10, + Italian = 11 + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 200a705500..9ca2cb0b81 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -66,6 +66,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"availability")] private BeatmapSetOnlineAvailability availability { get; set; } + [JsonProperty(@"genre_id")] + private BeatmapSetOnlineGenre genre { get; set; } + + [JsonProperty(@"language_id")] + private BeatmapSetOnlineLanguage language { get; set; } + [JsonProperty(@"beatmaps")] private IEnumerable beatmaps { get; set; } @@ -91,6 +97,8 @@ namespace osu.Game.Online.API.Requests.Responses Ranked = ranked, LastUpdated = lastUpdated, Availability = availability, + Genre = genre, + Language = language }, Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), }; diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 44827f0a0c..27ca58fbd3 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.BeatmapSet public Info() { - MetadataSection source, tags; + MetadataSection source, tags, genre, language; RelativeSizeAxes = Axes.X; Height = 220; Masking = true; @@ -88,6 +88,19 @@ namespace osu.Game.Overlays.BeatmapSet Children = new[] { source = new MetadataSection("Source"), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Width = 0.5f, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + genre = new MetadataSection("Genre"), + language = new MetadataSection("Language"), + } + }, tags = new MetadataSection("Tags"), }, }, @@ -119,6 +132,12 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; + + var genreId = b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified; + genre.Text = genreId.ToString(); + + var languageId = b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other; + language.Text = languageId.ToString(); }; } From 1e04fcc6b54d8dab5d24478794ad27478cf38b80 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 11 Jul 2019 17:47:09 +0300 Subject: [PATCH 002/162] Apply fixes --- osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs | 20 ------- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 56 ++++++++++++++++++- osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs | 21 ------- osu.Game/Overlays/BeatmapSet/Info.cs | 9 +-- 4 files changed, 57 insertions(+), 49 deletions(-) delete mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs delete mode 100644 osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs b/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs deleted file mode 100644 index cea9d94987..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetOnlineGenre.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Beatmaps -{ - public enum BeatmapSetOnlineGenre - { - Any = 0, - Unspecified = 1, - VideoGame = 2, - Anime = 3, - Rock = 4, - Pop = 5, - Other = 6, - Novelty = 7, - // genre_id 8 doesnt exist - HipHop = 9, - Electronic = 10 - } -} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 2d2f06a57c..4cb50b59fb 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; using Newtonsoft.Json; namespace osu.Game.Beatmaps @@ -72,16 +73,67 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineAvailability Availability { get; set; } /// - /// Beatmap set's song genre. + /// The song genre of this beatmap set. /// public BeatmapSetOnlineGenre Genre { get; set; } /// - /// Beatmap set's song language. + /// The song language of this beatmap set. /// public BeatmapSetOnlineLanguage Language { get; set; } } + public enum BeatmapSetOnlineGenre + { + [Description("Any")] + Any = 0, + + [Description("Unspecified")] + Unspecified = 1, + + [Description("Video Game")] + VideoGame = 2, + + [Description("Anime")] + Anime = 3, + + [Description("Rock")] + Rock = 4, + + [Description("Pop")] + Pop = 5, + + [Description("Other")] + Other = 6, + + [Description("Novelty")] + Novelty = 7, + + // genre_id 8 doesn't exist + + [Description("Hip-Hop")] + HipHop = 9, + + [Description("Electronic")] + Electronic = 10 + } + + public enum BeatmapSetOnlineLanguage + { + Any, + Other, + English, + Japanese, + Chinese, + Instrumental, + Korean, + French, + German, + Swedish, + Spanish, + Italian + } + public class BeatmapSetOnlineCovers { public string CoverLowRes { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs b/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs deleted file mode 100644 index 0fb0dec904..0000000000 --- a/osu.Game/Beatmaps/BeatmapSetOnlineLanguage.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Beatmaps -{ - public enum BeatmapSetOnlineLanguage - { - Any = 0, - Other = 1, - English = 2, - Japanese = 3, - Chinese = 4, - Instrumental = 5, - Korean = 6, - French = 7, - German = 8, - Swedish = 9, - Spanish = 10, - Italian = 11 - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 27ca58fbd3..0e4e9db948 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -132,12 +133,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - - var genreId = b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified; - genre.Text = genreId.ToString(); - - var languageId = b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other; - language.Text = languageId.ToString(); + genre.Text = (b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified).GetDescription(); + language.Text = (b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other).ToString(); }; } From 195609816674f0428a281d12e4c0757cba08169a Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 30 Jul 2019 13:58:08 +0900 Subject: [PATCH 003/162] Add a fallback for humanizer localization failure --- osu.Game/Graphics/DrawableDate.cs | 4 +-- .../BeatmapSet/Scores/TopScoreUserSection.cs | 4 +-- osu.Game/Utils/HumanizerUtils.cs | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Utils/HumanizerUtils.cs diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 125c994c92..533f02af7b 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Sprites; +using osu.Game.Utils; namespace osu.Game.Graphics { @@ -71,7 +71,7 @@ namespace osu.Game.Graphics Scheduler.AddDelayed(updateTimeWithReschedule, timeUntilNextUpdate); } - protected virtual string Format() => Date.Humanize(); + protected virtual string Format() => HumanizerUtils.Humanize(Date); private void updateTime() => Text = Format(); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index ffc39e5af2..38a909411a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; using osu.Game.Scoring; using osu.Game.Users.Drawables; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { avatar.User = value.User; flag.Country = value.User.Country; - date.Text = $@"achieved {value.Date.Humanize()}"; + date.Text = $@"achieved {HumanizerUtils.Humanize(value.Date)}"; usernameText.Clear(); usernameText.AddUserLink(value.User); diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs new file mode 100644 index 0000000000..398c76a09f --- /dev/null +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -0,0 +1,36 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using Humanizer; + +namespace osu.Game.Utils +{ + public static class HumanizerUtils + { + /// + /// Humanizes a string using the system culture, then falls back if one cannot be found. + /// + /// A localization lookup failure will throw an exception of type + /// + /// + /// The time to humanize. + /// A humanized string of the given time. + public static string Humanize(DateTimeOffset dateTimeOffset) + { + string offset; + + try + { + offset = dateTimeOffset.Humanize(); + } + catch (ArgumentException) + { + offset = dateTimeOffset.Humanize(culture: new CultureInfo("en-US")); + } + + return offset; + } + } +} From 5073bce2dcbf8a5bce4f33b06036822524494082 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 10:47:52 +0300 Subject: [PATCH 004/162] Basic request implementation --- .../Requests/GetUserKudosuHistoryRequest.cs | 21 +++++++++++++++++++ .../Requests/Responses/APIKudosuHistory.cs | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs new file mode 100644 index 0000000000..e90e297672 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetUserKudosuHistoryRequest : PaginatedAPIRequest> + { + private readonly long userId; + + public GetUserKudosuHistoryRequest(long userId, int page = 0, int itemsPerPage = 5) + : base(page, itemsPerPage) + { + this.userId = userId; + } + + protected override string Target => $"users/{userId}/kudosu"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs new file mode 100644 index 0000000000..949ce200d2 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIKudosuHistory + { + [JsonProperty("id")] + public int ID; + + [JsonProperty("createdAt")] + public DateTimeOffset CreatedAt; + + [JsonProperty("amount")] + public int Amount; + } +} From cf2a9db8e048abc2e788c8ee210a2d7b958c9010 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 11:14:38 +0300 Subject: [PATCH 005/162] Implement basic layout for kudosu history --- .../Kudosu/DrawableKudosuHistoryItem.cs | 56 +++++++++++++++++++ .../Kudosu/PaginatedKudosuHistoryContainer.cs | 51 +++++++++++++++++ .../Profile/Sections/KudosuSection.cs | 4 +- 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs create mode 100644 osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs new file mode 100644 index 0000000000..1671073242 --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Profile.Sections.Kudosu +{ + public class DrawableKudosuHistoryItem : DrawableProfileRow + { + private readonly APIKudosuHistory historyItem; + private LinkFlowContainer content; + + public DrawableKudosuHistoryItem(APIKudosuHistory historyItem) + { + this.historyItem = historyItem; + } + + [BackgroundDependencyLoader] + private void load() + { + LeftFlowContainer.Padding = new MarginPadding { Left = 10 }; + + LeftFlowContainer.Add(content = new LinkFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }); + + RightFlowContainer.Add(new DrawableDate(historyItem.CreatedAt) + { + Font = OsuFont.GetFont(size: 13), + Colour = OsuColour.Gray(0xAA), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }); + + var formatted = createMessage(); + + content.AddLinks(formatted.Text, formatted.Links); + } + + protected override Drawable CreateLeftVisual() => new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + }; + + private MessageFormatter.MessageFormatterResult createMessage() => MessageFormatter.FormatText($@"{historyItem.Amount}"); + } +} diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs new file mode 100644 index 0000000000..29b1d3c5aa --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests; +using osu.Game.Users; +using System.Linq; +using osu.Framework.Bindables; + +namespace osu.Game.Overlays.Profile.Sections.Kudosu +{ + public class PaginatedKudosuHistoryContainer : PaginatedContainer + { + private GetUserKudosuHistoryRequest request; + + public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing) + : base(user, header, missing) + { + ItemsPerPage = 5; + } + + protected override void ShowMore() + { + request = new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); + request.Success += items => Schedule(() => + { + MoreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; + + if (!items.Any() && VisiblePages == 1) + { + MissingText.Show(); + return; + } + + MissingText.Hide(); + + foreach (var item in items) + ItemsContainer.Add(new DrawableKudosuHistoryItem(item)); + }); + + Api.Queue(request); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + request?.Cancel(); + } + } +} diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index a17b68933c..9ccce7d837 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Game.Overlays.Profile.Sections.Kudosu; namespace osu.Game.Overlays.Profile.Sections @@ -13,9 +14,10 @@ namespace osu.Game.Overlays.Profile.Sections public KudosuSection() { - Children = new[] + Children = new Drawable[] { new KudosuInfo(User), + new PaginatedKudosuHistoryContainer(User, null, @"This user hasn't received any kudosu!"), }; } } From 093359c13bc05d590e227a9db7615f4dc287be57 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 9 Aug 2019 11:19:14 +0300 Subject: [PATCH 006/162] fix incorrect json property --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 949ce200d2..271dcc320e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("id")] public int ID; - [JsonProperty("createdAt")] + [JsonProperty("created_at")] public DateTimeOffset CreatedAt; [JsonProperty("amount")] From fd334e0319729576afb81d600b71128f1fff2970 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 14:57:21 +0300 Subject: [PATCH 007/162] Implement basic layout for AccuracyBar --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 19 +++++++++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 osu.Game/Screens/Play/HUD/AccuracyBar.cs diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs new file mode 100644 index 0000000000..8b85014b2f --- /dev/null +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; + +namespace osu.Game.Screens.Play.HUD +{ + public class AccuracyBar : Container + { + public AccuracyBar(bool mirrored) + { + } + + public void OnNewJudgement(JudgementResult judgement) + { + } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 43b9491750..88ad57c175 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + public readonly AccuracyBar LeftAccuracyBar; + public readonly AccuracyBar RightAccuracyBar; public Bindable ShowHealthbar = new Bindable(true); @@ -84,6 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), + LeftAccuracyBar = CreateAccuracyBar(false), + RightAccuracyBar = CreateAccuracyBar(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -256,6 +260,15 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; + protected virtual AccuracyBar CreateAccuracyBar(bool mirrored = true) => new AccuracyBar(mirrored) + { + Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + Height = 300, + Margin = new MarginPadding { Horizontal = 20 } + }; + protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) @@ -265,6 +278,12 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); + if (LeftAccuracyBar != null) + processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; + + if (RightAccuracyBar != null) + processor.NewJudgement += RightAccuracyBar.OnNewJudgement; + if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; } From ed409d113b2e02bc49c252c4074468393bc6a8ae Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 15:53:15 +0300 Subject: [PATCH 008/162] Add judgement lines generator --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 42 +++++++++++++++++++++++- osu.Game/Screens/Play/HUDOverlay.cs | 4 +-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 8b85014b2f..8b14bc47eb 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -1,19 +1,59 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; +using osuTK.Graphics; +using osuTK; namespace osu.Game.Screens.Play.HUD { public class AccuracyBar : Container { - public AccuracyBar(bool mirrored) + private const int bar_width = 5; + private const int bar_height = 250; + private const int spacing = 3; + + private readonly bool mirrored; + + public AccuracyBar(bool mirrored = false) { + this.mirrored = mirrored; + + Size = new Vector2(bar_width, bar_height); + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }; } public void OnNewJudgement(JudgementResult judgement) { + Container judgementLine; + + Add(judgementLine = CreateJudgementLine(judgement.TimeOffset)); + + judgementLine.FadeOut(5000, Easing.OutQuint); + judgementLine.Expire(); } + + protected virtual Container CreateJudgementLine(double offset) => new CircularContainer + { + Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Masking = true, + Size = new Vector2(10, 2), + RelativePositionAxes = Axes.Y, + Y = (float)offset / bar_height, + X = mirrored ? spacing : -spacing, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 88ad57c175..3fafc21ea8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -264,9 +264,7 @@ namespace osu.Game.Screens.Play { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - Height = 300, - Margin = new MarginPadding { Horizontal = 20 } + Margin = new MarginPadding { Horizontal = 30 } }; protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 0a255fe4d1750440e02fc429f40021f7df9b55c8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 16:38:03 +0300 Subject: [PATCH 009/162] Add moving arrow --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 46 ++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 8b14bc47eb..3c13111b22 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; using osuTK.Graphics; using osuTK; +using osu.Framework.Graphics.Sprites; +using System.Collections.Generic; namespace osu.Game.Screens.Play.HUD { @@ -17,16 +19,31 @@ namespace osu.Game.Screens.Play.HUD private const int spacing = 3; private readonly bool mirrored; + private readonly SpriteIcon arrow; + private readonly List judgementOffsets = new List(); public AccuracyBar(bool mirrored = false) { this.mirrored = mirrored; Size = new Vector2(bar_width, bar_height); - Child = new Box + + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + arrow = new SpriteIcon + { + Anchor = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, + X = mirrored ? -spacing : spacing, + RelativePositionAxes = Axes.Y, + Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(10), + } }; } @@ -34,20 +51,22 @@ namespace osu.Game.Screens.Play.HUD { Container judgementLine; - Add(judgementLine = CreateJudgementLine(judgement.TimeOffset)); + Add(judgementLine = CreateJudgementLine(judgement)); judgementLine.FadeOut(5000, Easing.OutQuint); judgementLine.Expire(); + + arrow.MoveToY(calculateArrowPosition(judgement) / bar_height, 500, Easing.OutQuint); } - protected virtual Container CreateJudgementLine(double offset) => new CircularContainer + protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, Masking = true, Size = new Vector2(10, 2), RelativePositionAxes = Axes.Y, - Y = (float)offset / bar_height, + Y = (float)judgement.TimeOffset / bar_height, X = mirrored ? spacing : -spacing, Child = new Box { @@ -55,5 +74,20 @@ namespace osu.Game.Screens.Play.HUD Colour = Color4.White, } }; + + private float calculateArrowPosition(JudgementResult judgement) + { + if (judgementOffsets.Count > 5) + judgementOffsets.RemoveAt(0); + + judgementOffsets.Add(judgement.TimeOffset); + + double offsets = 0; + + foreach (var offset in judgementOffsets) + offsets += offset; + + return (float)offsets / judgementOffsets.Count; + } } } From 2a35c3c3e24708ef174f5482250d4de773ccb6d9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:04:54 +0300 Subject: [PATCH 010/162] Calculate real position for judgement lines --- osu.Game/Screens/Play/HUD/AccuracyBar.cs | 25 ++++++++++++++++++++---- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/AccuracyBar.cs index 3c13111b22..590e9ca4d9 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/AccuracyBar.cs @@ -9,6 +9,10 @@ using osuTK.Graphics; using osuTK; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Game.Rulesets.Objects; +using osu.Game.Beatmaps; +using osu.Framework.Bindables; +using osu.Framework.Allocation; namespace osu.Game.Screens.Play.HUD { @@ -18,6 +22,11 @@ namespace osu.Game.Screens.Play.HUD private const int bar_height = 250; private const int spacing = 3; + public HitWindows HitWindows { get; set; } + + [Resolved] + private Bindable beatmap { get; set; } + private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly List judgementOffsets = new List(); @@ -47,6 +56,12 @@ namespace osu.Game.Screens.Play.HUD }; } + protected override void LoadComplete() + { + base.LoadComplete(); + HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + } + public void OnNewJudgement(JudgementResult judgement) { Container judgementLine; @@ -56,7 +71,7 @@ namespace osu.Game.Screens.Play.HUD judgementLine.FadeOut(5000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(calculateArrowPosition(judgement) / bar_height, 500, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -66,7 +81,7 @@ namespace osu.Game.Screens.Play.HUD Masking = true, Size = new Vector2(10, 2), RelativePositionAxes = Axes.Y, - Y = (float)judgement.TimeOffset / bar_height, + Y = getRelativeJudgementPosition(judgement.TimeOffset), X = mirrored ? spacing : -spacing, Child = new Box { @@ -75,7 +90,9 @@ namespace osu.Game.Screens.Play.HUD } }; - private float calculateArrowPosition(JudgementResult judgement) + private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Miss); + + private double calculateArrowPosition(JudgementResult judgement) { if (judgementOffsets.Count > 5) judgementOffsets.RemoveAt(0); @@ -87,7 +104,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var offset in judgementOffsets) offsets += offset; - return (float)offsets / judgementOffsets.Count; + return offsets / judgementOffsets.Count; } } } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 3fafc21ea8..d30a32343a 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -277,10 +277,16 @@ namespace osu.Game.Screens.Play HealthDisplay?.Current.BindTo(processor.Health); if (LeftAccuracyBar != null) + { processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; + LeftAccuracyBar.HitWindows = processor.CreateHitWindows(); + } if (RightAccuracyBar != null) + { processor.NewJudgement += RightAccuracyBar.OnNewJudgement; + RightAccuracyBar.HitWindows = processor.CreateHitWindows(); + } if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; From 177a317a48b34d73643b946a90db01fe245e24c9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:11:49 +0300 Subject: [PATCH 011/162] rename AccuracyBar to HitErrorDisplay --- .../{AccuracyBar.cs => HitErrorDisplay.cs} | 4 ++-- osu.Game/Screens/Play/HUDOverlay.cs | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Screens/Play/HUD/{AccuracyBar.cs => HitErrorDisplay.cs} (97%) diff --git a/osu.Game/Screens/Play/HUD/AccuracyBar.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs similarity index 97% rename from osu.Game/Screens/Play/HUD/AccuracyBar.cs rename to osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 590e9ca4d9..2d33cb08a0 100644 --- a/osu.Game/Screens/Play/HUD/AccuracyBar.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -16,7 +16,7 @@ using osu.Framework.Allocation; namespace osu.Game.Screens.Play.HUD { - public class AccuracyBar : Container + public class HitErrorDisplay : Container { private const int bar_width = 5; private const int bar_height = 250; @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Play.HUD private readonly SpriteIcon arrow; private readonly List judgementOffsets = new List(); - public AccuracyBar(bool mirrored = false) + public HitErrorDisplay(bool mirrored = false) { this.mirrored = mirrored; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d30a32343a..a9a469486e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,8 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly AccuracyBar LeftAccuracyBar; - public readonly AccuracyBar RightAccuracyBar; + public readonly HitErrorDisplay LeftHitErrorDisplay; + public readonly HitErrorDisplay RightHitErrorDisplay; public Bindable ShowHealthbar = new Bindable(true); @@ -86,8 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftAccuracyBar = CreateAccuracyBar(false), - RightAccuracyBar = CreateAccuracyBar(), + LeftHitErrorDisplay = CreateAccuracyBar(false), + RightHitErrorDisplay = CreateAccuracyBar(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -260,7 +260,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual AccuracyBar CreateAccuracyBar(bool mirrored = true) => new AccuracyBar(mirrored) + protected virtual HitErrorDisplay CreateAccuracyBar(bool mirrored = true) => new HitErrorDisplay(mirrored) { Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, @@ -276,16 +276,16 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - if (LeftAccuracyBar != null) + if (LeftHitErrorDisplay != null) { - processor.NewJudgement += LeftAccuracyBar.OnNewJudgement; - LeftAccuracyBar.HitWindows = processor.CreateHitWindows(); + processor.NewJudgement += LeftHitErrorDisplay.OnNewJudgement; + LeftHitErrorDisplay.HitWindows = processor.CreateHitWindows(); } - if (RightAccuracyBar != null) + if (RightHitErrorDisplay != null) { - processor.NewJudgement += RightAccuracyBar.OnNewJudgement; - RightAccuracyBar.HitWindows = processor.CreateHitWindows(); + processor.NewJudgement += RightHitErrorDisplay.OnNewJudgement; + RightHitErrorDisplay.HitWindows = processor.CreateHitWindows(); } if (HealthDisplay is StandardHealthDisplay shd) From 5e0ac28ca88cecd41586c162d5d3b6862b11cfdd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 18:30:03 +0300 Subject: [PATCH 012/162] Add basic colours --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 41 ++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2d33cb08a0..dba32d4ef8 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -13,12 +13,14 @@ using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; using osu.Framework.Bindables; using osu.Framework.Allocation; +using osu.Framework.Graphics.Colour; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { - private const int bar_width = 5; + private const int bar_width = 4; private const int bar_height = 250; private const int spacing = 3; @@ -39,10 +41,43 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - new Box + new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Orange), + Height = 0.3f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Height = 0.1f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Orange, Color4.Black.Opacity(0)), + Height = 0.3f + } + } }, arrow = new SpriteIcon { From 3136d46c7f366c169413f6d3f64f135dbdb98ff2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 19:04:56 +0300 Subject: [PATCH 013/162] Do not generate new judgement line on miss --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index dba32d4ef8..874a2cc088 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -99,6 +99,9 @@ namespace osu.Game.Screens.Play.HUD public void OnNewJudgement(JudgementResult judgement) { + if (!judgement.IsHit) + return; + Container judgementLine; Add(judgementLine = CreateJudgementLine(judgement)); From e7964c165f3c2c206dc404d23c7f3e6bcb4f5acd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 11 Aug 2019 19:09:50 +0300 Subject: [PATCH 014/162] Make judgement lines alive for a bit longer --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 874a2cc088..4cac73c975 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Play.HUD Add(judgementLine = CreateJudgementLine(judgement)); - judgementLine.FadeOut(5000, Easing.OutQuint); + judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); From f7024b513efeabd0a19b97716725770ab86138bd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 01:43:43 +0300 Subject: [PATCH 015/162] Visual improvements --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 83 +++++++++++--------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 4cac73c975..ad72063313 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -14,14 +14,15 @@ using osu.Game.Beatmaps; using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { - private const int bar_width = 4; - private const int bar_height = 250; + private const int bar_width = 3; + private const int bar_height = 200; private const int spacing = 3; public HitWindows HitWindows { get; set; } @@ -31,6 +32,7 @@ namespace osu.Game.Screens.Play.HUD private readonly bool mirrored; private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; private readonly List judgementOffsets = new List(); public HitErrorDisplay(bool mirrored = false) @@ -41,43 +43,10 @@ namespace osu.Game.Screens.Play.HUD Children = new Drawable[] { - new FillFlowContainer + bar = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Orange), - Height = 0.3f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - Height = 0.15f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Blue, - Height = 0.1f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - Height = 0.15f - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Orange, Color4.Black.Opacity(0)), - Height = 0.3f - } - } }, arrow = new SpriteIcon { @@ -86,11 +55,49 @@ namespace osu.Game.Screens.Play.HUD X = mirrored ? -spacing : spacing, RelativePositionAxes = Axes.Y, Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(10), + Size = new Vector2(8), } }; } + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bar.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + Height = 0.3f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.BlueLight, + Height = 0.1f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = 0.15f + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + Height = 0.3f + } + }); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -117,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, Masking = true, - Size = new Vector2(10, 2), + Size = new Vector2(8, 2), RelativePositionAxes = Axes.Y, Y = getRelativeJudgementPosition(judgement.TimeOffset), X = mirrored ? spacing : -spacing, From 906984ad952f5df2b3c6becf66c30339af2cef36 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 02:49:07 +0300 Subject: [PATCH 016/162] Fix the math --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 35 +++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index ad72063313..6ef102e575 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -16,6 +16,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; +using System.Linq; namespace osu.Game.Screens.Play.HUD { @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private Bindable beatmap { get; set; } + [Resolved] + private OsuColour colours { get; set; } + private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; @@ -60,50 +64,46 @@ namespace osu.Game.Screens.Play.HUD }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + protected override void LoadComplete() { + base.LoadComplete(); + HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + bar.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = 0.3f + Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = 0.15f + Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = 0.1f + Height = (float)(HitWindows.Great / HitWindows.Meh) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = 0.15f + Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = 0.3f + Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) } }); } - protected override void LoadComplete() - { - base.LoadComplete(); - HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - } - public void OnNewJudgement(JudgementResult judgement) { if (!judgement.IsHit) @@ -135,7 +135,7 @@ namespace osu.Game.Screens.Play.HUD } }; - private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Miss); + private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); private double calculateArrowPosition(JudgementResult judgement) { @@ -144,12 +144,7 @@ namespace osu.Game.Screens.Play.HUD judgementOffsets.Add(judgement.TimeOffset); - double offsets = 0; - - foreach (var offset in judgementOffsets) - offsets += offset; - - return offsets / judgementOffsets.Count; + return judgementOffsets.Average(); } } } From 50133ba8636159d4b3d79eda20ced928a4fdbf68 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 02:57:12 +0300 Subject: [PATCH 017/162] naming adjustments --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 6ef102e575..1dd77469ca 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -22,6 +22,7 @@ namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { + private const int stored_judgements_amount = 5; private const int bar_width = 3; private const int bar_height = 200; private const int spacing = 3; @@ -104,19 +105,19 @@ namespace osu.Game.Screens.Play.HUD }); } - public void OnNewJudgement(JudgementResult judgement) + public void OnNewJudgement(JudgementResult newJudgement) { - if (!judgement.IsHit) + if (!newJudgement.IsHit) return; Container judgementLine; - Add(judgementLine = CreateJudgementLine(judgement)); + Add(judgementLine = CreateJudgementLine(newJudgement)); judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(judgement)), 500, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(newJudgement)), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -137,12 +138,12 @@ namespace osu.Game.Screens.Play.HUD private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); - private double calculateArrowPosition(JudgementResult judgement) + private double calculateArrowPosition(JudgementResult newJudgement) { - if (judgementOffsets.Count > 5) + if (judgementOffsets.Count > stored_judgements_amount) judgementOffsets.RemoveAt(0); - judgementOffsets.Add(judgement.TimeOffset); + judgementOffsets.Add(newJudgement.TimeOffset); return judgementOffsets.Average(); } From ee5568e5968489b8dbce42023f4db425f28fff2c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 14:43:34 +0300 Subject: [PATCH 018/162] Use Queue instead of List for stored Judgements --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 1dd77469ca..7bb1d30f22 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; - private readonly List judgementOffsets = new List(); + private readonly Queue judgementOffsets = new Queue(); public HitErrorDisplay(bool mirrored = false) { @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Play.HUD judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); - arrow.MoveToY(getRelativeJudgementPosition(calculateArrowPosition(newJudgement)), 500, Easing.OutQuint); + arrow.MoveToY(calculateArrowPosition(newJudgement), 500, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer @@ -138,14 +138,14 @@ namespace osu.Game.Screens.Play.HUD private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); - private double calculateArrowPosition(JudgementResult newJudgement) + private float calculateArrowPosition(JudgementResult newJudgement) { if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.RemoveAt(0); + judgementOffsets.Dequeue(); - judgementOffsets.Add(newJudgement.TimeOffset); + judgementOffsets.Enqueue(newJudgement.TimeOffset); - return judgementOffsets.Average(); + return getRelativeJudgementPosition(judgementOffsets.Average()); } } } From a59a14c9e6f518ab3c99871c5ddae5617dfefc75 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:01:04 +0300 Subject: [PATCH 019/162] Add setting to enable/disable hit error visibility --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Configuration/ScoreMeterType.cs | 8 +++++-- .../Sections/Gameplay/GeneralSettings.cs | 5 ++++ osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 24 +++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 19f46c1d6a..bffbce2a52 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -79,6 +79,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); + Set(OsuSetting.ScoreMeter, ScoreMeterType.HitError); Set(OsuSetting.FloatingComments, false); @@ -132,6 +133,7 @@ namespace osu.Game.Configuration BlurLevel, ShowStoryboard, KeyOverlay, + ScoreMeter, FloatingComments, ShowInterface, ShowHealthDisplayWhenCantFail, diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 21a63fb3ed..e78220c9c9 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.ComponentModel; + namespace osu.Game.Configuration { public enum ScoreMeterType { + [Description("None")] None, - Colour, - Error + + [Description("Hit Error")] + HitError } } diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 9142492610..520a8852b3 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Always show key overlay", Bindable = config.GetBindable(OsuSetting.KeyOverlay) }, + new SettingsEnumDropdown + { + LabelText = "Score meter type", + Bindable = config.GetBindable(OsuSetting.ScoreMeter) + }, new SettingsEnumDropdown { LabelText = "Score display mode", diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 7bb1d30f22..9a5198b7cc 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using System.Linq; +using osu.Game.Configuration; namespace osu.Game.Screens.Play.HUD { @@ -40,6 +41,8 @@ namespace osu.Game.Screens.Play.HUD private readonly FillFlowContainer bar; private readonly Queue judgementOffsets = new Queue(); + private readonly Bindable type = new Bindable(); + public HitErrorDisplay(bool mirrored = false) { this.mirrored = mirrored; @@ -65,6 +68,12 @@ namespace osu.Game.Screens.Play.HUD }; } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ScoreMeter, type); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -103,6 +112,21 @@ namespace osu.Game.Screens.Play.HUD Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) } }); + + type.BindValueChanged(onTypeChanged, true); + } + + private void onTypeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + this.FadeOut(200, Easing.OutQuint); + break; + case ScoreMeterType.HitError: + this.FadeIn(200, Easing.OutQuint); + break; + } } public void OnNewJudgement(JudgementResult newJudgement) From 8740ebd13f2fcac9a46151cf0991031e3edbdf32 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:45:18 +0300 Subject: [PATCH 020/162] Simplify layout --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 72 +++++++++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++-- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 9a5198b7cc..1a4d2a45c5 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -21,10 +21,11 @@ using osu.Game.Configuration; namespace osu.Game.Screens.Play.HUD { - public class HitErrorDisplay : Container + public class HitErrorDisplay : CompositeDrawable { private const int stored_judgements_amount = 5; private const int bar_width = 3; + private const int judgement_line_width = 8; private const int bar_height = 200; private const int spacing = 3; @@ -36,36 +37,57 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private OsuColour colours { get; set; } - private readonly bool mirrored; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); private readonly Bindable type = new Bindable(); - public HitErrorDisplay(bool mirrored = false) + public HitErrorDisplay(bool reversed = false) { - this.mirrored = mirrored; + AutoSizeAxes = Axes.Both; - Size = new Vector2(bar_width, bar_height); - - Children = new Drawable[] + AddInternal(new FillFlowContainer { - bar = new FillFlowContainer + AutoSizeAxes = Axes.X, + Height = bar_height, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - }, - arrow = new SpriteIcon - { - Anchor = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - X = mirrored ? -spacing : spacing, - RelativePositionAxes = Axes.Y, - Icon = mirrored ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), + judgementsContainer = new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, + }, + bar = new FillFlowContainer + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(8), + } + }, } - }; + }); } [BackgroundDependencyLoader] @@ -136,7 +158,7 @@ namespace osu.Game.Screens.Play.HUD Container judgementLine; - Add(judgementLine = CreateJudgementLine(newJudgement)); + judgementsContainer.Add(judgementLine = CreateJudgementLine(newJudgement)); judgementLine.FadeOut(10000, Easing.OutQuint); judgementLine.Expire(); @@ -146,13 +168,13 @@ namespace osu.Game.Screens.Play.HUD protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer { - Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = mirrored ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Masking = true, - Size = new Vector2(8, 2), + RelativeSizeAxes = Axes.X, + Height = 2, RelativePositionAxes = Axes.Y, Y = getRelativeJudgementPosition(judgement.TimeOffset), - X = mirrored ? spacing : -spacing, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a9a469486e..50480c001c 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -86,8 +86,8 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftHitErrorDisplay = CreateAccuracyBar(false), - RightHitErrorDisplay = CreateAccuracyBar(), + LeftHitErrorDisplay = CreateHitErrorDisplay(false), + RightHitErrorDisplay = CreateHitErrorDisplay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -260,11 +260,11 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateAccuracyBar(bool mirrored = true) => new HitErrorDisplay(mirrored) + protected virtual HitErrorDisplay CreateHitErrorDisplay(bool reversed = true) => new HitErrorDisplay(reversed) { - Anchor = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = mirrored ? Anchor.CentreRight : Anchor.CentreLeft, - Margin = new MarginPadding { Horizontal = 30 } + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Margin = new MarginPadding { Horizontal = 20 } }; protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 55cd1cecdfb57c6ba8794ce66946b24be2d8fe4d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 15:53:42 +0300 Subject: [PATCH 021/162] Add missing blank line --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 1a4d2a45c5..2959a9aca7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -145,6 +145,7 @@ namespace osu.Game.Screens.Play.HUD case ScoreMeterType.None: this.FadeOut(200, Easing.OutQuint); break; + case ScoreMeterType.HitError: this.FadeIn(200, Easing.OutQuint); break; From 6c60db550ff9c3e76abdc89add98d6f26e415b28 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 16:24:13 +0300 Subject: [PATCH 022/162] Fix crash if ruleset has no Meh hit windows --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 21 ++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2959a9aca7..2ed06209de 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -107,37 +107,46 @@ namespace osu.Game.Screens.Play.HUD { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / HitWindows.Meh) + Height = (float)(HitWindows.Great / getMehHitWindows()) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (HitWindows.Meh * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((HitWindows.Meh - HitWindows.Good) / (HitWindows.Meh * 2)) + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) } }); type.BindValueChanged(onTypeChanged, true); } + private double getMehHitWindows() + { + // In case if ruleset has no Meh hit windows (like Taiko) + if (HitWindows.Meh == 0) + return HitWindows.Good + 40; + + return HitWindows.Meh; + } + private void onTypeChanged(ValueChangedEvent type) { switch (type.NewValue) @@ -183,7 +192,7 @@ namespace osu.Game.Screens.Play.HUD } }; - private float getRelativeJudgementPosition(double value) => (float)(value / HitWindows.Meh); + private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); private float calculateArrowPosition(JudgementResult newJudgement) { From dd6351b8caa83e917152a74220ed1a04fe56f3f3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 16:51:16 +0300 Subject: [PATCH 023/162] Apply suggested changes --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 16 +++++++++------- osu.Game/Screens/Play/HUDOverlay.cs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2ed06209de..1a3682351b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Play.HUD private const int bar_width = 3; private const int judgement_line_width = 8; private const int bar_height = 200; + private const int fade_duration = 200; + private const int arrow_move_duration = 500; + private const int judgement_life_time = 10000; private const int spacing = 3; public HitWindows HitWindows { get; set; } @@ -152,11 +155,11 @@ namespace osu.Game.Screens.Play.HUD switch (type.NewValue) { case ScoreMeterType.None: - this.FadeOut(200, Easing.OutQuint); + this.FadeOut(fade_duration, Easing.OutQuint); break; case ScoreMeterType.HitError: - this.FadeIn(200, Easing.OutQuint); + this.FadeIn(fade_duration, Easing.OutQuint); break; } } @@ -166,14 +169,13 @@ namespace osu.Game.Screens.Play.HUD if (!newJudgement.IsHit) return; - Container judgementLine; + var judgementLine = CreateJudgementLine(newJudgement); - judgementsContainer.Add(judgementLine = CreateJudgementLine(newJudgement)); + judgementsContainer.Add(judgementLine); - judgementLine.FadeOut(10000, Easing.OutQuint); - judgementLine.Expire(); + judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); - arrow.MoveToY(calculateArrowPosition(newJudgement), 500, Easing.OutQuint); + arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); } protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 50480c001c..114dc86757 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -276,17 +277,16 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - if (LeftHitErrorDisplay != null) - { - processor.NewJudgement += LeftHitErrorDisplay.OnNewJudgement; - LeftHitErrorDisplay.HitWindows = processor.CreateHitWindows(); - } + var hitWindows = processor.CreateHitWindows(); - if (RightHitErrorDisplay != null) + visibilityContainer.ForEach(drawable => { - processor.NewJudgement += RightHitErrorDisplay.OnNewJudgement; - RightHitErrorDisplay.HitWindows = processor.CreateHitWindows(); - } + if (drawable is HitErrorDisplay) + { + processor.NewJudgement += (drawable as HitErrorDisplay).OnNewJudgement; + (drawable as HitErrorDisplay).HitWindows = hitWindows; + } + }); if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; From 4c817b18b7f217ef9c95c01c867d8e4442c7ad18 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 17:03:11 +0300 Subject: [PATCH 024/162] Use direct cast --- osu.Game/Screens/Play/HUDOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 114dc86757..be291003b5 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -283,8 +283,8 @@ namespace osu.Game.Screens.Play { if (drawable is HitErrorDisplay) { - processor.NewJudgement += (drawable as HitErrorDisplay).OnNewJudgement; - (drawable as HitErrorDisplay).HitWindows = hitWindows; + processor.NewJudgement += ((HitErrorDisplay)drawable).OnNewJudgement; + ((HitErrorDisplay)drawable).HitWindows = hitWindows; } }); From 2393bbc69b15e1f3421f3d4b140393bbaa8462ac Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 21:27:53 +0300 Subject: [PATCH 025/162] Expand APIKudosuHistory --- .../Requests/GetUserKudosuHistoryRequest.cs | 10 ++++ .../Requests/Responses/APIKudosuHistory.cs | 46 +++++++++++++++++-- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index e90e297672..af37bd4b51 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -18,4 +18,14 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/kudosu"; } + + public enum KudosuAction + { + Give, + VoteGive, + Reset, + VoteReset, + DenyKudosuReset, + Revoke, + } } diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 271dcc320e..d02f71c339 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -2,19 +2,59 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Humanizer; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { public class APIKudosuHistory { - [JsonProperty("id")] - public int ID; - [JsonProperty("created_at")] public DateTimeOffset CreatedAt; [JsonProperty("amount")] + private int amount + { + set => Amount = Math.Abs(value); + } + public int Amount; + + [JsonProperty("post")] + public ModdingPost Post; + + public class ModdingPost + { + [JsonProperty("url")] + public string Url; + + [JsonProperty("title")] + public string Title; + } + + [JsonProperty("giver")] + public KudosuGiver Giver; + + public class KudosuGiver + { + [JsonProperty("url")] + public string Url; + + [JsonProperty("username")] + public string Username; + } + + [JsonProperty("action")] + private string action + { + set + { + string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); + + Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); + } + } + + public KudosuAction Action; } } From be97804180f00c93818ea91b9afd58037e27c7dd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 21:28:07 +0300 Subject: [PATCH 026/162] Implement text formatting --- .../Kudosu/DrawableKudosuHistoryItem.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 1671073242..9b68131515 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; namespace osu.Game.Overlays.Profile.Sections.Kudosu @@ -51,6 +52,45 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu AutoSizeAxes = Axes.X, }; - private MessageFormatter.MessageFormatterResult createMessage() => MessageFormatter.FormatText($@"{historyItem.Amount}"); + private MessageFormatter.MessageFormatterResult createMessage() + { + string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; + string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + + string message; + + switch (historyItem.Action) + { + case KudosuAction.Give: + message = $"Received {historyItem.Amount} kudosu from {userLinkTemplate()} for a post at {postLinkTemplate()}"; + break; + + case KudosuAction.VoteGive: + message = $"Received {historyItem.Amount} kudosu from obtaining votes in modding post of {postLinkTemplate()}"; + break; + + case KudosuAction.Reset: + message = $"Kudosu reset by {userLinkTemplate()} for the post {postLinkTemplate()}"; + break; + + case KudosuAction.VoteReset: + message = $"Lost {historyItem.Amount} kudosu from losing votes in modding post of {postLinkTemplate()}"; + break; + + case KudosuAction.DenyKudosuReset: + message = $"Denied {historyItem.Amount} kudosu from modding post {postLinkTemplate()}"; + break; + + case KudosuAction.Revoke: + message = $"Denied kudosu by {userLinkTemplate()} for the post {postLinkTemplate()}"; + break; + + default: + message = string.Empty; + break; + } + + return MessageFormatter.FormatText(message); + } } } From 4c9b621f43d9a702be1bd3149758a9e030f781bf Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 18 Aug 2019 22:01:36 +0300 Subject: [PATCH 027/162] Fix some user links can't be opened inside the game --- osu.Game/Online/Chat/MessageFormatter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4aaffdd161..db26945ef3 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -122,6 +122,7 @@ namespace osu.Game.Online.Chat return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); case "u": + case "users": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } From daeefc449c30785ba9db860ddaaf7f7fc7c92c18 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 17:49:53 +0300 Subject: [PATCH 028/162] Use another link format to avoid representation issues --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b68131515..ac5801f989 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; + string postLinkTemplate() => $"({historyItem.Post.Title})[{historyItem.Post.Url}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; From 6b8fbf0eb1ead2c25e04b8a62ca70aaaf8e42b2c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 17:59:55 +0300 Subject: [PATCH 029/162] Change link format back Due to unavaliability to handle round brackets --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index ac5801f989..9b68131515 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"({historyItem.Post.Title})[{historyItem.Post.Url}]"; + string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; From 70084b5553102e93fb5d592fb7d24d4672c070b4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 20:28:03 +0300 Subject: [PATCH 030/162] Move HitErrorDisplay outside of the HUD --- osu.Game/Screens/Play/HUDOverlay.cs | 22 ------ .../HitErrorDisplay.cs | 66 +++++------------- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 69 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 4 ++ 4 files changed, 90 insertions(+), 71 deletions(-) rename osu.Game/Screens/Play/{HUD => HitErrorDisplay}/HitErrorDisplay.cs (77%) create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index be291003b5..1124c8f5f0 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -36,8 +36,6 @@ namespace osu.Game.Screens.Play public readonly ModDisplay ModDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; - public readonly HitErrorDisplay LeftHitErrorDisplay; - public readonly HitErrorDisplay RightHitErrorDisplay; public Bindable ShowHealthbar = new Bindable(true); @@ -87,8 +85,6 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - LeftHitErrorDisplay = CreateHitErrorDisplay(false), - RightHitErrorDisplay = CreateHitErrorDisplay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -261,13 +257,6 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplay(bool reversed = true) => new HitErrorDisplay(reversed) - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Margin = new MarginPadding { Horizontal = 20 } - }; - protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) @@ -277,17 +266,6 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - var hitWindows = processor.CreateHitWindows(); - - visibilityContainer.ForEach(drawable => - { - if (drawable is HitErrorDisplay) - { - processor.NewJudgement += ((HitErrorDisplay)drawable).OnNewJudgement; - ((HitErrorDisplay)drawable).HitWindows = hitWindows; - } - }); - if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; } diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs similarity index 77% rename from osu.Game/Screens/Play/HUD/HitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 1a3682351b..1a776bc78d 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -10,16 +10,13 @@ using osuTK; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Game.Rulesets.Objects; -using osu.Game.Beatmaps; -using osu.Framework.Bindables; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using System.Linq; -using osu.Game.Configuration; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Screens.Play.HitErrorDisplay { public class HitErrorDisplay : CompositeDrawable { @@ -27,29 +24,24 @@ namespace osu.Game.Screens.Play.HUD private const int bar_width = 3; private const int judgement_line_width = 8; private const int bar_height = 200; - private const int fade_duration = 200; private const int arrow_move_duration = 500; private const int judgement_life_time = 10000; private const int spacing = 3; - public HitWindows HitWindows { get; set; } - - [Resolved] - private Bindable beatmap { get; set; } - - [Resolved] - private OsuColour colours { get; set; } - + private readonly HitWindows hitWindows; private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); - private readonly Bindable type = new Bindable(); - - public HitErrorDisplay(bool reversed = false) + public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) { + this.hitWindows = hitWindows; + hitWindows.SetDifficulty(overallDifficulty); + AutoSizeAxes = Axes.Both; + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; AddInternal(new FillFlowContainer { @@ -94,74 +86,50 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuColour colours) { - config.BindWith(OsuSetting.ScoreMeter, type); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - HitWindows.SetDifficulty(beatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - bar.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / getMehHitWindows()) + Height = (float)(hitWindows.Great / getMehHitWindows()) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) } }); - - type.BindValueChanged(onTypeChanged, true); } private double getMehHitWindows() { // In case if ruleset has no Meh hit windows (like Taiko) - if (HitWindows.Meh == 0) - return HitWindows.Good + 40; + if (hitWindows.Meh == 0) + return hitWindows.Good + 40; - return HitWindows.Meh; - } - - private void onTypeChanged(ValueChangedEvent type) - { - switch (type.NewValue) - { - case ScoreMeterType.None: - this.FadeOut(fade_duration, Easing.OutQuint); - break; - - case ScoreMeterType.HitError: - this.FadeIn(fade_duration, Easing.OutQuint); - break; - } + return hitWindows.Meh; } public void OnNewJudgement(JudgementResult newJudgement) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs new file mode 100644 index 0000000000..09ecbdfc66 --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; +using System.Linq; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public class HitErrorDisplayOverlay : Container + { + private const int fade_duration = 200; + private const int margin = 10; + + private readonly Bindable type = new Bindable(); + + public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) + { + float overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + + RelativeSizeAxes = Axes.Both; + Children = new[] + { + new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) + { + Margin = new MarginPadding { Left = margin } + }, + new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) + { + Margin = new MarginPadding { Right = margin } + }, + }; + + Children.ForEach(t => processor.NewJudgement += t.OnNewJudgement); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.ScoreMeter, type); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + type.BindValueChanged(onTypeChanged, true); + } + + private void onTypeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + InternalChildren.ForEach(t => t.FadeOut(fade_duration, Easing.OutQuint)); + break; + + default: + InternalChildren.ForEach(t => t.FadeIn(fade_duration, Easing.OutQuint)); + break; + } + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e7398be176..9ef8cc4509 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -73,6 +74,8 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } + protected HitErrorDisplayOverlay HitErrorDisplayOverlay { get; private set; } + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; protected GameplayClockContainer GameplayClockContainer { get; private set; } @@ -157,6 +160,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, + HitErrorDisplayOverlay = new HitErrorDisplayOverlay(ScoreProcessor, working), new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = GameplayClockContainer.Seek From 6d3aa0520b6cc6bc999809b123b600414314260f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 20:44:06 +0300 Subject: [PATCH 031/162] Make HitErrorDisplay an abstract class --- .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 174 ++++++++++++++++++ .../Play/HitErrorDisplay/HitErrorDisplay.cs | 167 +---------------- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 4 +- 3 files changed, 182 insertions(+), 163 deletions(-) create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs new file mode 100644 index 0000000000..6cf80b209a --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Judgements; +using osuTK.Graphics; +using osuTK; +using osu.Framework.Graphics.Sprites; +using System.Collections.Generic; +using osu.Game.Rulesets.Objects; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using System.Linq; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public class DefaultHitErrorDisplay : HitErrorDisplay + { + private const int stored_judgements_amount = 5; + private const int bar_width = 3; + private const int judgement_line_width = 8; + private const int bar_height = 200; + private const int arrow_move_duration = 500; + private const int judgement_life_time = 10000; + private const int spacing = 3; + + private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; + private readonly Queue judgementOffsets = new Queue(); + + public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + : base(overallDifficulty, hitWindows) + { + AutoSizeAxes = Axes.Both; + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; + + AddInternal(new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = bar_height, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(spacing, 0), + Children = new Drawable[] + { + judgementsContainer = new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = judgement_line_width, + RelativeSizeAxes = Axes.Y, + }, + bar = new FillFlowContainer + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Width = bar_width, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + new Container + { + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Child = arrow = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Size = new Vector2(8), + } + }, + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bar.AddRange(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.BlueLight, + Height = (float)(HitWindows.Great / getMehHitWindows()) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Green, + Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) + } + }); + } + + private double getMehHitWindows() + { + // In case if ruleset has no Meh hit windows (like Taiko) + if (HitWindows.Meh == 0) + return HitWindows.Good + 40; + + return HitWindows.Meh; + } + + public override void OnNewJudgement(JudgementResult newJudgement) + { + if (!newJudgement.IsHit) + return; + + var judgementLine = CreateJudgementLine(newJudgement); + + judgementsContainer.Add(judgementLine); + + judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); + + arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); + } + + protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + RelativeSizeAxes = Axes.X, + Height = 2, + RelativePositionAxes = Axes.Y, + Y = getRelativeJudgementPosition(judgement.TimeOffset), + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; + + private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); + + private float calculateArrowPosition(JudgementResult newJudgement) + { + if (judgementOffsets.Count > stored_judgements_amount) + judgementOffsets.Dequeue(); + + judgementOffsets.Enqueue(newJudgement.TimeOffset); + + return getRelativeJudgementPosition(judgementOffsets.Average()); + } + } +} diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 1a776bc78d..fb22b35736 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -1,177 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Judgements; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Graphics.Sprites; -using System.Collections.Generic; using osu.Game.Rulesets.Objects; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Colour; -using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class HitErrorDisplay : CompositeDrawable + public abstract class HitErrorDisplay : CompositeDrawable { - private const int stored_judgements_amount = 5; - private const int bar_width = 3; - private const int judgement_line_width = 8; - private const int bar_height = 200; - private const int arrow_move_duration = 500; - private const int judgement_life_time = 10000; - private const int spacing = 3; + protected readonly HitWindows HitWindows; - private readonly HitWindows hitWindows; - private readonly SpriteIcon arrow; - private readonly FillFlowContainer bar; - private readonly Container judgementsContainer; - private readonly Queue judgementOffsets = new Queue(); - - public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) { - this.hitWindows = hitWindows; - hitWindows.SetDifficulty(overallDifficulty); - - AutoSizeAxes = Axes.Both; - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - - AddInternal(new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = bar_height, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(spacing, 0), - Children = new Drawable[] - { - judgementsContainer = new Container - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Width = judgement_line_width, - RelativeSizeAxes = Axes.Y, - }, - bar = new FillFlowContainer - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Width = bar_width, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - new Container - { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Child = arrow = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), - } - }, - } - }); + HitWindows = hitWindows; + HitWindows.SetDifficulty(overallDifficulty); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - bar.AddRange(new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.BlueLight, - Height = (float)(hitWindows.Great / getMehHitWindows()) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((hitWindows.Good - hitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - hitWindows.Good) / (getMehHitWindows() * 2)) - } - }); - } - - private double getMehHitWindows() - { - // In case if ruleset has no Meh hit windows (like Taiko) - if (hitWindows.Meh == 0) - return hitWindows.Good + 40; - - return hitWindows.Meh; - } - - public void OnNewJudgement(JudgementResult newJudgement) - { - if (!newJudgement.IsHit) - return; - - var judgementLine = CreateJudgementLine(newJudgement); - - judgementsContainer.Add(judgementLine); - - judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); - - arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); - } - - protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - RelativeSizeAxes = Axes.X, - Height = 2, - RelativePositionAxes = Axes.Y, - Y = getRelativeJudgementPosition(judgement.TimeOffset), - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - } - }; - - private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); - - private float calculateArrowPosition(JudgementResult newJudgement) - { - if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.Dequeue(); - - judgementOffsets.Enqueue(newJudgement.TimeOffset); - - return getRelativeJudgementPosition(judgementOffsets.Average()); - } + public abstract void OnNewJudgement(JudgementResult newJudgement); } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 09ecbdfc66..35b883ff27 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -27,11 +27,11 @@ namespace osu.Game.Screens.Play.HitErrorDisplay RelativeSizeAxes = Axes.Both; Children = new[] { - new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) + new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) { Margin = new MarginPadding { Left = margin } }, - new HitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) + new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) { Margin = new MarginPadding { Right = margin } }, From 6d84523bc0d87ba814ddddf1edcdff53812a48be Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 21:10:12 +0300 Subject: [PATCH 032/162] Add testing --- .../Gameplay/TestSceneHitErrorDisplay.cs | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs new file mode 100644 index 0000000000..c5e5e96ad9 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Screens.Play.HitErrorDisplay; +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Framework.MathUtils; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneHitErrorDisplay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HitErrorDisplay), + }; + + private HitErrorDisplay display; + + public TestSceneHitErrorDisplay() + { + recreateDisplay(new OsuHitWindows(), 5); + AddStep("New random judgement", () => newJudgement()); + AddStep("New fixed judgement (50ms)", () => newJudgement(50)); + } + + [Test] + public void TestOsu() + { + AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new OsuHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new OsuHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new OsuHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new OsuHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new OsuHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new OsuHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new OsuHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new OsuHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10)); + } + + [Test] + public void TestTaiko() + { + AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new TaikoHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new TaikoHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new TaikoHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new TaikoHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new TaikoHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new TaikoHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new TaikoHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new TaikoHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10)); + } + + [Test] + public void TestMania() + { + AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new ManiaHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new ManiaHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new ManiaHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new ManiaHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new ManiaHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new ManiaHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new ManiaHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new ManiaHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10)); + } + + [Test] + public void TestCatch() + { + AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1)); + AddStep("OD 2", () => recreateDisplay(new CatchHitWindows(), 2)); + AddStep("OD 3", () => recreateDisplay(new CatchHitWindows(), 3)); + AddStep("OD 4", () => recreateDisplay(new CatchHitWindows(), 4)); + AddStep("OD 5", () => recreateDisplay(new CatchHitWindows(), 5)); + AddStep("OD 6", () => recreateDisplay(new CatchHitWindows(), 6)); + AddStep("OD 7", () => recreateDisplay(new CatchHitWindows(), 7)); + AddStep("OD 8", () => recreateDisplay(new CatchHitWindows(), 8)); + AddStep("OD 9", () => recreateDisplay(new CatchHitWindows(), 9)); + AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10)); + } + + private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) + { + Clear(); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows)); + } + + private void newJudgement(float offset = 0) + { + display?.OnNewJudgement(new JudgementResult(new Judgement()) + { + TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, + Type = HitResult.Perfect, + }); + } + } +} From 1bff103d3244aff12c6faf83454cb43bbfd2d852 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 21:25:14 +0300 Subject: [PATCH 033/162] CI fixes --- .../Visual/Gameplay/TestSceneHitErrorDisplay.cs | 7 ++++++- osu.Game/Screens/Play/HUDOverlay.cs | 1 - .../Play/HitErrorDisplay/DefaultHitErrorDisplay.cs | 2 -- osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs | 2 +- .../Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 9 ++++++--- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index c5e5e96ad9..e86606e4fc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Framework.MathUtils; +using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Gameplay { @@ -95,7 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { Clear(); - Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows)); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); } private void newJudgement(float offset = 0) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1124c8f5f0..43b9491750 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 6cf80b209a..a0b9787c5f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -37,8 +37,6 @@ namespace osu.Game.Screens.Play.HitErrorDisplay : base(overallDifficulty, hitWindows) { AutoSizeAxes = Axes.Both; - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft; - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft; AddInternal(new FillFlowContainer { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index fb22b35736..10b5e5b1cb 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -11,7 +11,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { protected readonly HitWindows HitWindows; - public HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) + protected HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) { HitWindows = hitWindows; HitWindows.SetDifficulty(overallDifficulty); diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 35b883ff27..fd118a26e5 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -29,11 +28,15 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) { - Margin = new MarginPadding { Left = margin } + Margin = new MarginPadding { Left = margin }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }, new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) { - Margin = new MarginPadding { Right = margin } + Margin = new MarginPadding { Right = margin }, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, }, }; From f1c3a60660d035128c8f3e938f88b216d4ab8669 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:04:27 +0300 Subject: [PATCH 034/162] Add ability to select side --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/Configuration/ScoreMeterType.cs | 10 ++- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 66 +++++++++++++------ 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index bffbce2a52..fbb17fa7f9 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -79,7 +79,7 @@ namespace osu.Game.Configuration Set(OsuSetting.ShowInterface, true); Set(OsuSetting.ShowHealthDisplayWhenCantFail, true); Set(OsuSetting.KeyOverlay, false); - Set(OsuSetting.ScoreMeter, ScoreMeterType.HitError); + Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); Set(OsuSetting.FloatingComments, false); diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index e78220c9c9..b85ef9309d 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -10,7 +10,13 @@ namespace osu.Game.Configuration [Description("None")] None, - [Description("Hit Error")] - HitError + [Description("Hit Error (left)")] + HitErrorLeft, + + [Description("Hit Error (right)")] + HitErrorRight, + + [Description("Hit Error (both)")] + HitErrorBoth, } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index fd118a26e5..bbed2b1618 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -18,29 +19,18 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int margin = 10; private readonly Bindable type = new Bindable(); + private readonly HitWindows hitWindows; + private readonly ScoreProcessor processor; + private readonly float overallDifficulty; public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) { - float overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + this.processor = processor; + + overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + hitWindows = processor.CreateHitWindows(); RelativeSizeAxes = Axes.Both; - Children = new[] - { - new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows()) - { - Margin = new MarginPadding { Left = margin }, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new DefaultHitErrorDisplay(overallDifficulty, processor.CreateHitWindows(), true) - { - Margin = new MarginPadding { Right = margin }, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - }, - }; - - Children.ForEach(t => processor.NewJudgement += t.OnNewJudgement); } [BackgroundDependencyLoader] @@ -57,16 +47,50 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void onTypeChanged(ValueChangedEvent type) { + clear(); + switch (type.NewValue) { case ScoreMeterType.None: - InternalChildren.ForEach(t => t.FadeOut(fade_duration, Easing.OutQuint)); break; - default: - InternalChildren.ForEach(t => t.FadeIn(fade_duration, Easing.OutQuint)); + case ScoreMeterType.HitErrorBoth: + createNew(); + createNew(true); + break; + + case ScoreMeterType.HitErrorLeft: + createNew(); + break; + + case ScoreMeterType.HitErrorRight: + createNew(true); break; } } + + private void clear() + { + Children.ForEach(t => + { + processor.NewJudgement -= t.OnNewJudgement; + t.FadeOut(fade_duration, Easing.OutQuint).Expire(); + }); + } + + private void createNew(bool reversed = false) + { + var display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows, reversed) + { + Margin = new MarginPadding(margin), + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + processor.NewJudgement += display.OnNewJudgement; + Add(display); + display.FadeInFromZero(fade_duration, Easing.OutQuint); + } } } From 50c47568e434f5a68002d40610d1618f221fbd39 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:45:27 +0300 Subject: [PATCH 035/162] Don't present Meh hit windows if it has no value --- .../Gameplay/TestSceneHitErrorDisplay.cs | 19 +++++++ .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 54 ++++++++++--------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index e86606e4fc..006773a091 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -14,6 +14,8 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Framework.MathUtils; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Tests.Visual.Gameplay { @@ -95,7 +97,24 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { + hitWindows.SetDifficulty(overallDifficulty); + Clear(); + + Add(new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new[] + { + new SpriteText { Text = $@"Great: {hitWindows.Great}" }, + new SpriteText { Text = $@"Good: {hitWindows.Good}" }, + new SpriteText { Text = $@"Meh: {hitWindows.Meh}" }, + } + }); + Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index a0b9787c5f..11de0696d3 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -32,12 +32,14 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); + private readonly double maxHitWindows; public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) : base(overallDifficulty, hitWindows) { - AutoSizeAxes = Axes.Both; + maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; + AutoSizeAxes = Axes.Both; AddInternal(new FillFlowContainer { AutoSizeAxes = Axes.X, @@ -83,48 +85,52 @@ namespace osu.Game.Screens.Play.HitErrorDisplay [BackgroundDependencyLoader] private void load(OsuColour colours) { - bar.AddRange(new Drawable[] - { - new Box + Box topGreenBox; + Box bottomGreenBox; + + if (HitWindows.Meh != 0) + bar.Add(new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) - }, - new Box + Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + }); + + bar.AddRange(new Drawable[] + { + topGreenBox = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) + Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) }, new Box { RelativeSizeAxes = Axes.Both, Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / getMehHitWindows()) + Height = (float)(HitWindows.Great / maxHitWindows) }, - new Box + bottomGreenBox = new Box { RelativeSizeAxes = Axes.Both, Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (getMehHitWindows() * 2)) - }, - new Box + Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) + } + });; + + if (HitWindows.Meh != 0) + bar.Add(new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((getMehHitWindows() - HitWindows.Good) / (getMehHitWindows() * 2)) - } - }); - } + Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + }); - private double getMehHitWindows() - { - // In case if ruleset has no Meh hit windows (like Taiko) if (HitWindows.Meh == 0) - return HitWindows.Good + 40; - - return HitWindows.Meh; + { + topGreenBox.Colour = ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green); + bottomGreenBox.Colour = ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)); + } } public override void OnNewJudgement(JudgementResult newJudgement) @@ -157,7 +163,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } }; - private float getRelativeJudgementPosition(double value) => (float)(value / getMehHitWindows()); + private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); private float calculateArrowPosition(JudgementResult newJudgement) { From 415f1802614dbcd96541746a02a8a61500ba07bb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 19 Aug 2019 22:53:28 +0300 Subject: [PATCH 036/162] Delete extra semicolon --- osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 11de0696d3..2d05dc8aba 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Colour = colours.Green, Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) } - });; + }); if (HitWindows.Meh != 0) bar.Add(new Box From c4251d512e069cfee758bafe8f85dd7113d360e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:00:09 +0300 Subject: [PATCH 037/162] Simplify bar building --- .../HitErrorDisplay/DefaultHitErrorDisplay.cs | 69 ++++++++----------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs index 2d05dc8aba..a9dd7ffcca 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int spacing = 3; private readonly SpriteIcon arrow; - private readonly FillFlowContainer bar; + private readonly FillFlowContainer bar; private readonly Container judgementsContainer; private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - bar = new FillFlowContainer + bar = new FillFlowContainer { Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, @@ -85,54 +85,39 @@ namespace osu.Game.Screens.Play.HitErrorDisplay [BackgroundDependencyLoader] private void load(OsuColour colours) { - Box topGreenBox; - Box bottomGreenBox; - if (HitWindows.Meh != 0) - bar.Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) - }); - - bar.AddRange(new Drawable[] { - topGreenBox = new Box + bar.AddRange(new[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.BlueLight, - Height = (float)(HitWindows.Great / maxHitWindows) - }, - bottomGreenBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Green, - Height = (float)((HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)) - } - }); - - if (HitWindows.Meh != 0) - bar.Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - Height = (float)((maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), + (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), + (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) }); - - if (HitWindows.Meh == 0) + } + else { - topGreenBox.Colour = ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green); - bottomGreenBox.Colour = ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)); + bar.AddRange(new[] + { + createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), + (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), + createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), + (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + }); } } + private Box createColoredPiece(ColourInfo colour, double height) => new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Height = (float)height + }; + public override void OnNewJudgement(JudgementResult newJudgement) { if (!newJudgement.IsHit) From d337f9b482a9144d85d8d12682b0a0461a8f3bff Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:03:17 +0300 Subject: [PATCH 038/162] DefaultHitErrorDisplay -> BarHitErrorDisplay --- osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs | 2 +- .../{DefaultHitErrorDisplay.cs => BarHitErrorDisplay.cs} | 4 ++-- .../Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Play/HitErrorDisplay/{DefaultHitErrorDisplay.cs => BarHitErrorDisplay.cs} (97%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index 006773a091..02773e0561 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows) + Add(display = new BarHitErrorDisplay(overallDifficulty, hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs similarity index 97% rename from osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index a9dd7ffcca..26f9e3a5e0 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/DefaultHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -18,7 +18,7 @@ using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class DefaultHitErrorDisplay : HitErrorDisplay + public class BarHitErrorDisplay : HitErrorDisplay { private const int stored_judgements_amount = 5; private const int bar_width = 3; @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; - public DefaultHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) + public BarHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) : base(overallDifficulty, hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index bbed2b1618..e4a630245d 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void createNew(bool reversed = false) { - var display = new DefaultHitErrorDisplay(overallDifficulty, hitWindows, reversed) + var display = new BarHitErrorDisplay(overallDifficulty, hitWindows, reversed) { Margin = new MarginPadding(margin), Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, From 9f64e0962534b6a4aa149ad24d2d0d18c1815ee3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:45:51 +0300 Subject: [PATCH 039/162] Move HitErrorDisplayOverlay back to the HUD --- .../Visual/Gameplay/TestSceneHitErrorDisplay.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 8 ++++++++ .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 4 ++-- .../Screens/Play/HitErrorDisplay/HitErrorDisplay.cs | 3 +-- .../Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 12 ++++-------- osu.Game/Screens/Play/Player.cs | 4 ---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs index 02773e0561..a148bdad67 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new BarHitErrorDisplay(overallDifficulty, hitWindows) + Add(display = new BarHitErrorDisplay(hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 43b9491750..02432cf64e 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -15,6 +15,7 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; + public readonly HitErrorDisplayOverlay HitErrorDisplayOverlay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -84,6 +86,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), + HitErrorDisplayOverlay = CreateHitErrorDisplayOverlay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -256,6 +259,11 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; + protected virtual HitErrorDisplayOverlay CreateHitErrorDisplayOverlay() => new HitErrorDisplayOverlay(scoreProcessor) + { + RelativeSizeAxes = Axes.Both, + }; + protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); protected virtual void BindProcessor(ScoreProcessor processor) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index 26f9e3a5e0..6539ce38a8 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -34,8 +34,8 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Queue judgementOffsets = new Queue(); private readonly double maxHitWindows; - public BarHitErrorDisplay(float overallDifficulty, HitWindows hitWindows, bool reversed = false) - : base(overallDifficulty, hitWindows) + public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) + : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 10b5e5b1cb..422e151d8a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -11,10 +11,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { protected readonly HitWindows HitWindows; - protected HitErrorDisplay(float overallDifficulty, HitWindows hitWindows) + protected HitErrorDisplay(HitWindows hitWindows) { HitWindows = hitWindows; - HitWindows.SetDifficulty(overallDifficulty); } public abstract void OnNewJudgement(JudgementResult newJudgement); diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index e4a630245d..1c61904461 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -21,22 +21,18 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; - private readonly float overallDifficulty; - public HitErrorDisplayOverlay(ScoreProcessor processor, WorkingBeatmap workingBeatmap) + public HitErrorDisplayOverlay(ScoreProcessor processor) { this.processor = processor; - - overallDifficulty = workingBeatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; hitWindows = processor.CreateHitWindows(); - - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, Bindable workingBeatmap) { config.BindWith(OsuSetting.ScoreMeter, type); + hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); } protected override void LoadComplete() @@ -80,7 +76,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void createNew(bool reversed = false) { - var display = new BarHitErrorDisplay(overallDifficulty, hitWindows, reversed) + var display = new BarHitErrorDisplay(hitWindows, reversed) { Margin = new MarginPadding(margin), Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9ef8cc4509..e7398be176 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,7 +24,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Users; @@ -74,8 +73,6 @@ namespace osu.Game.Screens.Play protected HUDOverlay HUDOverlay { get; private set; } - protected HitErrorDisplayOverlay HitErrorDisplayOverlay { get; private set; } - public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; protected GameplayClockContainer GameplayClockContainer { get; private set; } @@ -160,7 +157,6 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre }, - HitErrorDisplayOverlay = new HitErrorDisplayOverlay(ScoreProcessor, working), new SkipOverlay(DrawableRuleset.GameplayStartTime) { RequestSeek = GameplayClockContainer.Seek From 596ee150c6e2db6fc3963ca237afba868432543a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 08:51:41 +0300 Subject: [PATCH 040/162] Add xmldoc for not obvious const --- .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index 6539ce38a8..f68e17c908 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -20,12 +20,15 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorDisplay : HitErrorDisplay { + /// + /// The amount of which will be stored to calculate arrow position. + /// private const int stored_judgements_amount = 5; - private const int bar_width = 3; + private const int judgement_fade_duration = 10000; + private const int arrow_move_duration = 500; private const int judgement_line_width = 8; private const int bar_height = 200; - private const int arrow_move_duration = 500; - private const int judgement_life_time = 10000; + private const int bar_width = 3; private const int spacing = 3; private readonly SpriteIcon arrow; @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(judgementLine); - judgementLine.FadeOut(judgement_life_time, Easing.OutQuint).Expire(); + judgementLine.FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); } From f72edb8bf8840139adf03a8a125a09373167a20f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 09:03:31 +0300 Subject: [PATCH 041/162] Add missing blank line --- osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index f68e17c908..d8ae3dd9b0 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay /// The amount of which will be stored to calculate arrow position. /// private const int stored_judgements_amount = 5; + private const int judgement_fade_duration = 10000; private const int arrow_move_duration = 500; private const int judgement_line_width = 8; From f1d02d8169a6b38b6f8019c83a3fbaea8bae2d81 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:00:14 +0300 Subject: [PATCH 042/162] Update design in line with web --- .../Kudosu/DrawableKudosuHistoryItem.cs | 105 +++++++++++++----- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b68131515..7c120e60f3 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -12,49 +12,100 @@ using osu.Game.Online.Chat; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class DrawableKudosuHistoryItem : DrawableProfileRow + public class DrawableKudosuHistoryItem : CompositeDrawable { + private const int height = 25; + + [Resolved] + private OsuColour colours { get; set; } + private readonly APIKudosuHistory historyItem; - private LinkFlowContainer content; + private readonly LinkFlowContainer linkFlowContainer; + private readonly DrawableDate date; public DrawableKudosuHistoryItem(APIKudosuHistory historyItem) { this.historyItem = historyItem; + + Height = height; + RelativeSizeAxes = Axes.X; + AddRangeInternal(new Drawable[] + { + linkFlowContainer = new LinkFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + date = new DrawableDate(historyItem.CreatedAt) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }); } [BackgroundDependencyLoader] private void load() { - LeftFlowContainer.Padding = new MarginPadding { Left = 10 }; + date.Colour = colours.GreySeafoamLighter; - LeftFlowContainer.Add(content = new LinkFlowContainer + switch (historyItem.Action) { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }); + case KudosuAction.VoteGive: + case KudosuAction.Give: + linkFlowContainer.AddText($@"Received "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; - RightFlowContainer.Add(new DrawableDate(historyItem.CreatedAt) - { - Font = OsuFont.GetFont(size: 13), - Colour = OsuColour.Gray(0xAA), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }); + case KudosuAction.Reset: + addMainPart(); + addPostPart(); + break; - var formatted = createMessage(); + case KudosuAction.VoteReset: + linkFlowContainer.AddText($@"Lost "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; - content.AddLinks(formatted.Text, formatted.Links); + case KudosuAction.DenyKudosuReset: + linkFlowContainer.AddText($@"Denied "); + addKudosuPart(); + addMainPart(); + addPostPart(); + break; + + case KudosuAction.Revoke: + addMainPart(); + addPostPart(); + break; + } } - protected override Drawable CreateLeftVisual() => new Container + private void addKudosuPart() { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - }; + linkFlowContainer.AddText($@"{historyItem.Amount} kudosu", t => + { + t.Font = t.Font.With(italics: true); + t.Colour = colours.Blue; + }); + } + + private void addMainPart() + { + var text = createMessage(); + + linkFlowContainer.AddLinks(text.Text, text.Links); + } + + private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); private MessageFormatter.MessageFormatterResult createMessage() { - string postLinkTemplate() => $"[{historyItem.Post.Url} {historyItem.Post.Title}]"; string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; string message; @@ -62,27 +113,27 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.Give: - message = $"Received {historyItem.Amount} kudosu from {userLinkTemplate()} for a post at {postLinkTemplate()}"; + message = $" from {userLinkTemplate()} for a post at "; break; case KudosuAction.VoteGive: - message = $"Received {historyItem.Amount} kudosu from obtaining votes in modding post of {postLinkTemplate()}"; + message = $" from obtaining votes in modding post of "; break; case KudosuAction.Reset: - message = $"Kudosu reset by {userLinkTemplate()} for the post {postLinkTemplate()}"; + message = $"Kudosu reset by {userLinkTemplate()} for the post "; break; case KudosuAction.VoteReset: - message = $"Lost {historyItem.Amount} kudosu from losing votes in modding post of {postLinkTemplate()}"; + message = $" from losing votes in modding post of "; break; case KudosuAction.DenyKudosuReset: - message = $"Denied {historyItem.Amount} kudosu from modding post {postLinkTemplate()}"; + message = $" from modding post "; break; case KudosuAction.Revoke: - message = $"Denied kudosu by {userLinkTemplate()} for the post {postLinkTemplate()}"; + message = $"Denied kudosu by {userLinkTemplate()} for the post "; break; default: From 832b365bd08ac76da29707aea9d2d466d745497c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:17:31 +0300 Subject: [PATCH 043/162] Add testing --- .../Visual/Online/TestSceneKudosuHistory.cs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs new file mode 100644 index 0000000000..6424db89fa --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Overlays.Profile.Sections.Kudosu; +using System.Collections.Generic; +using System; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API.Requests; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneKudosuHistory : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableKudosuHistoryItem), + }; + + private readonly Box background; + + public TestSceneKudosuHistory() + { + FillFlowContainer content; + + AddRange(new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Width = 0.7f, + AutoSizeAxes = Axes.Y, + } + }); + + items.ForEach(t => content.Add(new DrawableKudosuHistoryItem(t))); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoam; + } + + private IEnumerable items = new[] + { + new APIKudosuHistory + { + Amount = 10, + CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)), + Action = KudosuAction.DenyKudosuReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 1", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username1", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 5, + CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), + Action = KudosuAction.Give, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 2", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username2", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 8, + CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), + Action = KudosuAction.Reset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 3", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username3", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 7, + CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), + Action = KudosuAction.Revoke, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 4", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username4", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 100, + CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)), + Action = KudosuAction.VoteGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 5", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username5", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 20, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.VoteReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 6", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username6", + Url = @"https://osu.ppy.sh/u/1234" + } + } + }; + } +} From c4344f3f7cc985b5f40ad4e1603c59ab910b3e2b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 15:29:32 +0300 Subject: [PATCH 044/162] CI fixes --- .../Visual/Online/TestSceneKudosuHistory.cs | 2 +- .../Kudosu/DrawableKudosuHistoryItem.cs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 6424db89fa..dcf2bec239 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Online background.Colour = colours.GreySeafoam; } - private IEnumerable items = new[] + private readonly IEnumerable items = new[] { new APIKudosuHistory { diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 7c120e60f3..d6dfdc84ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { case KudosuAction.VoteGive: case KudosuAction.Give: - linkFlowContainer.AddText($@"Received "); + linkFlowContainer.AddText(@"Received "); addKudosuPart(); addMainPart(); addPostPart(); @@ -66,14 +66,14 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu break; case KudosuAction.VoteReset: - linkFlowContainer.AddText($@"Lost "); + linkFlowContainer.AddText(@"Lost "); addKudosuPart(); addMainPart(); addPostPart(); break; case KudosuAction.DenyKudosuReset: - linkFlowContainer.AddText($@"Denied "); + linkFlowContainer.AddText(@"Denied "); addKudosuPart(); addMainPart(); addPostPart(); @@ -113,27 +113,27 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.Give: - message = $" from {userLinkTemplate()} for a post at "; + message = $@" from {userLinkTemplate()} for a post at "; break; case KudosuAction.VoteGive: - message = $" from obtaining votes in modding post of "; + message = @" from obtaining votes in modding post of "; break; case KudosuAction.Reset: - message = $"Kudosu reset by {userLinkTemplate()} for the post "; + message = $@"Kudosu reset by {userLinkTemplate()} for the post "; break; case KudosuAction.VoteReset: - message = $" from losing votes in modding post of "; + message = @" from losing votes in modding post of "; break; case KudosuAction.DenyKudosuReset: - message = $" from modding post "; + message = @" from modding post "; break; case KudosuAction.Revoke: - message = $"Denied kudosu by {userLinkTemplate()} for the post "; + message = $@"Denied kudosu by {userLinkTemplate()} for the post "; break; default: From 5c7cb4dc21ecb4a3b5961a4bed7c7f4b1562aa1a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 16:11:59 +0300 Subject: [PATCH 045/162] Simplify text creation --- .../Kudosu/DrawableKudosuHistoryItem.cs | 64 +++++-------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index d6dfdc84ec..1b0a501db2 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -50,37 +50,45 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { date.Colour = colours.GreySeafoamLighter; + string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + switch (historyItem.Action) { case KudosuAction.VoteGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart($@" from {userLinkTemplate()} for a post at "); + addPostPart(); + break; + case KudosuAction.Give: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart(); + addMainPart(@" from obtaining votes in modding post of "); addPostPart(); break; case KudosuAction.Reset: - addMainPart(); + addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); addPostPart(); break; case KudosuAction.VoteReset: linkFlowContainer.AddText(@"Lost "); addKudosuPart(); - addMainPart(); + addMainPart(@" from losing votes in modding post of "); addPostPart(); break; case KudosuAction.DenyKudosuReset: linkFlowContainer.AddText(@"Denied "); addKudosuPart(); - addMainPart(); + addMainPart(@" from modding post "); addPostPart(); break; case KudosuAction.Revoke: - addMainPart(); + addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); addPostPart(); break; } @@ -95,53 +103,13 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu }); } - private void addMainPart() + private void addMainPart(string text) { - var text = createMessage(); + var formatted = MessageFormatter.FormatText(text); - linkFlowContainer.AddLinks(text.Text, text.Links); + linkFlowContainer.AddLinks(formatted.Text, formatted.Links); } private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); - - private MessageFormatter.MessageFormatterResult createMessage() - { - string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; - - string message; - - switch (historyItem.Action) - { - case KudosuAction.Give: - message = $@" from {userLinkTemplate()} for a post at "; - break; - - case KudosuAction.VoteGive: - message = @" from obtaining votes in modding post of "; - break; - - case KudosuAction.Reset: - message = $@"Kudosu reset by {userLinkTemplate()} for the post "; - break; - - case KudosuAction.VoteReset: - message = @" from losing votes in modding post of "; - break; - - case KudosuAction.DenyKudosuReset: - message = @" from modding post "; - break; - - case KudosuAction.Revoke: - message = $@"Denied kudosu by {userLinkTemplate()} for the post "; - break; - - default: - message = string.Empty; - break; - } - - return MessageFormatter.FormatText(message); - } } } From 426c7a48989302453ca7e05b8c91738e06d6ad7e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 20 Aug 2019 16:19:21 +0300 Subject: [PATCH 046/162] Fix incorrect templates --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 1b0a501db2..e5f5b720c2 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -57,14 +57,14 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.VoteGive: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart($@" from {userLinkTemplate()} for a post at "); + addMainPart(@" from obtaining votes in modding post of "); addPostPart(); break; case KudosuAction.Give: linkFlowContainer.AddText(@"Received "); addKudosuPart(); - addMainPart(@" from obtaining votes in modding post of "); + addMainPart($@" from {userLinkTemplate()} for a post at "); addPostPart(); break; From 71cbc3525d98e3177c60c60f0677dece66ad9105 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:16:09 +0300 Subject: [PATCH 047/162] Add/remove displays only if necessary --- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 62 ++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index 1c61904461..fae1b68e4a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay @@ -22,6 +21,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; + private BarHitErrorDisplay leftDisplay; + private BarHitErrorDisplay rightDisplay; + public HitErrorDisplayOverlay(ScoreProcessor processor) { this.processor = processor; @@ -43,38 +45,69 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void onTypeChanged(ValueChangedEvent type) { - clear(); - switch (type.NewValue) { case ScoreMeterType.None: + removeLeftDisplay(); + removeRightDisplay(); break; case ScoreMeterType.HitErrorBoth: - createNew(); - createNew(true); + addLeftDisplay(); + addRightDisplay(); break; case ScoreMeterType.HitErrorLeft: - createNew(); + addLeftDisplay(); + removeRightDisplay(); break; case ScoreMeterType.HitErrorRight: - createNew(true); + addRightDisplay(); + removeLeftDisplay(); break; } } - private void clear() + private void addLeftDisplay() { - Children.ForEach(t => - { - processor.NewJudgement -= t.OnNewJudgement; - t.FadeOut(fade_duration, Easing.OutQuint).Expire(); - }); + if (leftDisplay != null) + return; + + leftDisplay = createNew(); } - private void createNew(bool reversed = false) + private void addRightDisplay() + { + if (rightDisplay != null) + return; + + rightDisplay = createNew(true); + } + + private void removeRightDisplay() + { + if (rightDisplay == null) + return; + + processor.NewJudgement -= rightDisplay.OnNewJudgement; + + rightDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); + rightDisplay = null; + } + + private void removeLeftDisplay() + { + if (leftDisplay == null) + return; + + processor.NewJudgement -= leftDisplay.OnNewJudgement; + + leftDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); + leftDisplay = null; + } + + private BarHitErrorDisplay createNew(bool reversed = false) { var display = new BarHitErrorDisplay(hitWindows, reversed) { @@ -87,6 +120,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay processor.NewJudgement += display.OnNewJudgement; Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); + return display; } } } From a994ad9c84047e583471443c0d68f4ecc0e94dc4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:40:15 +0300 Subject: [PATCH 048/162] Use moving average to calculate arrow position --- .../HitErrorDisplay/BarHitErrorDisplay.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d8ae3dd9b0..d24982635b 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly SpriteIcon arrow; private readonly FillFlowContainer bar; private readonly Container judgementsContainer; - private readonly Queue judgementOffsets = new Queue(); + private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) @@ -154,14 +154,23 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); + private double sum = 0; + private float calculateArrowPosition(JudgementResult newJudgement) { + var offset = newJudgement.TimeOffset; + + sum += offset; + + judgementOffsets.Add(offset); + if (judgementOffsets.Count > stored_judgements_amount) - judgementOffsets.Dequeue(); + { + sum -= judgementOffsets[0]; + judgementOffsets.RemoveAt(0); + } - judgementOffsets.Enqueue(newJudgement.TimeOffset); - - return getRelativeJudgementPosition(judgementOffsets.Average()); + return getRelativeJudgementPosition(sum / stored_judgements_amount); } } } From a5acc913eab464a2f0e313df630a1a1a6736e87b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 09:58:47 +0300 Subject: [PATCH 049/162] CI fixes --- osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d24982635b..d2bef75fd7 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -14,7 +14,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -154,7 +153,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); - private double sum = 0; + private double sum; private float calculateArrowPosition(JudgementResult newJudgement) { From 0ccfaeb8d9896ae901e09a8eb284477514cd5fde Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 21 Aug 2019 10:13:59 +0300 Subject: [PATCH 050/162] Simplify moving average --- .../Play/HitErrorDisplay/BarHitErrorDisplay.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs index d2bef75fd7..85d017073a 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs @@ -14,6 +14,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; +using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { @@ -153,21 +154,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); - private double sum; - private float calculateArrowPosition(JudgementResult newJudgement) { - var offset = newJudgement.TimeOffset; + judgementOffsets.Add(newJudgement.TimeOffset); - sum += offset; + if (judgementOffsets.Count < stored_judgements_amount) + return getRelativeJudgementPosition(judgementOffsets.Average()); - judgementOffsets.Add(offset); + double sum = 0; - if (judgementOffsets.Count > stored_judgements_amount) - { - sum -= judgementOffsets[0]; - judgementOffsets.RemoveAt(0); - } + for (int i = judgementOffsets.Count - stored_judgements_amount; i < judgementOffsets.Count; i++) + sum += judgementOffsets[i]; return getRelativeJudgementPosition(sum / stored_judgements_amount); } From f4d2bb036b3bcd9ed7819ad1380e1310bf8a2984 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 22 Aug 2019 16:50:54 +0300 Subject: [PATCH 051/162] Expand KudosuAction list --- .../Visual/Online/TestSceneKudosuHistory.cs | 80 +++++++++++++++++++ .../Requests/GetUserKudosuHistoryRequest.cs | 5 ++ .../Kudosu/DrawableKudosuHistoryItem.cs | 35 ++++++++ 3 files changed, 120 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index dcf2bec239..8badfeaa23 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -150,6 +150,86 @@ namespace osu.Game.Tests.Visual.Online Username = @"Username6", Url = @"https://osu.ppy.sh/u/1234" } + }, + new APIKudosuHistory + { + Amount = 11, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.AllowKudosuGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 7", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username7", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 24, + CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)), + Action = KudosuAction.DeleteReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 8", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username8", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 12, + CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), + Action = KudosuAction.RestoreGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 9", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username9", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 2, + CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)), + Action = KudosuAction.RecalculateGive, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 10", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username10", + Url = @"https://osu.ppy.sh/u/1234" + } + }, + new APIKudosuHistory + { + Amount = 32, + CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)), + Action = KudosuAction.RecalculateReset, + Post = new APIKudosuHistory.ModdingPost + { + Title = @"Random post 11", + Url = @"https://osu.ppy.sh/b/1234", + }, + Giver = new APIKudosuHistory.KudosuGiver + { + Username = @"Username11", + Url = @"https://osu.ppy.sh/u/1234" + } } }; } diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index af37bd4b51..dd6f2ccf22 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -27,5 +27,10 @@ namespace osu.Game.Online.API.Requests VoteReset, DenyKudosuReset, Revoke, + AllowKudosuGive, + DeleteReset, + RestoreGive, + RecalculateGive, + RecalculateReset } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index e5f5b720c2..3cc39f0e73 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -91,6 +91,41 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); addPostPart(); break; + + case KudosuAction.AllowKudosuGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from kudosu deny repeal of modding post "); + addPostPart(); + break; + + case KudosuAction.DeleteReset: + linkFlowContainer.AddText(@"Lost "); + addKudosuPart(); + addMainPart(@" from modding post deletion of "); + addPostPart(); + break; + + case KudosuAction.RestoreGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from modding post restoration of "); + addPostPart(); + break; + + case KudosuAction.RecalculateGive: + linkFlowContainer.AddText(@"Received "); + addKudosuPart(); + addMainPart(@" from votes recalculation in modding post of "); + addPostPart(); + break; + + case KudosuAction.RecalculateReset: + linkFlowContainer.AddText(@"Lost "); + addKudosuPart(); + addMainPart(@" from votes recalculation in modding post of "); + addPostPart(); + break; } } From 050130e1591e6ec6991b9027fa51c0d085498307 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:11:21 +0300 Subject: [PATCH 052/162] Refactor PaginatedContainer to centralise repetitive logic --- .../Beatmaps/PaginatedBeatmapContainer.cs | 48 +++---------- .../PaginatedMostPlayedBeatmapContainer.cs | 41 +++-------- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 38 ++-------- .../Profile/Sections/PaginatedContainer.cs | 72 +++++++++++++++---- .../Sections/Ranks/PaginatedScoreContainer.cs | 65 ++++++----------- .../PaginatedRecentActivityContainer.cs | 39 ++-------- 6 files changed, 110 insertions(+), 193 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 8a6b52b7ee..3fbd1dacd9 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,21 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Direct; using osu.Game.Users; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Beatmaps { - public class PaginatedBeatmapContainer : PaginatedContainer + public class PaginatedBeatmapContainer : PaginatedContainer { private const float panel_padding = 10f; private readonly BeatmapSetType type; - private GetUserBeatmapsRequest request; public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.") : base(user, header, missing) @@ -27,40 +28,13 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps ItemsContainer.Spacing = new Vector2(panel_padding); } - protected override void ShowMore() + protected override APIRequest> CreateRequest() + => new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APIBeatmapSet item) => new DirectGridPanel(item.ToBeatmapSet(Rulesets)) { - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += sets => Schedule(() => - { - MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - if (!sets.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - foreach (var s in sets) - { - if (!s.OnlineBeatmapSetID.HasValue) - continue; - - ItemsContainer.Add(new DirectGridPanel(s.ToBeatmapSet(Rulesets)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 23072f8d90..e444363e52 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,19 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; +using System.Collections.Generic; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections.Historical { - public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer + public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer { - private GetUserMostPlayedBeatmapsRequest request; - public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, "Most Played Beatmaps", "No records. :(") { @@ -22,35 +22,10 @@ namespace osu.Game.Overlays.Profile.Sections.Historical ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() - { - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += beatmaps => Schedule(() => - { - MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!beatmaps.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var beatmap in beatmaps) - { - ItemsContainer.Add(new DrawableMostPlayedBeatmap(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap item) + => new DrawableMostPlayedBeatmap(item.GetBeatmapInfo(Rulesets), item.PlayCount); } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index 29b1d3c5aa..0e7cfc37c0 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -4,48 +4,24 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; -using System.Linq; using osu.Framework.Bindables; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Kudosu { - public class PaginatedKudosuHistoryContainer : PaginatedContainer + public class PaginatedKudosuHistoryContainer : PaginatedContainer { - private GetUserKudosuHistoryRequest request; - public PaginatedKudosuHistoryContainer(Bindable user, string header, string missing) : base(user, header, missing) { ItemsPerPage = 5; } - protected override void ShowMore() - { - request = new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += items => Schedule(() => - { - MoreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserKudosuHistoryRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!items.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (var item in items) - ItemsContainer.Add(new DrawableKudosuHistoryItem(item)); - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item); } } diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index b459afcb49..e329fce67b 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -11,22 +11,25 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Users; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Overlays.Profile.Sections { - public abstract class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : FillFlowContainer { - protected readonly FillFlowContainer ItemsContainer; - protected readonly ShowMoreButton MoreButton; - protected readonly OsuSpriteText MissingText; + private readonly ShowMoreButton moreButton; + private readonly OsuSpriteText missingText; + private APIRequest> retrievalRequest; + + [Resolved] + private IAPIProvider api { get; set; } protected int VisiblePages; protected int ItemsPerPage; protected readonly Bindable User = new Bindable(); - - protected IAPIProvider Api; - protected APIRequest RetrievalRequest; + protected readonly FillFlowContainer ItemsContainer; protected RulesetStore Rulesets; protected PaginatedContainer(Bindable user, string header, string missing) @@ -51,15 +54,15 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - MoreButton = new ShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Alpha = 0, Margin = new MarginPadding { Top = 10 }, - Action = ShowMore, + Action = showMore, }, - MissingText = new OsuSpriteText + missingText = new OsuSpriteText { Font = OsuFont.GetFont(size: 15), Text = missing, @@ -69,9 +72,8 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(IAPIProvider api, RulesetStore rulesets) + private void load(RulesetStore rulesets) { - Api = api; Rulesets = rulesets; User.ValueChanged += onUserChanged; @@ -84,9 +86,51 @@ namespace osu.Game.Overlays.Profile.Sections ItemsContainer.Clear(); if (e.NewValue != null) - ShowMore(); + showMore(); } - protected abstract void ShowMore(); + private void showMore() + { + retrievalRequest = CreateRequest(); + retrievalRequest.Success += items => UpdateItems(items); + + api.Queue(retrievalRequest); + } + + protected virtual void UpdateItems(List items) + { + Schedule(() => + { + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; + + if (!items.Any() && VisiblePages == 1) + { + moreButton.Hide(); + moreButton.IsLoading = false; + missingText.Show(); + return; + } + + LoadComponentsAsync(items.Select(item => CreateDrawableItem(item)), i => + { + missingText.Hide(); + moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); + moreButton.IsLoading = false; + + ItemsContainer.AddRange(i); + }); + }); + } + + protected abstract APIRequest> CreateRequest(); + + protected abstract Drawable CreateDrawableItem(T item); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + retrievalRequest?.Cancel(); + } } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 4a9ac6e5c7..dfe300b069 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -5,18 +5,18 @@ using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Users; using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using osu.Game.Online.API; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedContainer + public class PaginatedScoreContainer : PaginatedContainer { private readonly bool includeWeight; private readonly ScoreType type; - private GetUserScoresRequest request; public PaginatedScoreContainer(ScoreType type, Bindable user, string header, string missing, bool includeWeight = false) : base(user, header, missing) @@ -29,52 +29,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks ItemsContainer.Direction = FillDirection.Vertical; } - protected override void ShowMore() + protected override void UpdateItems(List items) { - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); - request.Success += scores => Schedule(() => - { - foreach (var s in scores) - s.Ruleset = Rulesets.GetRuleset(s.RulesetID); + foreach (var item in items) + item.Ruleset = Rulesets.GetRuleset(item.RulesetID); - if (!scores.Any() && VisiblePages == 1) - { - MoreButton.Hide(); - MoreButton.IsLoading = false; - MissingText.Show(); - return; - } - - IEnumerable drawableScores; - - switch (type) - { - default: - drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); - break; - - case ScoreType.Recent: - drawableScores = scores.Select(score => new DrawableTotalScore(score)); - break; - } - - LoadComponentsAsync(drawableScores, s => - { - MissingText.Hide(); - MoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; - - ItemsContainer.AddRange(s); - }); - }); - - Api.Queue(request); + base.UpdateItems(items); } - protected override void Dispose(bool isDisposing) + protected override APIRequest> CreateRequest() + => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); + + protected override Drawable CreateDrawableItem(APILegacyScoreInfo item) { - base.Dispose(isDisposing); - request?.Cancel(); + switch (type) + { + default: + return new DrawablePerformanceScore(item, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); + + case ScoreType.Recent: + return new DrawableTotalScore(item); + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index f2a778a874..0251dd0740 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -4,51 +4,24 @@ using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; -using System.Linq; using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.API; +using System.Collections.Generic; namespace osu.Game.Overlays.Profile.Sections.Recent { - public class PaginatedRecentActivityContainer : PaginatedContainer + public class PaginatedRecentActivityContainer : PaginatedContainer { - private GetUserRecentActivitiesRequest request; - public PaginatedRecentActivityContainer(Bindable user, string header, string missing) : base(user, header, missing) { ItemsPerPage = 5; } - protected override void ShowMore() - { - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - request.Success += activities => Schedule(() => - { - MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); - MoreButton.IsLoading = false; + protected override APIRequest> CreateRequest() + => new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++, ItemsPerPage); - if (!activities.Any() && VisiblePages == 1) - { - MissingText.Show(); - return; - } - - MissingText.Hide(); - - foreach (APIRecentActivity activity in activities) - { - ItemsContainer.Add(new DrawableRecentActivity(activity)); - } - }); - - Api.Queue(request); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - request?.Cancel(); - } + protected override Drawable CreateDrawableItem(APIRecentActivity item) => new DrawableRecentActivity(item); } } From 45c0826314fb89ec07026037aa93c7e50979cb80 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:14:39 +0300 Subject: [PATCH 053/162] Remove repetitive code --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index e329fce67b..6c444b2e26 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -101,9 +101,6 @@ namespace osu.Game.Overlays.Profile.Sections { Schedule(() => { - moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); - moreButton.IsLoading = false; - if (!items.Any() && VisiblePages == 1) { moreButton.Hide(); From 7e34afeab874911ef0fd3edb1ad94388fc681d9d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:38:18 +0300 Subject: [PATCH 054/162] Conver to method group --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 6c444b2e26..0e2b4c986a 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -82,6 +82,8 @@ namespace osu.Game.Overlays.Profile.Sections private void onUserChanged(ValueChangedEvent e) { + retrievalRequest?.Cancel(); + VisiblePages = 0; ItemsContainer.Clear(); @@ -92,7 +94,7 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { retrievalRequest = CreateRequest(); - retrievalRequest.Success += items => UpdateItems(items); + retrievalRequest.Success += UpdateItems; api.Queue(retrievalRequest); } @@ -109,13 +111,13 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Select(item => CreateDrawableItem(item)), i => + LoadComponentsAsync(items.Select(CreateDrawableItem), drawables => { missingText.Hide(); moreButton.FadeTo(items.Count == ItemsPerPage ? 1 : 0); moreButton.IsLoading = false; - ItemsContainer.AddRange(i); + ItemsContainer.AddRange(drawables); }); }); } From 0cde0982e595da7dc28240b78ed534e83f171507 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 23 Aug 2019 14:52:26 +0300 Subject: [PATCH 055/162] Use cansellation token --- osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 0e2b4c986a..75601041e8 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Users; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace osu.Game.Overlays.Profile.Sections { @@ -21,6 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; + private CancellationTokenSource loadCancellation; [Resolved] private IAPIProvider api { get; set; } @@ -82,6 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections private void onUserChanged(ValueChangedEvent e) { + loadCancellation?.Cancel(); retrievalRequest?.Cancel(); VisiblePages = 0; @@ -93,6 +96,8 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { + loadCancellation = new CancellationTokenSource(); + retrievalRequest = CreateRequest(); retrievalRequest.Success += UpdateItems; @@ -118,7 +123,7 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.IsLoading = false; ItemsContainer.AddRange(drawables); - }); + }, loadCancellation.Token); }); } From de2c6aa23d30e0480755f1d592f18c864f8b4316 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:21:11 +0900 Subject: [PATCH 056/162] Rename and expand usability of SkinProvidingContainer --- .../Skinning/BeatmapSkinProvidingContainer.cs | 38 ++++++++++++++++ ...Container.cs => SkinProvidingContainer.cs} | 44 ++++++++----------- 2 files changed, 57 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Skinning/BeatmapSkinProvidingContainer.cs rename osu.Game/Skinning/{LocalSkinOverrideContainer.cs => SkinProvidingContainer.cs} (60%) diff --git a/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs new file mode 100644 index 0000000000..dc25a33f1e --- /dev/null +++ b/osu.Game/Skinning/BeatmapSkinProvidingContainer.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Configuration; + +namespace osu.Game.Skinning +{ + /// + /// A container which overrides existing skin options with beatmap-local values. + /// + public class BeatmapSkinProvidingContainer : SkinProvidingContainer + { + private readonly Bindable beatmapSkins = new Bindable(); + private readonly Bindable beatmapHitsounds = new Bindable(); + + protected override bool AllowConfigurationLookup => beatmapSkins.Value; + protected override bool AllowDrawableLookup(string componentName) => beatmapSkins.Value; + protected override bool AllowTextureLookup(string componentName) => beatmapSkins.Value; + protected override bool AllowSampleLookup(string componentName) => beatmapHitsounds.Value; + + public BeatmapSkinProvidingContainer(ISkin skin) + : base(skin) + { + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); + config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); + + beatmapSkins.BindValueChanged(_ => TriggerSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => TriggerSourceChanged()); + } + } +} diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs similarity index 60% rename from osu.Game/Skinning/LocalSkinOverrideContainer.cs rename to osu.Game/Skinning/SkinProvidingContainer.cs index 7882e0f31b..a92a0ae0d1 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -4,29 +4,32 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; -using osu.Game.Configuration; namespace osu.Game.Skinning { /// - /// A container which overrides existing skin options with beatmap-local values. + /// A container which adds a local to the hierarchy. /// - public class LocalSkinOverrideContainer : Container, ISkinSource + public class SkinProvidingContainer : Container, ISkinSource { public event Action SourceChanged; - private readonly Bindable beatmapSkins = new Bindable(); - private readonly Bindable beatmapHitsounds = new Bindable(); - private readonly ISkin skin; private ISkinSource fallbackSource; - public LocalSkinOverrideContainer(ISkin skin) + protected virtual bool AllowDrawableLookup(string componentName) => true; + + protected virtual bool AllowTextureLookup(string componentName) => true; + + protected virtual bool AllowSampleLookup(string componentName) => true; + + protected virtual bool AllowConfigurationLookup => true; + + public SkinProvidingContainer(ISkin skin) { this.skin = skin; } @@ -34,7 +37,7 @@ namespace osu.Game.Skinning public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins.Value && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) + if (AllowDrawableLookup(componentName) && (sourceDrawable = skin?.GetDrawableComponent(componentName)) != null) return sourceDrawable; return fallbackSource?.GetDrawableComponent(componentName); @@ -43,7 +46,7 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins.Value && (sourceTexture = skin?.GetTexture(componentName)) != null) + if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null) return sourceTexture; return fallbackSource.GetTexture(componentName); @@ -52,7 +55,7 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds.Value && (sourceChannel = skin?.GetSample(sampleName)) != null) + if (AllowSampleLookup(sampleName) && (sourceChannel = skin?.GetSample(sampleName)) != null) return sourceChannel; return fallbackSource?.GetSample(sampleName); @@ -61,14 +64,13 @@ namespace osu.Game.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { TValue val; - if ((skin as Skin)?.Configuration is TConfiguration conf) - if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) - return val; + if (AllowConfigurationLookup && skin != null && (val = skin.GetValue(query)) != null) + return val; return fallbackSource == null ? default : fallbackSource.GetValue(query); } - private void onSourceChanged() => SourceChanged?.Invoke(); + protected virtual void TriggerSourceChanged() => SourceChanged?.Invoke(); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { @@ -76,18 +78,10 @@ namespace osu.Game.Skinning fallbackSource = dependencies.Get(); if (fallbackSource != null) - fallbackSource.SourceChanged += onSourceChanged; + fallbackSource.SourceChanged += TriggerSourceChanged; dependencies.CacheAs(this); - var config = dependencies.Get(); - - config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); - config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); - - beatmapSkins.BindValueChanged(_ => onSourceChanged()); - beatmapHitsounds.BindValueChanged(_ => onSourceChanged()); - return dependencies; } @@ -99,7 +93,7 @@ namespace osu.Game.Skinning base.Dispose(isDisposing); if (fallbackSource != null) - fallbackSource.SourceChanged -= onSourceChanged; + fallbackSource.SourceChanged -= TriggerSourceChanged; } } } From 5e362d10b1c26443bb6c95c3f61c0b5984e81b7d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:21:49 +0900 Subject: [PATCH 057/162] Add ruleset-specific legacy skin providers This moves implementation of osu! skinnables to OsuLegacySkin. --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 228 +++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 + osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 3 + osu.Game/Screens/Play/Player.cs | 41 ++-- osu.Game/Skinning/LegacySkin.cs | 137 -------------- 6 files changed, 261 insertions(+), 153 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/OsuLegacySkin.cs diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs new file mode 100644 index 0000000000..0085d64353 --- /dev/null +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -0,0 +1,228 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu +{ + public class OsuLegacySkin : ISkin + { + private readonly ISkin source; + + private Lazy configuration; + + private Lazy hasHitCircle; + + /// + /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. + /// Their hittable area is 128px, but the actual circle portion is 118px. + /// We must account for some gameplay elements such as slider bodies, where this padding is not present. + /// + private const float legacy_circle_radius = 64 - 5; + + public OsuLegacySkin(ISkinSource source) + { + this.source = source; + + source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + configuration = new Lazy(() => + { + var config = new SkinConfiguration(); + if (hasHitCircle.Value) + config.SliderPathRadius = legacy_circle_radius; + return config; + }); + + hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); + } + + private const double default_frame_time = 1000 / 60d; + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/osu/sliderball": + var sliderBallContent = getAnimation("sliderb", true, true, ""); + + if (sliderBallContent != null) + { + var size = sliderBallContent.Size; + + sliderBallContent.RelativeSizeAxes = Axes.Both; + sliderBallContent.Size = Vector2.One; + + return new LegacySliderBall(sliderBallContent) + { + Size = size + }; + } + + return null; + + case "Play/osu/hitcircle": + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + } + + return null; + } + + private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") + { + Texture texture; + + Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); + + TextureAnimation animation = null; + + if (animatable) + { + for (int i = 0;; i++) + { + if ((texture = getFrameTexture(i)) == null) + break; + + if (animation == null) + animation = new TextureAnimation + { + DefaultFrameLength = default_frame_time, + Repeat = looping + }; + + animation.AddFrame(texture); + } + } + + if (animation != null) + return animation; + + if ((texture = source.GetTexture(componentName)) != null) + return new Sprite { Texture = texture }; + + return null; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(string sampleName) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + + public class LegacySliderBall : CompositeDrawable + { + private readonly Drawable animationContent; + + public LegacySliderBall(Drawable animationContent) + { + this.animationContent = animationContent; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableHitObject drawableObject) + { + animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + + InternalChildren = new[] + { + new Sprite + { + Texture = skin.GetTexture("sliderb-nd"), + Colour = new Color4(5, 5, 5, 255), + }, + animationContent, + new Sprite + { + Texture = skin.GetTexture("sliderb-spec"), + Blending = BlendingParameters.Additive, + }, + }; + } + } + + public class LegacyMainCirclePiece : CompositeDrawable + { + public LegacyMainCirclePiece() + { + Size = new Vector2(128); + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + Sprite hitCircleSprite; + + InternalChildren = new Drawable[] + { + hitCircleSprite = new Sprite + { + Texture = skin.GetTexture("hitcircle"), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }, confineMode: ConfineMode.NoScaling) + { + Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() + }, + new Sprite + { + Texture = skin.GetTexture("hitcircleoverlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + } + + private void updateState(ValueChangedEvent state) + { + const double legacy_fade_duration = 240; + + switch (state.NewValue) + { + case ArmedState.Hit: + this.FadeOut(legacy_fade_duration, Easing.Out); + this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..4211ae253c 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -24,6 +24,7 @@ using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu { @@ -163,6 +164,8 @@ namespace osu.Game.Rulesets.Osu public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); + public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkin(source); + public override int? LegacyID => 0; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9037faf606..178183bd22 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.UI }, // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal // Todo: Remove when hitobjects are properly pooled - new LocalSkinOverrideContainer(null) + new SkinProvidingContainer(null) { RelativeSizeAxes = Axes.Both, Child = HitObjectContainer, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 42b1322cae..2fbbd74d7a 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -18,6 +18,7 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets { @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); + public virtual ISkin CreateLegacySkinProvider(ISkinSource source) => null; + protected Ruleset(RulesetInfo rulesetInfo = null) { RulesetInfo = rulesetInfo ?? createRulesetInfo(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e7398be176..deed17a049 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -60,7 +60,9 @@ namespace osu.Game.Screens.Play [Resolved] private ScoreManager scoreManager { get; set; } - private RulesetInfo ruleset; + private RulesetInfo rulesetInfo; + + private Ruleset ruleset; private IAPIProvider api; @@ -121,20 +123,29 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + SkinProvidingContainer skinProvidingContainer = new BeatmapSkinProvidingContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + }; + GameplayClockContainer.Children = new[] { DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }, new ScalingContainer(ScalingMode.Gameplay) { - Child = new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + Child = skinProvidingContainer.WithChild( + // the skinProvidingContainer is used as the fallback source here to allow the ruleset-specific skin implementation + // full access to all skin sources. + new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(skinProvidingContainer)) { - DrawableRuleset, - new ComboEffects(ScoreProcessor) + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + DrawableRuleset, + new ComboEffects(ScoreProcessor) + } } - } + ) }, breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { @@ -222,20 +233,20 @@ namespace osu.Game.Screens.Play if (beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); - ruleset = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; - var rulesetInstance = ruleset.CreateInstance(); + rulesetInfo = Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset; + ruleset = rulesetInfo.CreateInstance(); try { - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. - ruleset = beatmap.BeatmapInfo.Ruleset; - rulesetInstance = ruleset.CreateInstance(); - DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); + rulesetInfo = beatmap.BeatmapInfo.Ruleset; + ruleset = rulesetInfo.CreateInstance(); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } if (!DrawableRuleset.Objects.Any()) @@ -313,7 +324,7 @@ namespace osu.Game.Screens.Play var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = ruleset, + Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), User = api.LocalUser.Value, }; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 48310cf027..785daba878 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; @@ -20,8 +19,6 @@ using osu.Framework.Text; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -34,13 +31,6 @@ namespace osu.Game.Skinning protected IResourceStore Samples; - /// - /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. - /// Their hittable area is 128px, but the actual circle portion is 118px. - /// We must account for some gameplay elements such as slider bodies, where this padding is not present. - /// - private const float legacy_circle_radius = 64 - 5; - public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { @@ -48,8 +38,6 @@ namespace osu.Game.Skinning if (!Configuration.CustomColours.ContainsKey("SliderBall")) Configuration.CustomColours["SliderBall"] = new Color4(2, 170, 255, 255); } - private readonly bool hasHitCircle; - protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) { @@ -62,14 +50,6 @@ namespace osu.Game.Skinning Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); - - using (var testStream = storage.GetStream("hitcircle@2x") ?? storage.GetStream("hitcircle")) - hasHitCircle |= testStream != null; - - if (hasHitCircle) - { - Configuration.SliderPathRadius = legacy_circle_radius; - } } protected override void Dispose(bool isDisposing) @@ -94,30 +74,6 @@ namespace osu.Game.Skinning return null; - case "Play/osu/sliderball": - var sliderBallContent = getAnimation("sliderb", true, true, ""); - - if (sliderBallContent != null) - { - var size = sliderBallContent.Size; - - sliderBallContent.RelativeSizeAxes = Axes.Both; - sliderBallContent.Size = Vector2.One; - - return new LegacySliderBall(sliderBallContent) - { - Size = size - }; - } - - return null; - - case "Play/osu/hitcircle": - if (hasHitCircle) - return new LegacyMainCirclePiece(); - - return null; - case "Play/osu/sliderfollowcircle": animatable = true; break; @@ -355,99 +311,6 @@ namespace osu.Game.Skinning } } - public class LegacySliderBall : CompositeDrawable - { - private readonly Drawable animationContent; - - public LegacySliderBall(Drawable animationContent) - { - this.animationContent = animationContent; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableObject) - { - animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; - - InternalChildren = new[] - { - new Sprite - { - Texture = skin.GetTexture("sliderb-nd"), - Colour = new Color4(5, 5, 5, 255), - }, - animationContent, - new Sprite - { - Texture = skin.GetTexture("sliderb-spec"), - Blending = BlendingParameters.Additive, - }, - }; - } - } - - public class LegacyMainCirclePiece : CompositeDrawable - { - public LegacyMainCirclePiece() - { - Size = new Vector2(128); - } - - private readonly IBindable state = new Bindable(); - - private readonly Bindable accentColour = new Bindable(); - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - Sprite hitCircleSprite; - - InternalChildren = new Drawable[] - { - hitCircleSprite = new Sprite - { - Texture = skin.GetTexture("hitcircle"), - Colour = drawableObject.AccentColour.Value, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling) - { - Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() - }, - new Sprite - { - Texture = skin.GetTexture("hitcircleoverlay"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - - state.BindTo(drawableObject.State); - state.BindValueChanged(updateState, true); - - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - } - - private void updateState(ValueChangedEvent state) - { - const double legacy_fade_duration = 240; - - switch (state.NewValue) - { - case ArmedState.Hit: - this.FadeOut(legacy_fade_duration, Easing.Out); - this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - break; - } - } - } - /// /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). From 6e3a63dae8a9dbc59d47993d884b97b2414de7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 12:31:51 +0900 Subject: [PATCH 058/162] Update tests --- .../SkinnableTestScene.cs | 20 +++++++++++++++---- .../Gameplay/TestSceneSkinnableDrawable.cs | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index 02716dc1d5..c3a64f8b54 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -37,10 +37,22 @@ namespace osu.Game.Rulesets.Osu.Tests public void SetContents(Func creationFunction) { - Cell(0).Child = new LocalSkinOverrideContainer(null) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(1).Child = new LocalSkinOverrideContainer(metricsSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(2).Child = new LocalSkinOverrideContainer(defaultSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); - Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction()); + Cell(0).Child = createProvider(null, creationFunction); + Cell(1).Child = createProvider(metricsSkin, creationFunction); + Cell(2).Child = createProvider(defaultSkin, creationFunction); + Cell(3).Child = createProvider(specialSkin, creationFunction); + } + + private Drawable createProvider(Skin skin, Func creationFunction) + { + var mainProvider = new SkinProvidingContainer(skin) { RelativeSizeAxes = Axes.Both }; + + return mainProvider + .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) + { + RelativeSizeAxes = Axes.Both, + Child = creationFunction() + }); } private class TestLegacySkin : LegacySkin diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs index 0b5978e3eb..b18eca0600 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("setup layout larger source", () => { - Child = new LocalSkinOverrideContainer(new SizedSource(50)) + Child = new SkinProvidingContainer(new SizedSource(50)) { RelativeSizeAxes = Axes.Both, Child = fill = new FillFlowContainer @@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("setup layout larger source", () => { - Child = new LocalSkinOverrideContainer(new SizedSource(30)) + Child = new SkinProvidingContainer(new SizedSource(30)) { RelativeSizeAxes = Axes.Both, Child = fill = new FillFlowContainer @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinSourceContainer { RelativeSizeAxes = Axes.Both, - Child = new LocalSkinOverrideContainer(secondarySource) + Child = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Gameplay Child = new SkinSourceContainer { RelativeSizeAxes = Axes.Both, - Child = target = new LocalSkinOverrideContainer(secondarySource) + Child = target = new SkinProvidingContainer(secondarySource) { RelativeSizeAxes = Axes.Both, } From d99c60adc7cf97d11de29631ec46c1c652a1b62e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 12:51:13 +0900 Subject: [PATCH 059/162] Provide a way to scale beat lengths relative to each other --- .../Rulesets/Timing/MultiplierControlPoint.cs | 8 +++- .../UI/Scrolling/DrawableScrollingRuleset.cs | 38 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 9bab065d1e..ffa35c24cd 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -20,7 +20,13 @@ namespace osu.Game.Rulesets.Timing /// /// The aggregate multiplier which this provides. /// - public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * 1000 / TimingPoint.BeatLength; + public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength; + + /// + /// The base beat length to scale the provided multiplier relative to. + /// + /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2. + public double BaseBeatLength = 1000; /// /// The velocity multiplier. diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 42ec0b79b9..385f824ef5 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -69,6 +69,11 @@ namespace osu.Game.Rulesets.UI.Scrolling /// protected virtual bool UserScrollSpeedAdjustment => true; + /// + /// Whether beat lengths should scale relative to the most common beat length in the . + /// + protected virtual bool RelativeScaleBeatLengths => false; + /// /// Provides the default s that adjust the scrolling rate of s /// inside this . @@ -107,16 +112,35 @@ namespace osu.Game.Rulesets.UI.Scrolling [BackgroundDependencyLoader] private void load() { - // Calculate default multiplier control points + double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; + double baseBeatLength = 1000; + + if (RelativeScaleBeatLengths) + { + IReadOnlyList timingPoints = Beatmap.ControlPointInfo.TimingPoints; + double maxDuration = 0; + + for (int i = 0; i < timingPoints.Count; i++) + { + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; + double duration = endTime - timingPoints[i].Time; + + if (duration > maxDuration) + { + maxDuration = duration; + baseBeatLength = timingPoints[i].BeatLength; + } + } + } + + // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point var lastTimingPoint = new TimingControlPoint(); var lastDifficultyPoint = new DifficultyControlPoint(); - - // Merge timing + difficulty points var allPoints = new SortedList(Comparer.Default); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); - // Generate the timing points, making non-timing changes use the previous timing change + // Generate the timing points, making non-timing changes use the previous timing change and vice-versa var timingChanges = allPoints.Select(c => { var timingPoint = c as TimingControlPoint; @@ -131,14 +155,13 @@ namespace osu.Game.Rulesets.UI.Scrolling return new MultiplierControlPoint(c.Time) { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, + BaseBeatLength = baseBeatLength, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }; }); - double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - - // Perform some post processing of the timing changes + // Trim unwanted sequences of timing changes timingChanges = timingChanges // Collapse sections after the last hit object .Where(s => s.StartTime <= lastObjectTime) @@ -147,7 +170,6 @@ namespace osu.Game.Rulesets.UI.Scrolling controlPoints.AddRange(timingChanges); - // If we have no control points, add a default one if (controlPoints.Count == 0) controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } From e30ae57ea66325491eb1f94c7314d6270da5d3c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 12:51:23 +0900 Subject: [PATCH 060/162] Scale mania beat lengths relative to each other --- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index c8aeda8fe4..0718de2c7d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -36,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI public IEnumerable BarLines; + protected override bool RelativeScaleBeatLengths => true; + protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); From 7b82121b8503f81de85013e9c82435d4b584d34c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 13:03:00 +0900 Subject: [PATCH 061/162] Add comment about lazy usage --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs index 0085d64353..988eb6a439 100644 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -45,6 +45,7 @@ namespace osu.Game.Rulesets.Osu private void sourceChanged() { + // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. configuration = new Lazy(() => { var config = new SkinConfiguration(); From db987c6077153102848ffe9f16c79de8480c39e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Aug 2019 14:24:48 +0900 Subject: [PATCH 062/162] Move SliderBall colour logic to OsuLegacySkinProvider --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs index 988eb6a439..1957156918 100644 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs @@ -51,6 +51,12 @@ namespace osu.Game.Rulesets.Osu var config = new SkinConfiguration(); if (hasHitCircle.Value) config.SliderPathRadius = legacy_circle_radius; + + // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). + config.CustomColours["SliderBall"] = + source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) + ?? new Color4(2, 170, 255, 255); + return config; }); From 1cfe2b7de8388ade173269519e7947400ef18778 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:25:23 +0900 Subject: [PATCH 063/162] Fix timing points beyond the end time potentially becoming dominant --- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 385f824ef5..65a22b10b7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -122,6 +122,9 @@ namespace osu.Game.Rulesets.UI.Scrolling for (int i = 0; i < timingPoints.Count; i++) { + if (timingPoints[i].Time > lastObjectTime) + break; + double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; double duration = endTime - timingPoints[i].Time; From fb8b5ee1060dfd0ec8467ed15ddcc4c84806603f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 26 Aug 2019 16:31:46 +0900 Subject: [PATCH 064/162] Add test --- .../TestSceneDrawableScrollingRuleset.cs | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs new file mode 100644 index 0000000000..ee11fc0d06 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -0,0 +1,305 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneDrawableScrollingRuleset : OsuTestScene + { + /// + /// The amount of time visible by the "view window" of the playfield. + /// All hitobjects added through are spaced apart by this value, such that for a beat length of 1000, + /// there will be at most 2 hitobjects visible in the "view window". + /// + private const double time_range = 1000; + + private readonly ManualClock testClock = new ManualClock(); + private TestDrawableScrollingRuleset drawableRuleset; + + [SetUp] + public void Setup() => Schedule(() => testClock.CurrentTime = 0); + + [Test] + public void TestRelativeBeatLengthScaleSingleTimingPoint() + { + var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + assertPosition(0, 0f); + + // The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear + // at the bottom of the view window regardless of the timing point's beat length + assertPosition(1, 1f); + } + + [Test] + public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range / 2 }, + new TimingControlPoint { Time = 12000, BeatLength = time_range }, + new TimingControlPoint { Time = 100000, BeatLength = time_range }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + assertPosition(0, 0f); + assertPosition(1, 1f); + } + + [Test] + public void TestRelativeBeatLengthScaleFromSecondTimingPoint() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range }, + new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + + createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); + + // The first timing point should have a relative velocity of 2 + assertPosition(0, 0f); + assertPosition(1, 0.5f); + assertPosition(2, 1f); + + // Move to the second timing point + setTime(3 * time_range); + assertPosition(3, 0f); + + // As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window + assertPosition(4, 1f); + } + + [Test] + public void TestNonRelativeScale() + { + var beatmap = createBeatmap( + new TimingControlPoint { BeatLength = time_range }, + new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + + createTest(beatmap); + + assertPosition(0, 0f); + assertPosition(1, 1); + + // Move to the second timing point + setTime(3 * time_range); + assertPosition(3, 0f); + + // For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen) + // To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom + setTime(3 * time_range + time_range / 2); + assertPosition(4, 1f); + } + + private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}", + () => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY)); + + private void setTime(double time) + { + AddStep($"set time = {time}", () => testClock.CurrentTime = time); + } + + /// + /// Creates an , containing 10 hitobjects and user-provided timing points. + /// The hitobjects are spaced milliseconds apart. + /// + /// The timing points to add to the beatmap. + /// The . + private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + { + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; + + beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); + + for (int i = 0; i < 10; i++) + beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); + + return beatmap; + } + + private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () => + { + var ruleset = new TestScrollingRuleset(); + + drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty()); + drawableRuleset.FrameStablePlayback = false; + + overrideAction?.Invoke(drawableRuleset); + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 0.75f, + Width = 400, + Masking = true, + Clock = new FramedClock(testClock), + Child = drawableRuleset + }; + }); + + #region Ruleset + + private class TestScrollingRuleset : Ruleset + { + public TestScrollingRuleset(RulesetInfo rulesetInfo = null) + : base(rulesetInfo) + { + } + + public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); + + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); + + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); + + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + + public override string Description { get; } = string.Empty; + + public override string ShortName { get; } = string.Empty; + } + + private class TestDrawableScrollingRuleset : DrawableScrollingRuleset + { + public bool RelativeScaleBeatLengthsOverride { get; set; } + + protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride; + + protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; + + public TestDrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) + { + TimeRange.Value = time_range; + } + + public override DrawableHitObject CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h); + + protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager(); + + protected override Playfield CreatePlayfield() => new TestPlayfield(); + } + + private class TestPlayfield : ScrollingPlayfield + { + public TestPlayfield() + { + AddInternal(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 150 }, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = Color4.Green + }, + HitObjectContainer + } + } + } + }); + } + } + + private class TestBeatmapConverter : BeatmapConverter + { + public TestBeatmapConverter(IBeatmap beatmap) + : base(beatmap) + { + } + + protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) }; + + protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) + { + yield return new TestHitObject + { + StartTime = original.StartTime, + EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + }; + } + } + + #endregion + + #region HitObject + + private class TestHitObject : HitObject, IHasEndTime + { + public double EndTime { get; set; } + + public double Duration => EndTime - StartTime; + } + + private class DrawableTestHitObject : DrawableHitObject + { + public DrawableTestHitObject(TestHitObject hitObject) + : base(hitObject) + { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(100, 25); + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.LightPink + }, + new Box + { + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = Color4.Red + } + }); + } + } + + #endregion + } +} From 7aeeb65ae7fe6df164155f31414997c8c64629ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Aug 2019 18:27:21 +0900 Subject: [PATCH 065/162] Tidy up Player's container loading logic Fixes drawable ruleset being loaded before skin sources are finished, by loading them as a separate operation (to avoid children being loaded first). --- .../SkinnableTestScene.cs | 3 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 1 - osu.Game/Screens/Play/Player.cs | 81 +++++++++++-------- osu.Game/Skinning/SkinProvidingContainer.cs | 2 + 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs index c3a64f8b54..29e5146ff1 100644 --- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs @@ -45,12 +45,11 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createProvider(Skin skin, Func creationFunction) { - var mainProvider = new SkinProvidingContainer(skin) { RelativeSizeAxes = Axes.Both }; + var mainProvider = new SkinProvidingContainer(skin); return mainProvider .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider)) { - RelativeSizeAxes = Axes.Both, Child = creationFunction() }); } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 178183bd22..ea7eee8bb8 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.UI // Todo: Remove when hitobjects are properly pooled new SkinProvidingContainer(null) { - RelativeSizeAxes = Axes.Both, Child = HitObjectContainer, }, approachCircles = new ApproachCircleProxyContainer diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index deed17a049..b487f3e61b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -123,30 +123,53 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); - SkinProvidingContainer skinProvidingContainer = new BeatmapSkinProvidingContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - }; + addUnderlayComponents(GameplayClockContainer); + addGameplayComponents(GameplayClockContainer, working); + addOverlayComponents(GameplayClockContainer, working); - GameplayClockContainer.Children = new[] + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + + // bind clock into components that require it + DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + + // Bind ScoreProcessor to ourselves + ScoreProcessor.AllJudged += onCompletion; + ScoreProcessor.Failed += onFail; + + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToScoreProcessor(ScoreProcessor); + } + + private void addUnderlayComponents(Container target) + { + target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); + } + + private void addGameplayComponents(Container target, WorkingBeatmap working) + { + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin); + + // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation + // full access to all skin sources. + var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider)); + + // load the skinning hierarchy first. + // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. + target.Add(new ScalingContainer(ScalingMode.Gameplay) + .WithChild(beatmapSkinProvider + .WithChild(target = rulesetSkinProvider))); + + target.AddRange(new Drawable[] + { + DrawableRuleset, + new ComboEffects(ScoreProcessor) + }); + } + + private void addOverlayComponents(Container target, WorkingBeatmap working) + { + target.AddRange(new[] { - DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }, - new ScalingContainer(ScalingMode.Gameplay) - { - Child = skinProvidingContainer.WithChild( - // the skinProvidingContainer is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(skinProvidingContainer)) - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - DrawableRuleset, - new ComboEffects(ScoreProcessor) - } - } - ) - }, breakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, @@ -205,19 +228,7 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } - }; - - DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); - - // bind clock into components that require it - DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); - - // Bind ScoreProcessor to ourselves - ScoreProcessor.AllJudged += onCompletion; - ScoreProcessor.Failed += onFail; - - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToScoreProcessor(ScoreProcessor); + }); } private WorkingBeatmap loadBeatmap() diff --git a/osu.Game/Skinning/SkinProvidingContainer.cs b/osu.Game/Skinning/SkinProvidingContainer.cs index a92a0ae0d1..9d6fbfdc74 100644 --- a/osu.Game/Skinning/SkinProvidingContainer.cs +++ b/osu.Game/Skinning/SkinProvidingContainer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Skinning public SkinProvidingContainer(ISkin skin) { this.skin = skin; + + RelativeSizeAxes = Axes.Both; } public Drawable GetDrawableComponent(string componentName) From f49b58c102e641ada62971df2413b8e83e1aefb6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:30:41 +0300 Subject: [PATCH 066/162] Simplify text building --- .../Kudosu/DrawableKudosuHistoryItem.cs | 46 ++++++------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 3cc39f0e73..4dba07713f 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -55,83 +55,67 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from obtaining votes in modding post of "); - addPostPart(); break; case KudosuAction.Give: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart($@" from {userLinkTemplate()} for a post at "); - addPostPart(); break; case KudosuAction.Reset: addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); - addPostPart(); break; case KudosuAction.VoteReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from losing votes in modding post of "); - addPostPart(); break; case KudosuAction.DenyKudosuReset: - linkFlowContainer.AddText(@"Denied "); - addKudosuPart(); + addKudosuPart(@"Denied"); addMainPart(@" from modding post "); - addPostPart(); break; case KudosuAction.Revoke: addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); - addPostPart(); break; case KudosuAction.AllowKudosuGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from kudosu deny repeal of modding post "); - addPostPart(); break; case KudosuAction.DeleteReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from modding post deletion of "); - addPostPart(); break; case KudosuAction.RestoreGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from modding post restoration of "); - addPostPart(); break; case KudosuAction.RecalculateGive: - linkFlowContainer.AddText(@"Received "); - addKudosuPart(); + addKudosuPart(@"Received"); addMainPart(@" from votes recalculation in modding post of "); - addPostPart(); break; case KudosuAction.RecalculateReset: - linkFlowContainer.AddText(@"Lost "); - addKudosuPart(); + addKudosuPart(@"Lost"); addMainPart(@" from votes recalculation in modding post of "); - addPostPart(); break; } + + addPostPart(); } - private void addKudosuPart() + private void addKudosuPart(string prefix) { - linkFlowContainer.AddText($@"{historyItem.Amount} kudosu", t => + linkFlowContainer.AddText(prefix); + + linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; From ed827d514f42d2d774f9b592dfee85c962036b94 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 27 Aug 2019 15:36:08 +0300 Subject: [PATCH 067/162] Add comments --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index d02f71c339..19ce11aa13 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -15,6 +15,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("amount")] private int amount { + //We can receive negative values. However "action" is enough to build needed items set => Amount = Math.Abs(value); } @@ -49,6 +50,7 @@ namespace osu.Game.Online.API.Requests.Responses { set { + //We will receive something like "foo.bar" or just "foo" string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); From 7cc92e2fad4cdfbeefd12d718d73e3cf0ad27af8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Aug 2019 13:20:28 +0900 Subject: [PATCH 068/162] Make taiko proxy containers use LifetimeManagementContainer --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e62dc45cab..a10f70a344 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -44,9 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly JudgementContainer judgementContainer; internal readonly HitTarget HitTarget; - private readonly Container topLevelHitContainer; - - private readonly Container barlineContainer; + private readonly ProxyContainer topLevelHitContainer; + private readonly ProxyContainer barlineContainer; private readonly Container overlayBackgroundContainer; private readonly Container backgroundContainer; @@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - barlineContainer = new Container + barlineContainer = new ProxyContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } @@ -183,7 +182,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - topLevelHitContainer = new Container + topLevelHitContainer = new ProxyContainer { Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, @@ -256,5 +255,15 @@ namespace osu.Game.Rulesets.Taiko.UI break; } } + + private class ProxyContainer : LifetimeManagementContainer + { + public new MarginPadding Padding + { + set => base.Padding = value; + } + + public void Add(Drawable proxy) => AddInternal(proxy); + } } } From bb22c2d6e48b8039b967bd7c7320f84e382af794 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 17:29:18 +0900 Subject: [PATCH 069/162] Tidy up text construction --- .../Kudosu/DrawableKudosuHistoryItem.cs | 116 ++++++++++-------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 4dba07713f..fb7d597012 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -50,85 +50,101 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { date.Colour = colours.GreySeafoamLighter; - string userLinkTemplate() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + string prefix = getPrefix(historyItem); + var formattedSource = MessageFormatter.FormatText(getSource(historyItem)); + + if (!string.IsNullOrEmpty(prefix)) + { + linkFlowContainer.AddText(prefix); + linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + { + t.Font = t.Font.With(italics: true); + t.Colour = colours.Blue; + }); + } + + linkFlowContainer.AddLinks(formattedSource.Text + " ", formattedSource.Links); + linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); + } + + private string getSource(APIKudosuHistory historyItem) + { + string userLink() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; switch (historyItem.Action) { case KudosuAction.VoteGive: - addKudosuPart(@"Received"); - addMainPart(@" from obtaining votes in modding post of "); - break; + return @" from obtaining votes in modding post of"; case KudosuAction.Give: - addKudosuPart(@"Received"); - addMainPart($@" from {userLinkTemplate()} for a post at "); - break; + return $@" from {userLink()} for a post at"; case KudosuAction.Reset: - addMainPart($@"Kudosu reset by {userLinkTemplate()} for the post "); - break; + return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: - addKudosuPart(@"Lost"); - addMainPart(@" from losing votes in modding post of "); - break; + return @" from losing votes in modding post of"; case KudosuAction.DenyKudosuReset: - addKudosuPart(@"Denied"); - addMainPart(@" from modding post "); - break; + return @" from modding post"; case KudosuAction.Revoke: - addMainPart($@"Denied kudosu by {userLinkTemplate()} for the post "); - break; + return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: - addKudosuPart(@"Received"); - addMainPart(@" from kudosu deny repeal of modding post "); - break; + return @" from kudosu deny repeal of modding post"; case KudosuAction.DeleteReset: - addKudosuPart(@"Lost"); - addMainPart(@" from modding post deletion of "); - break; + return @" from modding post deletion of"; case KudosuAction.RestoreGive: - addKudosuPart(@"Received"); - addMainPart(@" from modding post restoration of "); - break; + return @" from modding post restoration of"; case KudosuAction.RecalculateGive: - addKudosuPart(@"Received"); - addMainPart(@" from votes recalculation in modding post of "); - break; + return @" from votes recalculation in modding post of"; case KudosuAction.RecalculateReset: - addKudosuPart(@"Lost"); - addMainPart(@" from votes recalculation in modding post of "); - break; + return @" from votes recalculation in modding post of"; + + default: + return @" from unknown event "; } - - addPostPart(); } - private void addKudosuPart(string prefix) + private string getPrefix(APIKudosuHistory historyItem) { - linkFlowContainer.AddText(prefix); - - linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + switch (historyItem.Action) { - t.Font = t.Font.With(italics: true); - t.Colour = colours.Blue; - }); + case KudosuAction.VoteGive: + return @"Received"; + + case KudosuAction.Give: + return @"Received"; + + case KudosuAction.VoteReset: + return @"Lost"; + + case KudosuAction.DenyKudosuReset: + return @"Denied"; + + case KudosuAction.AllowKudosuGive: + return @"Received"; + + case KudosuAction.DeleteReset: + return @"Lost"; + + case KudosuAction.RestoreGive: + return @"Received"; + + case KudosuAction.RecalculateGive: + return @"Received"; + + case KudosuAction.RecalculateReset: + return @"Lost"; + + default: + return null; + } } - - private void addMainPart(string text) - { - var formatted = MessageFormatter.FormatText(text); - - linkFlowContainer.AddLinks(formatted.Text, formatted.Links); - } - - private void addPostPart() => linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } } From 9752dbf9505ba614d6609c42fa874aeb73aad026 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 18:10:58 +0900 Subject: [PATCH 070/162] Fix osu! approach circles fading in too late --- .../Objects/Drawables/DrawableHitCircle.cs | 6 +++++- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index ca124e9214..0af278f6a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly HitArea hitArea; + private readonly SkinnableDrawable mainContent; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -56,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return true; }, }, - new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), + mainContent = new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)), ApproachCircle = new ApproachCircle { Alpha = 0, @@ -108,6 +110,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { base.UpdateInitialTransforms(); + mainContent.FadeInFromZero(HitObject.TimeFadeIn); + ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.ScaleTo(1f, HitObject.TimePreempt); ApproachCircle.Expire(true); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index a89fb8b682..17efefa959 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn); + protected override void UpdateInitialTransforms() => this.FadeInFromZero(); private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index a0626707af..1749ea1f60 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -93,6 +93,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); + + Body.FadeInFromZero(HitObject.TimeFadeIn); + } + [BackgroundDependencyLoader] private void load() { From 9a98f39f06623a181bf4298e8ffb1c8f80e238a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 18:12:47 +0900 Subject: [PATCH 071/162] Share logic with other rulesets (and make default) --- .../Objects/Drawable/DrawableCatchHitObject.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 -- .../Objects/Drawables/DrawableOsuHitObject.cs | 2 -- .../Objects/Drawables/DrawableTaikoHitObject.cs | 2 -- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ++++ 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index f4218061d4..00734810b3 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeIn(200); + protected override void UpdateInitialTransforms() => this.FadeInFromZero(200); protected override void UpdateStateTransforms(ArmedState state) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index ce1484d460..e5b114ca81 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -46,8 +46,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - protected override void UpdateInitialTransforms() => this.FadeIn(); - protected override void UpdateStateTransforms(ArmedState state) { switch (state) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 17efefa959..b4f5642f45 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt; - protected override void UpdateInitialTransforms() => this.FadeInFromZero(); - private OsuInputManager osuActionInputManager; internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 5f3bab5b28..5424ccb4de 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public abstract bool OnPressed(TaikoAction action); public virtual bool OnReleased(TaikoAction action) => false; - protected override void UpdateInitialTransforms() => this.FadeIn(); - public override double LifetimeStart { get => base.LifetimeStart; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 80e70589bd..4a6f261905 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; @@ -186,6 +187,8 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Apply (generally fade-in) transforms leading into the start time. /// The local drawable hierarchy is recursively delayed to for convenience. + /// + /// By default this will fade in the object from zero with no duration. /// /// /// This is called once before every . This is to ensure a good state in the case @@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// protected virtual void UpdateInitialTransforms() { + this.FadeInFromZero(); } /// From 01aede3e299f53f0fd2c86f00a5c5087559ace88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 19:57:17 +0900 Subject: [PATCH 072/162] Add comprehensive skin fallback integration testing --- .../TestSceneOsuPlayer.cs | 140 ++++++++++++++++++ .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 8 +- osu.Game/Skinning/SkinnableDrawable.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 7 +- osu.Game/Tests/Visual/TestPlayer.cs | 3 + 6 files changed, 153 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 0a33b09ba8..7c84ae80c8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -1,7 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests @@ -9,9 +25,133 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneOsuPlayer : PlayerTestScene { + private readonly TestSource testUserSkin; + private readonly TestSource testBeatmapSkin; + public TestSceneOsuPlayer() : base(new OsuRuleset()) { + testUserSkin = new TestSource("user"); + testBeatmapSkin = new TestSource("beatmap"); + } + + [Test] + public void TestBeatmapSkinDefault() + { + AddStep("enable user provider", () => testUserSkin.Enabled = true); + + AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); + checkNextHitObject("beatmap"); + + AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); + checkNextHitObject("user"); + + AddStep("disable user provider", () => testUserSkin.Enabled = false); + checkNextHitObject(null); + } + + private void checkNextHitObject(string skin) => + AddUntilStep($"check skin from {skin}", () => + { + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + + if (firstObject == null) + return false; + + var skinnable = firstObject?.ApproachCircle.Child as SkinnableDrawable; + + if (skin == null && skinnable?.Drawable is Sprite) + // check for default skin provider + return true; + + var text = skinnable?.Drawable as SpriteText; + + return text?.Text == skin; + }); + + [Resolved] + private AudioManager audio { get; set; } + + protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + + public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly ISkinSource skin; + + public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, frameBasedClock, audio) + { + this.skin = skin; + } + + protected override ISkin GetSkin() => skin; + } + + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + + public class TestSource : ISkinSource + { + private readonly string identifier; + + public TestSource(string identifier) + { + this.identifier = identifier; + } + + public Drawable GetDrawableComponent(string componentName) + { + if (!enabled) return null; + + return new SpriteText + { + Text = identifier, + Font = OsuFont.Default.With(size: 30), + }; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; + + public event Action SourceChanged; + + private bool enabled = true; + + public bool Enabled + { + get => enabled; + set + { + if (value == enabled) + return; + + enabled = value; + SourceChanged?.Invoke(); + } + } } } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 2d8a0b1249..5bbffc2f77 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -136,7 +136,7 @@ namespace osu.Game.Beatmaps return storyboard; } - protected override Skin GetSkin() + protected override ISkin GetSkin() { try { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8605caa5fe..9addcfbdd7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -46,7 +46,7 @@ namespace osu.Game.Beatmaps background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); - skin = new RecyclableLazy(GetSkin); + skin = new RecyclableLazy(GetSkin); total_count.Value++; } @@ -214,10 +214,10 @@ namespace osu.Game.Beatmaps private readonly RecyclableLazy storyboard; public bool SkinLoaded => skin.IsResultAvailable; - public Skin Skin => skin.Value; + public ISkin Skin => skin.Value; - protected virtual Skin GetSkin() => new DefaultSkin(); - private readonly RecyclableLazy skin; + protected virtual ISkin GetSkin() => new DefaultSkin(); + private readonly RecyclableLazy skin; /// /// Transfer pieces of a beatmap to a new one, where possible, to save on loading. diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 0c635a3d2f..07f802944b 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning /// /// The displayed component. /// - protected Drawable Drawable { get; private set; } + public Drawable Drawable { get; private set; } private readonly string componentName; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 03e17a819c..1ab20ecd48 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -22,12 +22,13 @@ namespace osu.Game.Tests.Visual this.ruleset = ruleset; } + protected OsuConfigManager LocalConfig; + [BackgroundDependencyLoader] private void load() { - OsuConfigManager manager; - Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); - manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; + Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); + LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; } [SetUpSteps] diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index b93a1466e0..31f6edadec 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -9,6 +10,8 @@ namespace osu.Game.Tests.Visual { protected override bool PauseOnFocusLost => false; + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + public TestPlayer(bool allowPause = true, bool showResults = true) : base(allowPause, showResults) { From c7e20b34bae62458749b973acb1e0cd54f1e8c7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:15:28 +0900 Subject: [PATCH 073/162] Cleanup --- osu.Game/Utils/HumanizerUtils.cs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game/Utils/HumanizerUtils.cs b/osu.Game/Utils/HumanizerUtils.cs index 398c76a09f..5b7c3630d9 100644 --- a/osu.Game/Utils/HumanizerUtils.cs +++ b/osu.Game/Utils/HumanizerUtils.cs @@ -10,27 +10,21 @@ namespace osu.Game.Utils public static class HumanizerUtils { /// - /// Humanizes a string using the system culture, then falls back if one cannot be found. - /// - /// A localization lookup failure will throw an exception of type - /// + /// Turns the current or provided date into a human readable sentence /// - /// The time to humanize. - /// A humanized string of the given time. - public static string Humanize(DateTimeOffset dateTimeOffset) + /// The date to be humanized + /// distance of time in words + public static string Humanize(DateTimeOffset input) { - string offset; - + // this works around https://github.com/xamarin/xamarin-android/issues/2012 and https://github.com/Humanizr/Humanizer/issues/690#issuecomment-368536282 try { - offset = dateTimeOffset.Humanize(); + return input.Humanize(); } catch (ArgumentException) { - offset = dateTimeOffset.Humanize(culture: new CultureInfo("en-US")); + return input.Humanize(culture: new CultureInfo("en-US")); } - - return offset; } } } From 8b42890644d503b32e663e028bd711434b1969c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:19:19 +0900 Subject: [PATCH 074/162] Fix unnecessary null check --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 7c84ae80c8..d61378f4f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (firstObject == null) return false; - var skinnable = firstObject?.ApproachCircle.Child as SkinnableDrawable; + var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; if (skin == null && skinnable?.Drawable is Sprite) // check for default skin provider From 348d88846da7d530b992a651512843d123f07bb7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 28 Aug 2019 20:19:22 +0900 Subject: [PATCH 075/162] Add IBeatmap interface for typed hitobject retrieval --- osu.Game/Beatmaps/Beatmap.cs | 12 ++++-------- osu.Game/Beatmaps/IBeatmap.cs | 9 +++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index a09a1bb9cb..5435e86dfd 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps /// /// A Beatmap containing converted HitObjects. /// - public class Beatmap : IBeatmap + public class Beatmap : IBeatmap where T : HitObject { public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo @@ -36,17 +36,13 @@ namespace osu.Game.Beatmaps public List Breaks { get; set; } = new List(); - /// - /// Total amount of break time in the beatmap. - /// [JsonIgnore] public double TotalBreakTime => Breaks.Sum(b => b.Duration); - /// - /// The HitObjects this Beatmap contains. - /// [JsonConverter(typeof(TypedListConverter))] - public List HitObjects = new List(); + public List HitObjects { get; set; } = new List(); + + IReadOnlyList IBeatmap.HitObjects => HitObjects; IReadOnlyList IBeatmap.HitObjects => HitObjects; diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 512fe25809..8f27e0b0e9 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -53,4 +53,13 @@ namespace osu.Game.Beatmaps /// The shallow-cloned beatmap. IBeatmap Clone(); } + + public interface IBeatmap : IBeatmap + where T : HitObject + { + /// + /// The hitobjects contained by this beatmap. + /// + new IReadOnlyList HitObjects { get; } + } } From f6ad95018adda03061931a74039c8a375ae887df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 20:22:16 +0900 Subject: [PATCH 076/162] Centralise default beat length specification --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 4 +++- .../Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 2 +- osu.Game/Rulesets/Timing/MultiplierControlPoint.cs | 2 +- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index e5815a3f3b..ccb8a92b3a 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -14,6 +14,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public const double DEFAULT_BEAT_LENGTH = 1000; + /// /// The beat length at this control point. /// @@ -23,7 +25,7 @@ namespace osu.Game.Beatmaps.ControlPoints set => beatLength = MathHelper.Clamp(value, 6, 60000); } - private double beatLength = 1000; + private double beatLength = DEFAULT_BEAT_LENGTH; public bool Equals(TimingControlPoint other) => base.Equals(other) diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 540f616ea9..8775c15f17 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = 1000; + public override double BeatLength { get; set; } = TimingControlPoint.DEFAULT_BEAT_LENGTH; } } } diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index ffa35c24cd..4b3c3f90f0 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Timing /// The base beat length to scale the provided multiplier relative to. /// /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2. - public double BaseBeatLength = 1000; + public double BaseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; /// /// The velocity multiplier. diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 65a22b10b7..c1a4c9520e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.UI.Scrolling private void load() { double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; - double baseBeatLength = 1000; + double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) { From c6e757fdae6c89b70927a26a83db61ed2cb8e572 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Aug 2019 22:11:23 +0900 Subject: [PATCH 077/162] Remove redundant qualifier --- .../Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 8775c15f17..2c493254e0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = TimingControlPoint.DEFAULT_BEAT_LENGTH; + public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; } } } From 03a4acaf4ca0071c9885e5176e67296bb7b68073 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Aug 2019 12:07:58 +0900 Subject: [PATCH 078/162] Fix drags outside of overlay container bounds not hiding overlay --- .../Containers/OsuFocusedOverlayContainer.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 0f7b26835b..9c948d6f90 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -62,15 +62,23 @@ namespace osu.Game.Graphics.Containers protected override bool OnClick(ClickEvent e) { - if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) - { - Hide(); - return true; - } + closeIfOutside(e); return base.OnClick(e); } + protected override bool OnDragEnd(DragEndEvent e) + { + closeIfOutside(e); + return base.OnDragEnd(e); + } + + private void closeIfOutside(MouseEvent e) + { + if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) + Hide(); + } + public virtual bool OnPressed(GlobalAction action) { switch (action) From 6949c96aaa51952258a335de43b73fc0ba7a793f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 12:43:43 +0900 Subject: [PATCH 079/162] Add initial EditorBeatmap structure --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 ++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 79 +++++++++++++++++++++ osu.Game/Screens/Edit/IEditorBeatmap.cs | 17 +++++ 3 files changed, 110 insertions(+) create mode 100644 osu.Game/Screens/Edit/EditorBeatmap.cs create mode 100644 osu.Game/Screens/Edit/IEditorBeatmap.cs diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38ec09535d..6d98f45187 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose.Components; @@ -185,6 +186,19 @@ namespace osu.Game.Rulesets.Edit { } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var workingBeatmap = parent.Get>(); + var playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + var editorBeatmap = new EditorBeatmap(playableBeatmap); + + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs(editorBeatmap); + + return dependencies; + } + internal override DrawableEditRuleset CreateDrawableRuleset() => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs new file mode 100644 index 0000000000..99dd441578 --- /dev/null +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public class EditorBeatmap : IBeatmap, IEditorBeatmap + where T : HitObject + { + public event Action HitObjectRemoved; + public event Action HitObjectAdded; + + event Action IEditorBeatmap.HitObjectAdded + { + add => HitObjectAdded += value; + remove => HitObjectAdded -= value; + } + + event Action IEditorBeatmap.HitObjectRemoved + { + add => HitObjectRemoved += value; + remove => HitObjectRemoved -= value; + } + + private readonly Beatmap beatmap; + + public EditorBeatmap(Beatmap beatmap) + { + this.beatmap = beatmap; + } + + public BeatmapInfo BeatmapInfo + { + get => beatmap.BeatmapInfo; + set => beatmap.BeatmapInfo = value; + } + + public BeatmapMetadata Metadata => beatmap.Metadata; + + public ControlPointInfo ControlPointInfo => beatmap.ControlPointInfo; + + public List Breaks => beatmap.Breaks; + + public double TotalBreakTime => beatmap.TotalBreakTime; + + IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; + + IReadOnlyList IBeatmap.HitObjects => beatmap.HitObjects; + + public IEnumerable GetStatistics() => beatmap.GetStatistics(); + + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); + + public void Add(T hitObject) + { + // Preserve existing sorting order in the beatmap + var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); + beatmap.HitObjects.Insert(insertionIndex + 1, hitObject); + + HitObjectAdded?.Invoke(hitObject); + } + + public void Remove(T hitObject) + { + if (beatmap.HitObjects.Remove(hitObject)) + HitObjectRemoved?.Invoke(hitObject); + } + + public void Add(HitObject hitObject) => Add((T)hitObject); + + public void Remove(HitObject hitObject) => Remove((T)hitObject); + } +} diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs new file mode 100644 index 0000000000..602a096d65 --- /dev/null +++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Edit +{ + public interface IEditorBeatmap + { + event Action HitObjectAdded; + event Action HitObjectRemoved; + + void Add(HitObject hitObject); + void Remove(HitObject hitObject); + } +} From 840f2246199d7818dc29771de2dd81628f689294 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:05:44 +0900 Subject: [PATCH 080/162] Remove typed events to reduce complexity --- osu.Game/Screens/Edit/EditorBeatmap.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 99dd441578..d9f17abfa5 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -13,20 +13,8 @@ namespace osu.Game.Screens.Edit public class EditorBeatmap : IBeatmap, IEditorBeatmap where T : HitObject { - public event Action HitObjectRemoved; - public event Action HitObjectAdded; - - event Action IEditorBeatmap.HitObjectAdded - { - add => HitObjectAdded += value; - remove => HitObjectAdded -= value; - } - - event Action IEditorBeatmap.HitObjectRemoved - { - add => HitObjectRemoved += value; - remove => HitObjectRemoved -= value; - } + public event Action HitObjectAdded; + public event Action HitObjectRemoved; private readonly Beatmap beatmap; From 7927b684d3c0ace143ae033f20b944c37c9f1f41 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:06:40 +0900 Subject: [PATCH 081/162] Hook up + use editor beatmap --- .../Editor/TestSceneHitObjectComposer.cs | 16 +--- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 63 ++++----------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 66 ++++++++++++---- .../Compose/Components/BlueprintContainer.cs | 76 +++++++++++++------ .../Screens/Edit/Compose/ComposeScreen.cs | 12 +-- 5 files changed, 118 insertions(+), 115 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index 7accbe2fa8..0ea73fb3de 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -16,15 +16,13 @@ using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; using osuTK; namespace osu.Game.Tests.Visual.Editor { [TestFixture] - [Cached(Type = typeof(IPlacementHandler))] - public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler + public class TestSceneHitObjectComposer : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -39,8 +37,6 @@ namespace osu.Game.Tests.Visual.Editor typeof(HitCirclePlacementBlueprint), }; - private HitObjectComposer composer; - [BackgroundDependencyLoader] private void load() { @@ -67,15 +63,7 @@ namespace osu.Game.Tests.Visual.Editor Dependencies.CacheAs(clock); Dependencies.CacheAs(clock); - Child = composer = new OsuHitObjectComposer(new OsuRuleset()); + Child = new OsuHitObjectComposer(new OsuRuleset()); } - - public void BeginPlacement(HitObject hitObject) - { - } - - public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); - - public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index e85ebb5f3a..c9d7b2cd81 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -5,10 +5,9 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { @@ -25,20 +24,6 @@ namespace osu.Game.Rulesets.Edit { RelativeSizeAxes = Axes.Both; } - - /// - /// Adds a to the and displays a visual representation of it. - /// - /// The to add. - /// The visual representation of . - internal abstract DrawableHitObject Add(HitObject hitObject); - - /// - /// Removes a from the and the display. - /// - /// The to remove. - /// The visual representation of the removed . - internal abstract DrawableHitObject Remove(HitObject hitObject); } public class DrawableEditRuleset : DrawableEditRuleset @@ -48,11 +33,11 @@ namespace osu.Game.Rulesets.Edit public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); - private Ruleset ruleset => drawableRuleset.Ruleset; - private Beatmap beatmap => drawableRuleset.Beatmap; - private readonly DrawableRuleset drawableRuleset; + [Resolved] + private EditorBeatmap beatmap { get; set; } + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -67,50 +52,28 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } - internal override DrawableHitObject Add(HitObject hitObject) + protected override void LoadComplete() { - var tObject = (TObject)hitObject; + base.LoadComplete(); - // Add to beatmap, preserving sorting order - var insertionIndex = beatmap.HitObjects.FindLastIndex(h => h.StartTime <= hitObject.StartTime); - beatmap.HitObjects.Insert(insertionIndex + 1, tObject); + beatmap.HitObjectAdded += addHitObject; + beatmap.HitObjectRemoved += removeHitObject; + } - // Process object - var processor = ruleset.CreateBeatmapProcessor(beatmap); - - processor?.PreProcess(); - tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - processor?.PostProcess(); - - // Add visual representation - var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); + private void addHitObject(HitObject hitObject) + { + var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); - - return drawableObject; } - internal override DrawableHitObject Remove(HitObject hitObject) + private void removeHitObject(HitObject hitObject) { - var tObject = (TObject)hitObject; - - // Remove from beatmap - beatmap.HitObjects.Remove(tObject); - - // Process the beatmap - var processor = ruleset.CreateBeatmapProcessor(beatmap); - - processor?.PreProcess(); - processor?.PostProcess(); - - // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - - return drawableObject; } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 6d98f45187..fe81f6747d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -20,6 +20,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; +using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit @@ -154,14 +155,6 @@ namespace osu.Game.Rulesets.Edit /// public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - /// - /// Adds a to the and visualises it. - /// - /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -178,9 +171,16 @@ namespace osu.Game.Rulesets.Edit public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); } - public abstract class HitObjectComposer : HitObjectComposer + [Cached(Type = typeof(IPlacementHandler))] + public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler where TObject : HitObject { + private Beatmap playableBeatmap; + + [Cached] + [Cached(typeof(IEditorBeatmap))] + private EditorBeatmap editorBeatmap; + protected HitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -189,19 +189,55 @@ namespace osu.Game.Rulesets.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var workingBeatmap = parent.Get>(); - var playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - var editorBeatmap = new EditorBeatmap(playableBeatmap); + playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(editorBeatmap); - dependencies.CacheAs(editorBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap.HitObjectAdded += addHitObject; + editorBeatmap.HitObjectRemoved += removeHitObject; - return dependencies; + return base.CreateChildDependencies(parent); + } + + private void addHitObject(HitObject hitObject) + { + // Process object + var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + processor?.PreProcess(); + hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); + processor?.PostProcess(); + } + + private void removeHitObject(HitObject hitObject) + { + var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + processor?.PreProcess(); + processor?.PostProcess(); } internal override DrawableEditRuleset CreateDrawableRuleset() => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); + + public void BeginPlacement(HitObject hitObject) + { + } + + public void EndPlacement(HitObject hitObject) => editorBeatmap.Add(hitObject); + + public void Delete(HitObject hitObject) => editorBeatmap.Remove(hitObject); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorBeatmap != null) + { + editorBeatmap.HitObjectAdded -= addHitObject; + editorBeatmap.HitObjectRemoved -= removeHitObject; + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a1e62cd38b..7d25fd5283 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Screens.Edit.Compose.Components @@ -29,6 +30,9 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private HitObjectComposer composer { get; set; } + [Resolved] + private IEditorBeatmap beatmap { get; set; } + public BlueprintContainer() { RelativeSizeAxes = Axes.Both; @@ -53,7 +57,15 @@ namespace osu.Game.Screens.Edit.Compose.Components }; foreach (var obj in composer.HitObjects) - AddBlueprintFor(obj); + addBlueprintFor(obj); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + beatmap.HitObjectAdded += addBlueprintFor; + beatmap.HitObjectRemoved += removeBlueprintFor; } private HitObjectCompositionTool currentTool; @@ -75,11 +87,32 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - /// - /// Adds a blueprint for a which adds movement support. - /// - /// The to create a blueprint for. - public void AddBlueprintFor(DrawableHitObject hitObject) + private void addBlueprintFor(HitObject hitObject) + { + var drawable = composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject); + if (drawable == null) + return; + + addBlueprintFor(drawable); + } + + private void removeBlueprintFor(HitObject hitObject) + { + var blueprint = selectionBlueprints.Single(m => m.HitObject.HitObject == hitObject); + if (blueprint == null) + return; + + blueprint.Deselect(); + + blueprint.Selected -= onBlueprintSelected; + blueprint.Deselected -= onBlueprintDeselected; + blueprint.SelectionRequested -= onSelectionRequested; + blueprint.DragRequested -= onDragRequested; + + selectionBlueprints.Remove(blueprint); + } + + private void addBlueprintFor(DrawableHitObject hitObject) { refreshTool(); @@ -95,25 +128,7 @@ namespace osu.Game.Screens.Edit.Compose.Components selectionBlueprints.Add(blueprint); } - /// - /// Removes a blueprint for a . - /// - /// The for which to remove the blueprint. - public void RemoveBlueprintFor(DrawableHitObject hitObject) - { - var blueprint = selectionBlueprints.Single(m => m.HitObject == hitObject); - if (blueprint == null) - return; - - blueprint.Deselect(); - - blueprint.Selected -= onBlueprintSelected; - blueprint.Deselected -= onBlueprintDeselected; - blueprint.SelectionRequested -= onSelectionRequested; - blueprint.DragRequested -= onDragRequested; - - selectionBlueprints.Remove(blueprint); - } + private void removeBlueprintFor(DrawableHitObject hitObject) => removeBlueprintFor(hitObject.HitObject); protected override bool OnClick(ClickEvent e) { @@ -183,6 +198,17 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.HitObjectAdded -= addBlueprintFor; + beatmap.HitObjectRemoved -= removeBlueprintFor; + } + } + private class SelectionBlueprintContainer : Container { protected override int Compare(Drawable x, Drawable y) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 5699ef0a84..ec4dda5c23 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -9,15 +9,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK.Graphics; namespace osu.Game.Screens.Edit.Compose { - [Cached(Type = typeof(IPlacementHandler))] - public class ComposeScreen : EditorScreen, IPlacementHandler + public class ComposeScreen : EditorScreen { private const float vertical_margins = 10; private const float horizontal_margins = 20; @@ -119,13 +117,5 @@ namespace osu.Game.Screens.Edit.Compose composerContainer.Child = composer; } - - public void BeginPlacement(HitObject hitObject) - { - } - - public void EndPlacement(HitObject hitObject) => composer.Add(hitObject); - - public void Delete(HitObject hitObject) => composer.Remove(hitObject); } } From 5db813b7a411b94d61676cb325e883f929861755 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:26:39 +0900 Subject: [PATCH 082/162] Add secondary interface for further abstraction --- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- osu.Game/Screens/Edit/IEditorBeatmap.cs | 27 ++++++++++++++++--- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index c9d7b2cd81..95a1492a45 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Edit private readonly DrawableRuleset drawableRuleset; [Resolved] - private EditorBeatmap beatmap { get; set; } + private IEditorBeatmap beatmap { get; set; } public DrawableEditRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fe81f6747d..fb7021bfa8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -176,9 +176,6 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { private Beatmap playableBeatmap; - - [Cached] - [Cached(typeof(IEditorBeatmap))] private EditorBeatmap editorBeatmap; protected HitObjectComposer(Ruleset ruleset) @@ -195,7 +192,11 @@ namespace osu.Game.Rulesets.Edit editorBeatmap.HitObjectAdded += addHitObject; editorBeatmap.HitObjectRemoved += removeHitObject; - return base.CreateChildDependencies(parent); + var dependencies = new DependencyContainer(parent); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs>(editorBeatmap); + + return base.CreateChildDependencies(dependencies); } private void addHitObject(HitObject hitObject) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index d9f17abfa5..2261f6f45c 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap, IEditorBeatmap + public class EditorBeatmap : IEditorBeatmap where T : HitObject { public event Action HitObjectAdded; diff --git a/osu.Game/Screens/Edit/IEditorBeatmap.cs b/osu.Game/Screens/Edit/IEditorBeatmap.cs index 602a096d65..2f250ba446 100644 --- a/osu.Game/Screens/Edit/IEditorBeatmap.cs +++ b/osu.Game/Screens/Edit/IEditorBeatmap.cs @@ -2,16 +2,35 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public interface IEditorBeatmap + /// + /// Interface for the contained by the see . + /// Children of may resolve the beatmap via or . + /// + public interface IEditorBeatmap : IBeatmap { + /// + /// Invoked when a is added to this . + /// event Action HitObjectAdded; - event Action HitObjectRemoved; - void Add(HitObject hitObject); - void Remove(HitObject hitObject); + /// + /// Invoked when a is removed from this . + /// + event Action HitObjectRemoved; + } + + /// + /// Interface for the contained by the see . + /// Children of may resolve the beatmap via or . + /// + public interface IEditorBeatmap : IEditorBeatmap, IBeatmap + where T : HitObject + { } } From dad0fa2dca16f672569e1a59bf4d16c54456346b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:31:40 +0900 Subject: [PATCH 083/162] Bind disposal --- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index 95a1492a45..a12e4ba3ab 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -75,5 +75,16 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.HitObjectAdded -= addHitObject; + beatmap.HitObjectRemoved -= removeHitObject; + } + } } } From b04a8ae8560e467ca8f3172e0261b9f3f4b05591 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 16:31:43 +0900 Subject: [PATCH 084/162] Add xmldocs --- osu.Game/Screens/Edit/EditorBeatmap.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 2261f6f45c..f0b6c62154 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -45,6 +45,10 @@ namespace osu.Game.Screens.Edit public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); + /// + /// Adds a to this . + /// + /// The to add. public void Add(T hitObject) { // Preserve existing sorting order in the beatmap @@ -54,14 +58,26 @@ namespace osu.Game.Screens.Edit HitObjectAdded?.Invoke(hitObject); } + /// + /// Removes a from this . + /// + /// The to add. public void Remove(T hitObject) { if (beatmap.HitObjects.Remove(hitObject)) HitObjectRemoved?.Invoke(hitObject); } + /// + /// Adds a to this . + /// + /// The to add. public void Add(HitObject hitObject) => Add((T)hitObject); + /// + /// Removes a from this . + /// + /// The to add. public void Remove(HitObject hitObject) => Remove((T)hitObject); } } From 40c1c6072ed587b0f9f3c8ec6d720117788fd62b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Aug 2019 16:38:39 +0900 Subject: [PATCH 085/162] Add "osu!classic" as a bundled skin choice --- osu.Game/Configuration/OsuConfigManager.cs | 2 +- osu.Game/OsuGame.cs | 21 +++++++++++++++++++- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Skinning/DefaultLegacySkin.cs | 23 ++++++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 3 +++ osu.Game/Skinning/SkinInfo.cs | 6 +++++- osu.Game/Skinning/SkinManager.cs | 18 +++++++++++++++-- 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Skinning/DefaultLegacySkin.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b13e115387..fb472f3f89 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -18,7 +18,7 @@ namespace osu.Game.Configuration { // UI/selection defaults Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); - Set(OsuSetting.Skin, 0, 0, int.MaxValue); + Set(OsuSetting.Skin, 0, -1, int.MaxValue); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 0e804ecbaf..8fa8ffaf9b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -182,7 +182,26 @@ namespace osu.Game // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID; - configSkin.ValueChanged += skinId => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == skinId.NewValue) ?? SkinInfo.Default; + configSkin.ValueChanged += skinId => + { + var skinInfo = SkinManager.Query(s => s.ID == skinId.NewValue); + + if (skinInfo == null) + { + switch (skinId.NewValue) + { + case -1: + skinInfo = DefaultLegacySkin.Info; + break; + + default: + skinInfo = SkinInfo.Default; + break; + } + } + + SkinManager.CurrentSkinInfo.Value = skinInfo; + }; configSkin.TriggerChange(); IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 076c9ada78..de8f316b06 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -158,7 +158,7 @@ namespace osu.Game runMigrations(); - dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); + dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); API = new APIAccess(LocalConfig); diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs new file mode 100644 index 0000000000..b35c9c7b97 --- /dev/null +++ b/osu.Game/Skinning/DefaultLegacySkin.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Audio; +using osu.Framework.IO.Stores; + +namespace osu.Game.Skinning +{ + public class DefaultLegacySkin : LegacySkin + { + public DefaultLegacySkin(IResourceStore storage, AudioManager audioManager) + : base(Info, storage, audioManager, string.Empty) + { + } + + public static SkinInfo Info { get; } = new SkinInfo + { + ID = -1, // this is temporary until database storage is decided upon. + Name = "osu!classic", + Creator = "team osu!" + }; + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 570ba1ced7..0cc5e9c9b6 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -248,6 +248,9 @@ namespace osu.Game.Skinning private string getPathForFile(string filename) { + if (source.Files == null) + return null; + bool hasExtension = filename.Contains('.'); var file = source.Files.Find(f => diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 187ea910a7..6b9627188e 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -26,7 +26,11 @@ namespace osu.Game.Skinning public string FullName => $"\"{Name}\" by {Creator}"; - public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + public static SkinInfo Default { get; } = new SkinInfo + { + Name = "osu!lazer", + Creator = "team osu!" + }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index e747a8b1ce..0e40eb5376 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -14,6 +14,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Audio; using osu.Game.Database; @@ -25,6 +26,8 @@ namespace osu.Game.Skinning { private readonly AudioManager audio; + private readonly IResourceStore legacyDefaultResources; + public readonly Bindable CurrentSkin = new Bindable(new DefaultSkin()); public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; @@ -34,10 +37,11 @@ namespace osu.Game.Skinning protected override string ImportFromStablePath => "Skins"; - public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio, IResourceStore legacyDefaultResources) : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) { this.audio = audio; + this.legacyDefaultResources = legacyDefaultResources; ItemRemoved += removedInfo => { @@ -56,6 +60,9 @@ namespace osu.Game.Skinning }; } + private Skin createIfNotExisting(SkinInfo skinInfo) => + GetSkin(Query(s => s.Name == skinInfo.Name) ?? Import(skinInfo).Result); + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk"; /// @@ -66,6 +73,7 @@ namespace osu.Game.Skinning { var userSkins = GetAllUserSkins(); userSkins.Insert(0, SkinInfo.Default); + userSkins.Insert(1, DefaultLegacySkin.Info); return userSkins; } @@ -91,7 +99,7 @@ namespace osu.Game.Skinning else { model.Name = model.Name.Replace(".osk", ""); - model.Creator = "Unknown"; + model.Creator = model.Creator ?? "Unknown"; } } @@ -102,9 +110,15 @@ namespace osu.Game.Skinning /// A instance correlating to the provided . public Skin GetSkin(SkinInfo skinInfo) { + if (skinInfo == null) + return null; + if (skinInfo == SkinInfo.Default) return new DefaultSkin(); + if (skinInfo == DefaultLegacySkin.Info) + return new DefaultLegacySkin(legacyDefaultResources, audio); + return new LegacySkin(skinInfo, Files.Store, audio); } From b40143cb73d8d5b8a0eaf8605b81b40ee30c05e7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 17:18:34 +0900 Subject: [PATCH 086/162] Remove unnecessary comment --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fb7021bfa8..d5a0e052a5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -201,7 +201,6 @@ namespace osu.Game.Rulesets.Edit private void addHitObject(HitObject hitObject) { - // Process object var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); processor?.PreProcess(); From 0fbdcabb6faa374e20fd43f1a762c839b73d4ced Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 17:21:52 +0900 Subject: [PATCH 087/162] Re-use a single beatmap processor --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d5a0e052a5..ed2ef5d9f8 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -177,6 +177,7 @@ namespace osu.Game.Rulesets.Edit { private Beatmap playableBeatmap; private EditorBeatmap editorBeatmap; + private IBeatmapProcessor beatmapProcessor; protected HitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -188,6 +189,8 @@ namespace osu.Game.Rulesets.Edit var workingBeatmap = parent.Get>(); playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + editorBeatmap = new EditorBeatmap(playableBeatmap); editorBeatmap.HitObjectAdded += addHitObject; editorBeatmap.HitObjectRemoved += removeHitObject; @@ -201,19 +204,15 @@ namespace osu.Game.Rulesets.Edit private void addHitObject(HitObject hitObject) { - var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - processor?.PreProcess(); + beatmapProcessor?.PreProcess(); hitObject.ApplyDefaults(playableBeatmap.ControlPointInfo, playableBeatmap.BeatmapInfo.BaseDifficulty); - processor?.PostProcess(); + beatmapProcessor?.PostProcess(); } private void removeHitObject(HitObject hitObject) { - var processor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - processor?.PreProcess(); - processor?.PostProcess(); + beatmapProcessor?.PreProcess(); + beatmapProcessor?.PostProcess(); } internal override DrawableEditRuleset CreateDrawableRuleset() From 59296d12f3af1e2d107c0b0c3206e47cd1feab08 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:02:50 +0900 Subject: [PATCH 088/162] Refactor HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 150 +++++++++----------- 1 file changed, 70 insertions(+), 80 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index ed2ef5d9f8..239ec572b2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -25,40 +25,40 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Edit { - public abstract class HitObjectComposer : CompositeDrawable + [Cached(Type = typeof(IPlacementHandler))] + public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler + where TObject : HitObject { - public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; + protected IRulesetConfigManager Config { get; private set; } + protected DrawableEditRuleset DrawableRuleset { get; private set; } protected readonly Ruleset Ruleset; - protected readonly IBindable Beatmap = new Bindable(); - - protected IRulesetConfigManager Config { get; private set; } - - private readonly List layerContainers = new List(); - - protected DrawableEditRuleset DrawableRuleset { get; private set; } + private IBindable workingBeatmap; + private Beatmap playableBeatmap; + private EditorBeatmap editorBeatmap; + private IBeatmapProcessor beatmapProcessor; private BlueprintContainer blueprintContainer; + private readonly List layerContainers = new List(); private InputManager inputManager; - internal HitObjectComposer(Ruleset ruleset) + protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IFrameBasedClock framedClock) + private void load(IFrameBasedClock framedClock) { - Beatmap.BindTo(beatmap); - try { - DrawableRuleset = CreateDrawableRuleset(); - DrawableRuleset.Clock = framedClock; + DrawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + { + Clock = framedClock + }; } catch (Exception e) { @@ -120,6 +120,26 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items[0].Select(); } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + workingBeatmap = parent.Get>().GetBoundCopy(); + playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + + beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); + + editorBeatmap = new EditorBeatmap(playableBeatmap); + editorBeatmap.HitObjectAdded += addHitObject; + editorBeatmap.HitObjectRemoved += removeHitObject; + + var dependencies = new DependencyContainer(parent); + dependencies.CacheAs(editorBeatmap); + dependencies.CacheAs>(editorBeatmap); + + Config = dependencies.Get().GetConfigFor(Ruleset); + + return base.CreateChildDependencies(dependencies); + } + protected override void LoadComplete() { base.LoadComplete(); @@ -127,16 +147,6 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(this); - Config = dependencies.Get().GetConfigFor(Ruleset); - - return dependencies; - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -150,58 +160,6 @@ namespace osu.Game.Rulesets.Edit }); } - /// - /// Whether the user's cursor is currently in an area of the that is valid for placement. - /// - public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - internal abstract DrawableEditRuleset CreateDrawableRuleset(); - - protected abstract IReadOnlyList CompositionTools { get; } - - /// - /// Creates a for a specific . - /// - /// The to create the overlay for. - public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; - - /// - /// Creates a which outlines s and handles movement of selections. - /// - public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); - } - - [Cached(Type = typeof(IPlacementHandler))] - public abstract class HitObjectComposer : HitObjectComposer, IPlacementHandler - where TObject : HitObject - { - private Beatmap playableBeatmap; - private EditorBeatmap editorBeatmap; - private IBeatmapProcessor beatmapProcessor; - - protected HitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var workingBeatmap = parent.Get>(); - playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); - - beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); - - editorBeatmap = new EditorBeatmap(playableBeatmap); - editorBeatmap.HitObjectAdded += addHitObject; - editorBeatmap.HitObjectRemoved += removeHitObject; - - var dependencies = new DependencyContainer(parent); - dependencies.CacheAs(editorBeatmap); - dependencies.CacheAs>(editorBeatmap); - - return base.CreateChildDependencies(dependencies); - } - private void addHitObject(HitObject hitObject) { beatmapProcessor?.PreProcess(); @@ -215,8 +173,10 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - internal override DrawableEditRuleset CreateDrawableRuleset() - => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); + public override IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + protected abstract IReadOnlyList CompositionTools { get; } protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); @@ -239,4 +199,34 @@ namespace osu.Game.Rulesets.Edit } } } + + [Cached(typeof(HitObjectComposer))] + public abstract class HitObjectComposer : CompositeDrawable + { + internal HitObjectComposer() + { + RelativeSizeAxes = Axes.Both; + } + + /// + /// All the s. + /// + public abstract IEnumerable HitObjects { get; } + + /// + /// Whether the user's cursor is currently in an area of the that is valid for placement. + /// + public abstract bool CursorInPlacementArea { get; } + + /// + /// Creates a for a specific . + /// + /// The to create the overlay for. + public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; + + /// + /// Creates a which outlines s and handles movement of selections. + /// + public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); + } } From 87e28ab1f97869de9fef85deac8ec2be1072f0e0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:12:29 +0900 Subject: [PATCH 089/162] Remove non-generic DrawableEditRuleset --- .../Edit/ManiaHitObjectComposer.cs | 12 ++++----- osu.Game/Rulesets/Edit/DrawableEditRuleset.cs | 25 +++++-------------- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 ++++++++-------- 3 files changed, 23 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 2729621ab3..3a28149946 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Cached(Type = typeof(IManiaHitObjectComposer))] public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { - protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } + private DrawableManiaEditRuleset drawableRuleset; public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) @@ -33,23 +33,23 @@ namespace osu.Game.Rulesets.Mania.Edit /// /// The screen-space position. /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); + public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition); private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; + public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { - DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); + drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(DrawableRuleset.ScrollingInfo); + dependencies.CacheAs(drawableRuleset.ScrollingInfo); - return DrawableRuleset; + return drawableRuleset; } protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index a12e4ba3ab..a9f0dd4197 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -11,27 +11,10 @@ using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { - public abstract class DrawableEditRuleset : CompositeDrawable - { - /// - /// The contained by this . - /// - public abstract Playfield Playfield { get; } - - public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); - - internal DrawableEditRuleset() - { - RelativeSizeAxes = Axes.Both; - } - } - - public class DrawableEditRuleset : DrawableEditRuleset + public class DrawableEditRuleset : CompositeDrawable where TObject : HitObject { - public override Playfield Playfield => drawableRuleset.Playfield; - - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + public Playfield Playfield => drawableRuleset.Playfield; private readonly DrawableRuleset drawableRuleset; @@ -42,6 +25,8 @@ namespace osu.Game.Rulesets.Edit { this.drawableRuleset = drawableRuleset; + RelativeSizeAxes = Axes.Both; + InternalChild = drawableRuleset; } @@ -76,6 +61,8 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.PostProcess(); } + public PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 239ec572b2..35c5a63ef1 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Edit where TObject : HitObject { protected IRulesetConfigManager Config { get; private set; } - protected DrawableEditRuleset DrawableRuleset { get; private set; } protected readonly Ruleset Ruleset; @@ -39,6 +38,7 @@ namespace osu.Game.Rulesets.Edit private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; + private DrawableEditRuleset drawableRuleset; private BlueprintContainer blueprintContainer; private readonly List layerContainers = new List(); @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - DrawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) { Clock = framedClock }; @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerBelowRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerAboveRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - DrawableRuleset, + drawableRuleset, layerAboveRuleset } } @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = DrawableRuleset.Playfield.Anchor; - l.Origin = DrawableRuleset.Playfield.Origin; - l.Position = DrawableRuleset.Playfield.Position; - l.Size = DrawableRuleset.Playfield.Size; + l.Anchor = drawableRuleset.Playfield.Anchor; + l.Origin = drawableRuleset.Playfield.Origin; + l.Position = drawableRuleset.Playfield.Position; + l.Size = drawableRuleset.Playfield.Size; }); } @@ -173,8 +173,8 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - public override IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; - public override bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public override IEnumerable HitObjects => drawableRuleset.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => drawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); protected abstract IReadOnlyList CompositionTools { get; } From 714ee312da0338bb768ac543ad5ffb4cf43328f0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 18:20:43 +0900 Subject: [PATCH 090/162] Rename DrawableEditRuleset -> DrawableEditRulesetWrapper --- ...leset.cs => DrawableEditRulesetWrapper.cs} | 7 ++++-- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 +++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) rename osu.Game/Rulesets/Edit/{DrawableEditRuleset.cs => DrawableEditRulesetWrapper.cs} (85%) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs similarity index 85% rename from osu.Game/Rulesets/Edit/DrawableEditRuleset.cs rename to osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index a9f0dd4197..af565f8896 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -11,7 +11,10 @@ using osu.Game.Screens.Edit; namespace osu.Game.Rulesets.Edit { - public class DrawableEditRuleset : CompositeDrawable + /// + /// A wrapper for a . Handles adding visual representations of s to the underlying . + /// + internal class DrawableEditRulesetWrapper : CompositeDrawable where TObject : HitObject { public Playfield Playfield => drawableRuleset.Playfield; @@ -21,7 +24,7 @@ namespace osu.Game.Rulesets.Edit [Resolved] private IEditorBeatmap beatmap { get; set; } - public DrawableEditRuleset(DrawableRuleset drawableRuleset) + public DrawableEditRulesetWrapper(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 35c5a63ef1..d7ee63d4a4 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; - private DrawableEditRuleset drawableRuleset; + private DrawableEditRulesetWrapper drawableRulesetWrapper; private BlueprintContainer blueprintContainer; private readonly List layerContainers = new List(); @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - drawableRuleset = new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) { Clock = framedClock }; @@ -66,10 +66,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = drawableRuleset.CreatePlayfieldAdjustmentContainer(); + var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - drawableRuleset, + drawableRulesetWrapper, layerAboveRuleset } } @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = drawableRuleset.Playfield.Anchor; - l.Origin = drawableRuleset.Playfield.Origin; - l.Position = drawableRuleset.Playfield.Position; - l.Size = drawableRuleset.Playfield.Size; + l.Anchor = drawableRulesetWrapper.Playfield.Anchor; + l.Origin = drawableRulesetWrapper.Playfield.Origin; + l.Position = drawableRulesetWrapper.Playfield.Position; + l.Size = drawableRulesetWrapper.Playfield.Size; }); } @@ -173,8 +173,8 @@ namespace osu.Game.Rulesets.Edit beatmapProcessor?.PostProcess(); } - public override IEnumerable HitObjects => drawableRuleset.Playfield.AllHitObjects; - public override bool CursorInPlacementArea => drawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); protected abstract IReadOnlyList CompositionTools { get; } From d3030831793dc68f7d8e04fce2d05209e6fab796 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 12:29:31 +0300 Subject: [PATCH 091/162] Update to match api --- .../Online/TestSceneBeatmapSetOverlay.cs | 4 +- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 51 +++---------------- .../API/Requests/Responses/APIBeatmapSet.cs | 4 +- osu.Game/Overlays/BeatmapSet/Info.cs | 4 +- 4 files changed, 12 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 2d918442eb..ee9e088dcc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -176,8 +176,8 @@ namespace osu.Game.Tests.Visual.Online HasVideo = true, HasStoryboard = true, Covers = new BeatmapSetOnlineCovers(), - Language = BeatmapSetOnlineLanguage.English, - Genre = BeatmapSetOnlineGenre.Rock, + Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" }, + Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" }, }, Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, Beatmaps = new List diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index decf2d10db..500e42096c 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -88,55 +88,16 @@ namespace osu.Game.Beatmaps public BeatmapSetOnlineLanguage Language { get; set; } } - public enum BeatmapSetOnlineGenre + public class BeatmapSetOnlineGenre { - [Description("Any")] - Any = 0, - - [Description("Unspecified")] - Unspecified = 1, - - [Description("Video Game")] - VideoGame = 2, - - [Description("Anime")] - Anime = 3, - - [Description("Rock")] - Rock = 4, - - [Description("Pop")] - Pop = 5, - - [Description("Other")] - Other = 6, - - [Description("Novelty")] - Novelty = 7, - - // genre_id 8 doesn't exist - - [Description("Hip-Hop")] - HipHop = 9, - - [Description("Electronic")] - Electronic = 10 + public int Id { get; set; } + public string Name { get; set; } } - public enum BeatmapSetOnlineLanguage + public class BeatmapSetOnlineLanguage { - Any, - Other, - English, - Japanese, - Chinese, - Instrumental, - Korean, - French, - German, - Swedish, - Spanish, - Italian + public int Id { get; set; } + public string Name { get; set; } } public class BeatmapSetOnlineCovers diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 1526cccf31..1ca14256e5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -69,10 +69,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"availability")] private BeatmapSetOnlineAvailability availability { get; set; } - [JsonProperty(@"genre_id")] + [JsonProperty(@"genre")] private BeatmapSetOnlineGenre genre { get; set; } - [JsonProperty(@"language_id")] + [JsonProperty(@"language")] private BeatmapSetOnlineLanguage language { get; set; } [JsonProperty(@"beatmaps")] diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 0e4e9db948..9c5cce89f9 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -133,8 +133,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = (b.NewValue?.OnlineInfo.Genre ?? BeatmapSetOnlineGenre.Unspecified).GetDescription(); - language.Text = (b.NewValue?.OnlineInfo.Language ?? BeatmapSetOnlineLanguage.Other).ToString(); + genre.Text = b.NewValue?.OnlineInfo.Genre.Name ?? "Unspecified"; + language.Text = b.NewValue?.OnlineInfo.Language.Name ?? "Other"; }; } From 68ee7346b213b099e6eceed2cd8478181e15b484 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 12:49:44 +0300 Subject: [PATCH 092/162] Remove usings --- osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs | 1 - osu.Game/Overlays/BeatmapSet/Info.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 500e42096c..06dee4d3f5 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel; using Newtonsoft.Json; namespace osu.Game.Beatmaps diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 9c5cce89f9..f17b44c8f7 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,7 +3,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; From c1c1c7874bab0786ffeaa6f3a99e63c9b2317853 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 29 Aug 2019 13:25:05 +0300 Subject: [PATCH 093/162] Nullcheck --- osu.Game/Overlays/BeatmapSet/Info.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index f17b44c8f7..72db03a5a6 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -132,8 +132,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo.Genre.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo.Language.Name ?? "Other"; + genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Other"; }; } From 6ab2b20c70dcea4dd98ec7c7ebc9f477170ea95d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:38:44 +0900 Subject: [PATCH 094/162] Add an interface for working beatmaps --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 +- .../UI/DrawableCatchRuleset.cs | 4 +- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../UI/DrawableOsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../UI/DrawableTaikoRuleset.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 +- osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 2 +- osu.Game/Beatmaps/IWorkingBeatmap.cs | 61 +++++++++++++++++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 16 +---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 12 ++-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 16 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 osu.Game/Beatmaps/IWorkingBeatmap.cs diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 6f1a7873ec..71e05083be 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index f48b84e344..6b7f00c5d0 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 8966b5058f..0de86c2149 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 0718de2c7d..f26526fe70 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI private readonly Bindable configDirection = new Bindable(); - public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { // Generate the bar lines diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..3bbfc25d22 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index d185d7d4c9..aa61fb6922 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.UI { protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; - public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 83356b77c2..6d0a5eb1e1 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index ec3a56e9c7..b03bea578e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool UserScrollSpeedAdjustment => false; - public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index ee11fc0d06..60ace8ea69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; - public TestDrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { TimeRange.Value = time_range; diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 3a4c677bd1..29ade24328 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) + public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs new file mode 100644 index 0000000000..aea3751bb5 --- /dev/null +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; +using osu.Game.Storyboards; + +namespace osu.Game.Beatmaps +{ + public interface IWorkingBeatmap + { + /// + /// Retrieves the which this represents. + /// + IBeatmap Beatmap { get; } + + /// + /// Retrieves the background for this . + /// + Texture Background { get; } + + /// + /// Retrieves the audio track for this . + /// + Track Track { get; } + + /// + /// Retrieves the for the of this . + /// + Waveform Waveform { get; } + + /// + /// Retrieves the which this provides. + /// + Storyboard Storyboard { get; } + + /// + /// Retrieves the which this provides. + /// + Skin Skin { get; } + + /// + /// Constructs a playable from using the applicable converters for a specific . + /// + /// The returned is in a playable state - all and s + /// have been applied, and s have been fully constructed. + /// + /// + /// The to create a playable for. + /// The s to apply to the . + /// The converted . + /// If could not be converted to . + IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods); + } +} diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8605caa5fe..1cce3dc5fe 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -16,14 +16,13 @@ using osu.Framework.Audio; using osu.Framework.Statistics; using osu.Game.IO.Serialization; using osu.Game.Rulesets; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; namespace osu.Game.Beatmaps { - public abstract class WorkingBeatmap : IDisposable + public abstract class WorkingBeatmap : IWorkingBeatmap, IDisposable { public readonly BeatmapInfo BeatmapInfo; @@ -97,18 +96,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - /// - /// Constructs a playable from using the applicable converters for a specific . - /// - /// The returned is in a playable state - all and s - /// have been applied, and s have been fully constructed. - /// - /// - /// The to create a playable for. - /// The s to apply to the . - /// The converted . - /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d7ee63d4a4..fc324d7021 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; - private IBindable workingBeatmap; + private IWorkingBeatmap workingBeatmap; private Beatmap playableBeatmap; private EditorBeatmap editorBeatmap; private IBeatmapProcessor beatmapProcessor; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Edit { try { - drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap.Value, Array.Empty())) + drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, workingBeatmap, Array.Empty())) { Clock = framedClock }; @@ -122,8 +122,10 @@ namespace osu.Game.Rulesets.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - workingBeatmap = parent.Get>().GetBoundCopy(); - playableBeatmap = (Beatmap)workingBeatmap.Value.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + var parentWorkingBeatmap = parent.Get>().Value; + + playableBeatmap = (Beatmap)parentWorkingBeatmap.GetPlayableBeatmap(Ruleset.RulesetInfo, Array.Empty()); + workingBeatmap = new EditorWorkingBeatmap(playableBeatmap, parentWorkingBeatmap); beatmapProcessor = Ruleset.CreateBeatmapProcessor(playableBeatmap); @@ -178,7 +180,7 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods); public void BeginPlacement(HitObject hitObject) { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 42b1322cae..b089840f1e 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); + public abstract DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index ccfd89adca..021bd515b5 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.UI /// The ruleset being represented. /// The beatmap to create the hit renderer for. /// The s to apply. - protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) + protected DrawableRuleset(Ruleset ruleset, IWorkingBeatmap workingBeatmap, IReadOnlyList mods) : base(ruleset) { if (workingBeatmap == null) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index c1a4c9520e..64e491858b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected DrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); From 6641811125b938aa1511325938504f4be4e876a0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:40:56 +0900 Subject: [PATCH 095/162] Add EditorWorkingBeatmap for reuse of the playable beatmap --- .../Edit/DrawableManiaEditRuleset.cs | 2 +- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Edit/DrawableOsuEditRuleset.cs | 2 +- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 42 +++++++++++++++++++ 5 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 osu.Game/Screens/Edit/EditorWorkingBeatmap.cs diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index e5f379f608..97d8aaa052 100644 --- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3a28149946..0bfe6f9517 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns; - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) { drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs index bcb6099cfb..cc08d356f9 100644 --- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public class DrawableOsuEditRuleset : DrawableOsuRuleset { - public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index c5452ae0aa..1c040e9dee 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs new file mode 100644 index 0000000000..8bec68596c --- /dev/null +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Skinning; +using osu.Game.Storyboards; + +namespace osu.Game.Screens.Edit +{ + public class EditorWorkingBeatmap : IWorkingBeatmap + where TObject : HitObject + { + private readonly Beatmap playableBeatmap; + private readonly WorkingBeatmap workingBeatmap; + + public EditorWorkingBeatmap(Beatmap playableBeatmap, WorkingBeatmap workingBeatmap) + { + this.playableBeatmap = playableBeatmap; + this.workingBeatmap = workingBeatmap; + } + + public IBeatmap Beatmap => workingBeatmap.Beatmap; + + public Texture Background => workingBeatmap.Background; + + public Track Track => workingBeatmap.Track; + + public Waveform Waveform => workingBeatmap.Waveform; + + public Storyboard Storyboard => workingBeatmap.Storyboard; + + public Skin Skin => workingBeatmap.Skin; + + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap; + } +} From ae0a5504d705fb4ed1dd95751e71d581f40cf636 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 29 Aug 2019 19:43:33 +0900 Subject: [PATCH 096/162] Revert unnecessary change --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 1cce3dc5fe..90dde4239c 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -96,7 +96,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); From 3f500131d483b5ebbdc60fb4c905de2e754965d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 11:33:50 +0900 Subject: [PATCH 097/162] Add basic xmldoc --- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs index 8bec68596c..45fca493a2 100644 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -13,6 +13,10 @@ using osu.Game.Storyboards; namespace osu.Game.Screens.Edit { + /// + /// Encapsulates a while providing an overridden . + /// + /// public class EditorWorkingBeatmap : IWorkingBeatmap where TObject : HitObject { From bfbec067b13ba7546b1ece8f460b4c433200564c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 11:54:36 +0900 Subject: [PATCH 098/162] Remove remnants of user skin PR --- osu.Game/Skinning/SkinManager.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 0e40eb5376..a713933c6e 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -60,9 +60,6 @@ namespace osu.Game.Skinning }; } - private Skin createIfNotExisting(SkinInfo skinInfo) => - GetSkin(Query(s => s.Name == skinInfo.Name) ?? Import(skinInfo).Result); - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osk"; /// @@ -110,9 +107,6 @@ namespace osu.Game.Skinning /// A instance correlating to the provided . public Skin GetSkin(SkinInfo skinInfo) { - if (skinInfo == null) - return null; - if (skinInfo == SkinInfo.Default) return new DefaultSkin(); From 8fe37d0c43aec1182f9b437ba16cce2103a6a526 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:35:44 +0900 Subject: [PATCH 099/162] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 0f6e32d664..3854cab34f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -60,7 +60,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d791909372..5563f9efeb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -14,7 +14,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9fc472bf40..a430ff59ab 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -117,7 +117,7 @@ - + From 9c622680e32a5f21777ffb4aee13acbee1a7fe86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:35:53 +0900 Subject: [PATCH 100/162] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3854cab34f..2c3c8bcaad 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5563f9efeb..8e6ce2d1ba 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index a430ff59ab..47cc6ec97a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 84e474826817f394594a0973016492d3bda740d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 12:59:58 +0900 Subject: [PATCH 101/162] Remove duplicate getAnimation function and improve namespacing --- osu.Game.Rulesets.Osu/OsuLegacySkin.cs | 236 ------------------ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + .../Skinning/LegacyMainCirclePiece.cs | 81 ++++++ .../Skinning/LegacySliderBall.cs | 44 ++++ .../Skinning/OsuLegacySkin.cs | 97 +++++++ osu.Game/Beatmaps/IWorkingBeatmap.cs | 2 +- osu.Game/Screens/Edit/EditorWorkingBeatmap.cs | 2 +- osu.Game/Skinning/LegacySkin.cs | 40 +-- osu.Game/Skinning/LegacySkinExtensions.cs | 53 ++++ 9 files changed, 279 insertions(+), 277 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/OsuLegacySkin.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs create mode 100644 osu.Game/Skinning/LegacySkinExtensions.cs diff --git a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/OsuLegacySkin.cs deleted file mode 100644 index d4b00ab911..0000000000 --- a/osu.Game.Rulesets.Osu/OsuLegacySkin.cs +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu -{ - public class OsuLegacySkin : ISkin - { - private readonly ISkin source; - - private Lazy configuration; - - private Lazy hasHitCircle; - - /// - /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. - /// Their hittable area is 128px, but the actual circle portion is 118px. - /// We must account for some gameplay elements such as slider bodies, where this padding is not present. - /// - private const float legacy_circle_radius = 64 - 5; - - public OsuLegacySkin(ISkinSource source) - { - this.source = source; - - source.SourceChanged += sourceChanged; - sourceChanged(); - } - - private void sourceChanged() - { - // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. - configuration = new Lazy(() => - { - var config = new SkinConfiguration(); - if (hasHitCircle.Value) - config.SliderPathRadius = legacy_circle_radius; - - // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). - config.CustomColours["SliderBall"] = - source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) - ?? new Color4(2, 170, 255, 255); - - return config; - }); - - hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); - } - - private const double default_frame_time = 1000 / 60d; - - public Drawable GetDrawableComponent(string componentName) - { - switch (componentName) - { - case "Play/osu/sliderball": - var sliderBallContent = getAnimation("sliderb", true, true, ""); - - if (sliderBallContent != null) - { - var size = sliderBallContent.Size; - - sliderBallContent.RelativeSizeAxes = Axes.Both; - sliderBallContent.Size = Vector2.One; - - return new LegacySliderBall(sliderBallContent) - { - Size = size - }; - } - - return null; - - case "Play/osu/hitcircle": - if (hasHitCircle.Value) - return new LegacyMainCirclePiece(); - - return null; - } - - return null; - } - - private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") - { - Texture texture; - - Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); - - TextureAnimation animation = null; - - if (animatable) - { - for (int i = 0;; i++) - { - if ((texture = getFrameTexture(i)) == null) - break; - - if (animation == null) - animation = new TextureAnimation - { - DefaultFrameLength = default_frame_time, - Repeat = looping - }; - - animation.AddFrame(texture); - } - } - - if (animation != null) - return animation; - - if ((texture = source.GetTexture(componentName)) != null) - return new Sprite { Texture = texture }; - - return null; - } - - public Texture GetTexture(string componentName) => null; - - public SampleChannel GetSample(ISampleInfo sample) => null; - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; - - public class LegacySliderBall : CompositeDrawable - { - private readonly Drawable animationContent; - - public LegacySliderBall(Drawable animationContent) - { - this.animationContent = animationContent; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin, DrawableHitObject drawableObject) - { - animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; - - InternalChildren = new[] - { - new Sprite - { - Texture = skin.GetTexture("sliderb-nd"), - Colour = new Color4(5, 5, 5, 255), - }, - animationContent, - new Sprite - { - Texture = skin.GetTexture("sliderb-spec"), - Blending = BlendingParameters.Additive, - }, - }; - } - } - - public class LegacyMainCirclePiece : CompositeDrawable - { - public LegacyMainCirclePiece() - { - Size = new Vector2(128); - } - - private readonly IBindable state = new Bindable(); - - private readonly Bindable accentColour = new Bindable(); - - [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) - { - Sprite hitCircleSprite; - - InternalChildren = new Drawable[] - { - hitCircleSprite = new Sprite - { - Texture = skin.GetTexture("hitcircle"), - Colour = drawableObject.AccentColour.Value, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText - { - Font = OsuFont.Numeric.With(size: 40), - UseFullGlyphHeight = false, - }, confineMode: ConfineMode.NoScaling) - { - Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() - }, - new Sprite - { - Texture = skin.GetTexture("hitcircleoverlay"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - - state.BindTo(drawableObject.State); - state.BindValueChanged(updateState, true); - - accentColour.BindTo(drawableObject.AccentColour); - accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); - } - - private void updateState(ValueChangedEvent state) - { - const double legacy_fade_duration = 240; - - switch (state.NewValue) - { - case ArmedState.Hit: - this.FadeOut(legacy_fade_duration, Easing.Out); - this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - break; - } - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index e2c64bbedf..49676933e1 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs new file mode 100644 index 0000000000..a7906ddd24 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyMainCirclePiece : CompositeDrawable + { + public LegacyMainCirclePiece() + { + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); + } + + private readonly IBindable state = new Bindable(); + + private readonly Bindable accentColour = new Bindable(); + + [BackgroundDependencyLoader] + private void load(DrawableHitObject drawableObject, ISkinSource skin) + { + Sprite hitCircleSprite; + + InternalChildren = new Drawable[] + { + hitCircleSprite = new Sprite + { + Texture = skin.GetTexture("hitcircle"), + Colour = drawableObject.AccentColour.Value, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText + { + Font = OsuFont.Numeric.With(size: 40), + UseFullGlyphHeight = false, + }, confineMode: ConfineMode.NoScaling) + { + Text = (((IHasComboInformation)drawableObject.HitObject).IndexInCurrentCombo + 1).ToString() + }, + new Sprite + { + Texture = skin.GetTexture("hitcircleoverlay"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + + state.BindTo(drawableObject.State); + state.BindValueChanged(updateState, true); + + accentColour.BindTo(drawableObject.AccentColour); + accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true); + } + + private void updateState(ValueChangedEvent state) + { + const double legacy_fade_duration = 240; + + switch (state.NewValue) + { + case ArmedState.Hit: + this.FadeOut(legacy_fade_duration, Easing.Out); + this.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs new file mode 100644 index 0000000000..ec838c596d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacySliderBall : CompositeDrawable + { + private readonly Drawable animationContent; + + public LegacySliderBall(Drawable animationContent) + { + this.animationContent = animationContent; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin, DrawableHitObject drawableObject) + { + animationContent.Colour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? Color4.White; + + InternalChildren = new[] + { + new Sprite + { + Texture = skin.GetTexture("sliderb-nd"), + Colour = new Color4(5, 5, 5, 255), + }, + animationContent, + new Sprite + { + Texture = skin.GetTexture("sliderb-spec"), + Blending = BlendingParameters.Additive, + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs new file mode 100644 index 0000000000..927cbc5d2f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class OsuLegacySkin : ISkin + { + private readonly ISkin source; + + private Lazy configuration; + + private Lazy hasHitCircle; + + /// + /// On osu-stable, hitcircles have 5 pixels of transparent padding on each side to allow for shadows etc. + /// Their hittable area is 128px, but the actual circle portion is 118px. + /// We must account for some gameplay elements such as slider bodies, where this padding is not present. + /// + private const float legacy_circle_radius = 64 - 5; + + public OsuLegacySkin(ISkinSource source) + { + this.source = source; + + source.SourceChanged += sourceChanged; + sourceChanged(); + } + + private void sourceChanged() + { + // these need to be lazy in order to ensure they aren't called before the dependencies have been loaded into our source. + configuration = new Lazy(() => + { + var config = new SkinConfiguration(); + if (hasHitCircle.Value) + config.SliderPathRadius = legacy_circle_radius; + + // defaults should only be applied for non-beatmap skins (which are parsed via this constructor). + config.CustomColours["SliderBall"] = + source.GetValue(s => s.CustomColours.TryGetValue("SliderBall", out var val) ? val : (Color4?)null) + ?? new Color4(2, 170, 255, 255); + + return config; + }); + + hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); + } + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/osu/sliderball": + var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); + + if (sliderBallContent != null) + { + var size = sliderBallContent.Size; + + sliderBallContent.RelativeSizeAxes = Axes.Both; + sliderBallContent.Size = Vector2.One; + + return new LegacySliderBall(sliderBallContent) + { + Size = size + }; + } + + return null; + + case "Play/osu/hitcircle": + if (hasHitCircle.Value) + return new LegacyMainCirclePiece(); + + return null; + } + + return null; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sample) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + } +} diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index aea3751bb5..44071d9cc1 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps /// /// Retrieves the which this provides. /// - Skin Skin { get; } + ISkin Skin { get; } /// /// Constructs a playable from using the applicable converters for a specific . diff --git a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs index 45fca493a2..299059407c 100644 --- a/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorWorkingBeatmap.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Edit public Storyboard Storyboard => workingBeatmap.Storyboard; - public Skin Skin => workingBeatmap.Skin; + public ISkin Skin => workingBeatmap.Skin; public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) => playableBeatmap; } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 8dfefcfa1b..d567e48d9a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -60,8 +59,6 @@ namespace osu.Game.Skinning Samples?.Dispose(); } - private const double default_frame_time = 1000 / 60d; - public override Drawable GetDrawableComponent(string componentName) { bool animatable = false; @@ -114,7 +111,7 @@ namespace osu.Game.Skinning }; } - return getAnimation(componentName, animatable, looping); + return this.GetAnimation(componentName, animatable, looping); } public override Texture GetTexture(string componentName) @@ -161,41 +158,6 @@ namespace osu.Game.Skinning return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - private Drawable getAnimation(string componentName, bool animatable, bool looping, string animationSeparator = "-") - { - Texture texture; - - Texture getFrameTexture(int frame) => GetTexture($"{componentName}{animationSeparator}{frame}"); - - TextureAnimation animation = null; - - if (animatable) - { - for (int i = 0;; i++) - { - if ((texture = getFrameTexture(i)) == null) - break; - - if (animation == null) - animation = new TextureAnimation - { - DefaultFrameLength = default_frame_time, - Repeat = looping - }; - - animation.AddFrame(texture); - } - } - - if (animation != null) - return animation; - - if ((texture = GetTexture(componentName)) != null) - return new Sprite { Texture = texture }; - - return null; - } - protected class LegacySkinResourceStore : IResourceStore where T : INamedFileInfo { diff --git a/osu.Game/Skinning/LegacySkinExtensions.cs b/osu.Game/Skinning/LegacySkinExtensions.cs new file mode 100644 index 0000000000..c5582af836 --- /dev/null +++ b/osu.Game/Skinning/LegacySkinExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + public static class LegacySkinExtensions + { + public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, string animationSeparator = "-") + { + const double default_frame_time = 1000 / 60d; + + Texture texture; + + Texture getFrameTexture(int frame) => source.GetTexture($"{componentName}{animationSeparator}{frame}"); + + TextureAnimation animation = null; + + if (animatable) + { + for (int i = 0;; i++) + { + if ((texture = getFrameTexture(i)) == null) + break; + + if (animation == null) + animation = new TextureAnimation + { + DefaultFrameLength = default_frame_time, + Repeat = looping + }; + + animation.AddFrame(texture); + } + } + + if (animation != null) + return animation; + + if ((texture = source.GetTexture(componentName)) != null) + return new Sprite + { + Texture = texture + }; + + return null; + } + } +} From 7bba8ca14bb92bea3f1d6a707ba1c85cc8f8f9a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:04:11 +0900 Subject: [PATCH 102/162] Split out nested classes --- osu.Game/Skinning/LegacySkin.cs | 112 ------------------- osu.Game/Skinning/LegacySkinResourceStore.cs | 76 +++++++++++++ osu.Game/Skinning/LegacySpriteText.cs | 53 +++++++++ 3 files changed, 129 insertions(+), 112 deletions(-) create mode 100644 osu.Game/Skinning/LegacySkinResourceStore.cs create mode 100644 osu.Game/Skinning/LegacySpriteText.cs diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index d567e48d9a..0151a211d3 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -14,11 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; -using osu.Framework.Text; using osu.Game.Audio; -using osu.Game.Database; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.UI; using osuTK; using osuTK.Graphics; @@ -158,111 +151,6 @@ namespace osu.Game.Skinning return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - protected class LegacySkinResourceStore : IResourceStore - where T : INamedFileInfo - { - private readonly IHasFiles source; - private readonly IResourceStore underlyingStore; - - private string getPathForFile(string filename) - { - bool hasExtension = filename.Contains('.'); - - var file = source.Files.Find(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); - return file?.FileInfo.StoragePath; - } - - public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) - { - this.source = source; - this.underlyingStore = underlyingStore; - } - - public Stream GetStream(string name) - { - string path = getPathForFile(name); - return path == null ? null : underlyingStore.GetStream(path); - } - - public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); - - byte[] IResourceStore.Get(string name) => GetAsync(name).Result; - - public Task GetAsync(string name) - { - string path = getPathForFile(name); - return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); - } - - #region IDisposable Support - - private bool isDisposed; - - protected virtual void Dispose(bool disposing) - { - if (!isDisposed) - { - isDisposed = true; - } - } - - ~LegacySkinResourceStore() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - } - - private class LegacySpriteText : OsuSpriteText - { - private readonly LegacyGlyphStore glyphStore; - - public LegacySpriteText(ISkin skin, string font) - { - Shadow = false; - UseFullGlyphHeight = false; - - Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); - glyphStore = new LegacyGlyphStore(skin); - } - - protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); - - private class LegacyGlyphStore : ITexturedGlyphLookupStore - { - private readonly ISkin skin; - - public LegacyGlyphStore(ISkin skin) - { - this.skin = skin; - } - - public ITexturedCharacterGlyph Get(string fontName, char character) - { - var texture = skin.GetTexture($"{fontName}-{character}"); - - if (texture != null) - // Approximate value that brings character sizing roughly in-line with stable - texture.ScaleAdjust *= 18; - - if (texture == null) - return null; - - return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); - } - - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); - } - } - public class LegacyCursor : CompositeDrawable { public LegacyCursor() diff --git a/osu.Game/Skinning/LegacySkinResourceStore.cs b/osu.Game/Skinning/LegacySkinResourceStore.cs new file mode 100644 index 0000000000..c8912aeb1f --- /dev/null +++ b/osu.Game/Skinning/LegacySkinResourceStore.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.IO.Stores; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class LegacySkinResourceStore : IResourceStore + where T : INamedFileInfo + { + private readonly IHasFiles source; + private readonly IResourceStore underlyingStore; + + private string getPathForFile(string filename) + { + bool hasExtension = filename.Contains('.'); + + var file = source.Files.Find(f => + string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), filename, StringComparison.InvariantCultureIgnoreCase)); + return file?.FileInfo.StoragePath; + } + + public LegacySkinResourceStore(IHasFiles source, IResourceStore underlyingStore) + { + this.source = source; + this.underlyingStore = underlyingStore; + } + + public Stream GetStream(string name) + { + string path = getPathForFile(name); + return path == null ? null : underlyingStore.GetStream(path); + } + + public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); + + byte[] IResourceStore.Get(string name) => GetAsync(name).Result; + + public Task GetAsync(string name) + { + string path = getPathForFile(name); + return path == null ? Task.FromResult(null) : underlyingStore.GetAsync(path); + } + + #region IDisposable Support + + private bool isDisposed; + + protected virtual void Dispose(bool disposing) + { + if (!isDisposed) + { + isDisposed = true; + } + } + + ~LegacySkinResourceStore() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } +} diff --git a/osu.Game/Skinning/LegacySpriteText.cs b/osu.Game/Skinning/LegacySpriteText.cs new file mode 100644 index 0000000000..dbcec019d6 --- /dev/null +++ b/osu.Game/Skinning/LegacySpriteText.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Text; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Skinning +{ + public class LegacySpriteText : OsuSpriteText + { + private readonly LegacyGlyphStore glyphStore; + + public LegacySpriteText(ISkin skin, string font) + { + Shadow = false; + UseFullGlyphHeight = false; + + Font = new FontUsage(font, OsuFont.DEFAULT_FONT_SIZE); + glyphStore = new LegacyGlyphStore(skin); + } + + protected override TextBuilder CreateTextBuilder(ITexturedGlyphLookupStore store) => base.CreateTextBuilder(glyphStore); + + private class LegacyGlyphStore : ITexturedGlyphLookupStore + { + private readonly ISkin skin; + + public LegacyGlyphStore(ISkin skin) + { + this.skin = skin; + } + + public ITexturedCharacterGlyph Get(string fontName, char character) + { + var texture = skin.GetTexture($"{fontName}-{character}"); + + if (texture != null) + // Approximate value that brings character sizing roughly in-line with stable + texture.ScaleAdjust *= 18; + + if (texture == null) + return null; + + return new TexturedCharacterGlyph(new CharacterGlyph(character, 0, 0, texture.Width, null), texture, 1f / texture.ScaleAdjust); + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + } + } +} From c389a5c798974d33f1b8728b09b99ebdd7e23137 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:42:29 +0900 Subject: [PATCH 103/162] Move remaining osu-specific implementations to OsuLegacySkin --- .../Skinning/LegacyCursor.cs | 42 +++++++++++ .../Skinning/NonPlayfieldSprite.cs | 28 +++++++ .../Skinning/OsuLegacySkin.cs | 22 ++++++ osu.Game/Skinning/LegacySkin.cs | 73 ------------------- 4 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs create mode 100644 osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs new file mode 100644 index 0000000000..470ba3acae --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public class LegacyCursor : CompositeDrawable + { + public LegacyCursor() + { + Size = new Vector2(50); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + InternalChildren = new Drawable[] + { + new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursormiddle"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new NonPlayfieldSprite + { + Texture = skin.GetTexture("cursor"), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs new file mode 100644 index 0000000000..55257106e2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/NonPlayfieldSprite.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + /// + /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. + /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). + /// + public class NonPlayfieldSprite : Sprite + { + public override Texture Texture + { + get => base.Texture; + set + { + if (value != null) + // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. + value.ScaleAdjust *= 1.6f; + base.Texture = value; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 927cbc5d2f..27bfdc315b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -82,6 +82,26 @@ namespace osu.Game.Rulesets.Osu.Skinning return new LegacyMainCirclePiece(); return null; + + case "Play/osu/cursor": + if (GetTexture("cursor") != null) + return new LegacyCursor(); + + return null; + + case "Play/osu/number-text": + + string font = GetValue(config => config.HitCircleFont); + var overlap = GetValue(config => config.HitCircleOverlap); + + return !hasFont(font) + ? null + : new LegacySpriteText(this, font) + { + // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size + Scale = new Vector2(0.96f), + Spacing = new Vector2(-overlap * 0.89f, 0) + }; } return null; @@ -93,5 +113,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + + private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0151a211d3..9a47e01f4e 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -3,17 +3,12 @@ using System.IO; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Audio; -using osu.Game.Rulesets.UI; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -59,12 +54,6 @@ namespace osu.Game.Skinning switch (componentName) { - case "Play/osu/cursor": - if (GetTexture("cursor") != null) - return new LegacyCursor(); - - return null; - case "Play/osu/sliderfollowcircle": animatable = true; break; @@ -92,16 +81,6 @@ namespace osu.Game.Skinning animatable = true; looping = false; break; - - case "Play/osu/number-text": - return !hasFont(Configuration.HitCircleFont) - ? null - : new LegacySpriteText(this, Configuration.HitCircleFont) - { - Scale = new Vector2(0.96f), - // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size - Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) - }; } return this.GetAnimation(componentName, animatable, looping); @@ -143,62 +122,10 @@ namespace osu.Game.Skinning return null; } - private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; - private string getFallbackName(string componentName) { string lastPiece = componentName.Split('/').Last(); return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } - - public class LegacyCursor : CompositeDrawable - { - public LegacyCursor() - { - Size = new Vector2(50); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - [BackgroundDependencyLoader] - private void load(ISkinSource skin) - { - InternalChildren = new Drawable[] - { - new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursormiddle"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new NonPlayfieldSprite - { - Texture = skin.GetTexture("cursor"), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }; - } - } - - /// - /// A sprite which is displayed within the playfield, but historically was not considered part of the playfield. - /// Performs scale adjustment to undo the scale applied by (osu! ruleset specifically). - /// - private class NonPlayfieldSprite : Sprite - { - public override Texture Texture - { - get => base.Texture; - set - { - if (value != null) - // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. - value.ScaleAdjust *= 1.6f; - base.Texture = value; - } - } - } } } From 22e3ad8b9c388ee6b4ff43c9acb3f186a7070264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 13:58:17 +0900 Subject: [PATCH 104/162] Add skinning support to cursor test --- .../Resources/default-skin/cursor@2x.png | Bin 0 -> 28063 bytes .../default-skin/cursormiddle@2x.png | Bin 0 -> 7676 bytes .../TestSceneGameplayCursor.cs | 18 ++++++------------ .../Skinning/OsuLegacySkin.cs | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png create mode 100755 osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursor@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..75f9ba5ea6637636912004f8a889f5575d830436 GIT binary patch literal 28063 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 j?W=x{bW*0DSqRCSN6M9{T?P4Lr@_ literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/cursormiddle@2x.png new file mode 100755 index 0000000000000000000000000000000000000000..ebf59c18ba9f62390f49be4bed696a997a51f05c GIT binary patch literal 7676 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 q!@f`~Q0000 RequiredTypes => new[] { typeof(CursorTrail) }; - public CursorContainer Cursor => cursorContainer; - - public bool ProvidingUserCursor => true; - [BackgroundDependencyLoader] private void load() { - Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); + SetContents(() => new OsuCursorContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + }); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 27bfdc315b..0295a419ac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; case "Play/osu/cursor": - if (GetTexture("cursor") != null) + if (source.GetTexture("cursor") != null) return new LegacyCursor(); return null; From 8f9fe9923746dfd62231d30dc198bf2458bfa36d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:40:36 +0900 Subject: [PATCH 105/162] Move SliderFollowCircle implementation --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 3 +++ osu.Game/Skinning/LegacySkin.cs | 4 ---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 0295a419ac..3c508f34e0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -59,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Skinning { switch (componentName) { + case "Play/osu/sliderfollowcircle": + return this.GetAnimation(componentName, true, true); + case "Play/osu/sliderball": var sliderBallContent = this.GetAnimation("sliderb", true, true, ""); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 9a47e01f4e..1572c588e8 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -54,10 +54,6 @@ namespace osu.Game.Skinning switch (componentName) { - case "Play/osu/sliderfollowcircle": - animatable = true; - break; - case "Play/Miss": componentName = "hit0"; animatable = true; From 493fc5d400d616135e94291f6ec6f498cbd971da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:44:36 +0900 Subject: [PATCH 106/162] Bring back OsuPlayer test --- .../TestSceneOsuPlayer.cs | 148 +---------------- .../TestSceneSkinFallbacks.cs | 157 ++++++++++++++++++ 2 files changed, 161 insertions(+), 144 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index d61378f4f8..1ba027ea4b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -1,157 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Timing; -using osu.Game.Audio; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Screens.Play; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - [TestFixture] - public class TestSceneOsuPlayer : PlayerTestScene - { - private readonly TestSource testUserSkin; - private readonly TestSource testBeatmapSkin; - - public TestSceneOsuPlayer() - : base(new OsuRuleset()) + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene { - testUserSkin = new TestSource("user"); - testBeatmapSkin = new TestSource("beatmap"); - } - - [Test] - public void TestBeatmapSkinDefault() - { - AddStep("enable user provider", () => testUserSkin.Enabled = true); - - AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); - checkNextHitObject("beatmap"); - - AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); - checkNextHitObject("user"); - - AddStep("disable user provider", () => testUserSkin.Enabled = false); - checkNextHitObject(null); - } - - private void checkNextHitObject(string skin) => - AddUntilStep($"check skin from {skin}", () => + public TestSceneOsuPlayer() + : base(new OsuRuleset()) { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); - - if (firstObject == null) - return false; - - var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; - - if (skin == null && skinnable?.Drawable is Sprite) - // check for default skin provider - return true; - - var text = skinnable?.Drawable as SpriteText; - - return text?.Text == skin; - }); - - [Resolved] - private AudioManager audio { get; set; } - - protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); - - public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap - { - private readonly ISkinSource skin; - - public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) - : base(beatmap, frameBasedClock, audio) - { - this.skin = skin; - } - - protected override ISkin GetSkin() => skin; - } - - public class SkinProvidingPlayer : TestPlayer - { - private readonly TestSource userSkin; - - public SkinProvidingPlayer(TestSource userSkin) - { - this.userSkin = userSkin; - } - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - dependencies.CacheAs(userSkin); - - return dependencies; } } - - public class TestSource : ISkinSource - { - private readonly string identifier; - - public TestSource(string identifier) - { - this.identifier = identifier; - } - - public Drawable GetDrawableComponent(string componentName) - { - if (!enabled) return null; - - return new SpriteText - { - Text = identifier, - Font = OsuFont.Default.With(size: 30), - }; - } - - public Texture GetTexture(string componentName) => null; - - public SampleChannel GetSample(ISampleInfo sampleInfo) => null; - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; - - public event Action SourceChanged; - - private bool enabled = true; - - public bool Enabled - { - get => enabled; - set - { - if (value == enabled) - return; - - enabled = value; - SourceChanged?.Invoke(); - } - } - } - } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs new file mode 100644 index 0000000000..24d0b39bed --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Timing; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneSkinFallbacks : PlayerTestScene + { + private readonly TestSource testUserSkin; + private readonly TestSource testBeatmapSkin; + + public TestSceneSkinFallbacks() + : base(new OsuRuleset()) + { + testUserSkin = new TestSource("user"); + testBeatmapSkin = new TestSource("beatmap"); + } + + [Test] + public void TestBeatmapSkinDefault() + { + AddStep("enable user provider", () => testUserSkin.Enabled = true); + + AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true)); + checkNextHitObject("beatmap"); + + AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false)); + checkNextHitObject("user"); + + AddStep("disable user provider", () => testUserSkin.Enabled = false); + checkNextHitObject(null); + } + + private void checkNextHitObject(string skin) => + AddUntilStep($"check skin from {skin}", () => + { + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + + if (firstObject == null) + return false; + + var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable; + + if (skin == null && skinnable?.Drawable is Sprite) + // check for default skin provider + return true; + + var text = skinnable?.Drawable as SpriteText; + + return text?.Text == skin; + }); + + [Resolved] + private AudioManager audio { get; set; } + + protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin); + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin); + + public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly ISkinSource skin; + + public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin) + : base(beatmap, frameBasedClock, audio) + { + this.skin = skin; + } + + protected override ISkin GetSkin() => skin; + } + + public class SkinProvidingPlayer : TestPlayer + { + private readonly TestSource userSkin; + + public SkinProvidingPlayer(TestSource userSkin) + { + this.userSkin = userSkin; + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(userSkin); + + return dependencies; + } + } + + public class TestSource : ISkinSource + { + private readonly string identifier; + + public TestSource(string identifier) + { + this.identifier = identifier; + } + + public Drawable GetDrawableComponent(string componentName) + { + if (!enabled) return null; + + return new SpriteText + { + Text = identifier, + Font = OsuFont.Default.With(size: 30), + }; + } + + public Texture GetTexture(string componentName) => null; + + public SampleChannel GetSample(ISampleInfo sampleInfo) => null; + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default; + + public event Action SourceChanged; + + private bool enabled = true; + + public bool Enabled + { + get => enabled; + set + { + if (value == enabled) + return; + + enabled = value; + SourceChanged?.Invoke(); + } + } + } + } +} From c3fb4b9099c9f7dd7e0bd71f6ae14a61d66c6f3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:51:47 +0900 Subject: [PATCH 107/162] Fix test failing --- osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index 24d0b39bed..731b0a84e9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void checkNextHitObject(string skin) => AddUntilStep($"check skin from {skin}", () => { - var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.OfType().FirstOrDefault(); + var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault(); if (firstObject == null) return false; From ae05faa6d2e96d49c0de9d2791d0bbb4d12c4e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 14:54:46 +0900 Subject: [PATCH 108/162] Fix indentation --- osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 1ba027ea4b..0a33b09ba8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -6,12 +6,12 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - [TestFixture] - public class TestSceneOsuPlayer : PlayerTestScene + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + public TestSceneOsuPlayer() + : base(new OsuRuleset()) { - public TestSceneOsuPlayer() - : base(new OsuRuleset()) - { - } } + } } From 835ee0aa2fbd75ed38782a54750135131396e708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:29:13 +0900 Subject: [PATCH 109/162] Code quality fixes --- .../Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs index fae1b68e4a..fbd6aedd45 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs @@ -18,10 +18,13 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int margin = 10; private readonly Bindable type = new Bindable(); + private readonly HitWindows hitWindows; + private readonly ScoreProcessor processor; private BarHitErrorDisplay leftDisplay; + private BarHitErrorDisplay rightDisplay; public HitErrorDisplayOverlay(ScoreProcessor processor) @@ -40,10 +43,10 @@ namespace osu.Game.Screens.Play.HitErrorDisplay protected override void LoadComplete() { base.LoadComplete(); - type.BindValueChanged(onTypeChanged, true); + type.BindValueChanged(typeChanged, true); } - private void onTypeChanged(ValueChangedEvent type) + private void typeChanged(ValueChangedEvent type) { switch (type.NewValue) { From bdbfa7bd2f5a4afde5c21478144f76a49dc25b2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:32:47 +0900 Subject: [PATCH 110/162] Fix class naming --- ...isplay.cs => TestSceneBarHitErrorMeter.cs} | 12 +- osu.Game/Screens/Play/HUDOverlay.cs | 7 +- ...HitErrorDisplay.cs => BarHitErrorMeter.cs} | 4 +- .../Play/HitErrorDisplay/HitErrorDisplay.cs | 120 +++++++++++++++- .../HitErrorDisplay/HitErrorDisplayOverlay.cs | 129 ------------------ .../Play/HitErrorDisplay/HitErrorMeter.cs | 21 +++ 6 files changed, 146 insertions(+), 147 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestSceneHitErrorDisplay.cs => TestSceneBarHitErrorMeter.cs} (94%) rename osu.Game/Screens/Play/HitErrorDisplay/{BarHitErrorDisplay.cs => BarHitErrorMeter.cs} (98%) delete mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs create mode 100644 osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs similarity index 94% rename from osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index a148bdad67..aac9e206c3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorDisplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -19,16 +19,16 @@ using osu.Framework.Graphics.Sprites; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneHitErrorDisplay : OsuTestScene + public class TestSceneBarHitErrorMeter : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(HitErrorDisplay), + typeof(HitErrorMeter), }; - private HitErrorDisplay display; + private HitErrorMeter meter; - public TestSceneHitErrorDisplay() + public TestSceneBarHitErrorMeter() { recreateDisplay(new OsuHitWindows(), 5); AddStep("New random judgement", () => newJudgement()); @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(display = new BarHitErrorDisplay(hitWindows) + Add(meter = new BarHitErrorMeter(hitWindows) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void newJudgement(float offset = 0) { - display?.OnNewJudgement(new JudgementResult(new Judgement()) + meter?.OnNewJudgement(new JudgementResult(new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, Type = HitResult.Perfect, diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 02432cf64e..79392221e4 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -15,7 +15,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play.HitErrorDisplay; using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; @@ -34,7 +33,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly HitErrorDisplayOverlay HitErrorDisplayOverlay; + public readonly HitErrorDisplay.HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -86,7 +85,7 @@ namespace osu.Game.Screens.Play HealthDisplay = CreateHealthDisplay(), Progress = CreateProgress(), ModDisplay = CreateModsContainer(), - HitErrorDisplayOverlay = CreateHitErrorDisplayOverlay(), + HitErrorDisplay = CreateHitErrorDisplayOverlay(), } }, PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), @@ -259,7 +258,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplayOverlay CreateHitErrorDisplayOverlay() => new HitErrorDisplayOverlay(scoreProcessor) + protected virtual HitErrorDisplay.HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay.HitErrorDisplay(scoreProcessor) { RelativeSizeAxes = Axes.Both, }; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs similarity index 98% rename from osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs rename to osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 85d017073a..3ec3740816 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -18,7 +18,7 @@ using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { - public class BarHitErrorDisplay : HitErrorDisplay + public class BarHitErrorMeter : HitErrorMeter { /// /// The amount of which will be stored to calculate arrow position. @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; - public BarHitErrorDisplay(HitWindows hitWindows, bool reversed = false) + public BarHitErrorMeter(HitWindows hitWindows, bool reversed = false) : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 422e151d8a..5c884f3f53 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -2,20 +2,128 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Judgements; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Scoring; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay { - public abstract class HitErrorDisplay : CompositeDrawable + public class HitErrorDisplay : Container { - protected readonly HitWindows HitWindows; + private const int fade_duration = 200; + private const int margin = 10; - protected HitErrorDisplay(HitWindows hitWindows) + private readonly Bindable type = new Bindable(); + + private readonly HitWindows hitWindows; + + private readonly ScoreProcessor processor; + + private BarHitErrorMeter leftMeter; + + private BarHitErrorMeter rightMeter; + + public HitErrorDisplay(ScoreProcessor processor) { - HitWindows = hitWindows; + this.processor = processor; + hitWindows = processor.CreateHitWindows(); } - public abstract void OnNewJudgement(JudgementResult newJudgement); + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, Bindable workingBeatmap) + { + config.BindWith(OsuSetting.ScoreMeter, type); + hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + type.BindValueChanged(typeChanged, true); + } + + private void typeChanged(ValueChangedEvent type) + { + switch (type.NewValue) + { + case ScoreMeterType.None: + removeLeftDisplay(); + removeRightDisplay(); + break; + + case ScoreMeterType.HitErrorBoth: + addLeftDisplay(); + addRightDisplay(); + break; + + case ScoreMeterType.HitErrorLeft: + addLeftDisplay(); + removeRightDisplay(); + break; + + case ScoreMeterType.HitErrorRight: + addRightDisplay(); + removeLeftDisplay(); + break; + } + } + + private void addLeftDisplay() + { + if (leftMeter != null) + return; + + leftMeter = createNew(); + } + + private void addRightDisplay() + { + if (rightMeter != null) + return; + + rightMeter = createNew(true); + } + + private void removeRightDisplay() + { + if (rightMeter == null) + return; + + processor.NewJudgement -= rightMeter.OnNewJudgement; + + rightMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); + rightMeter = null; + } + + private void removeLeftDisplay() + { + if (leftMeter == null) + return; + + processor.NewJudgement -= leftMeter.OnNewJudgement; + + leftMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); + leftMeter = null; + } + + private BarHitErrorMeter createNew(bool reversed = false) + { + var display = new BarHitErrorMeter(hitWindows, reversed) + { + Margin = new MarginPadding(margin), + Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Alpha = 0, + }; + + processor.NewJudgement += display.OnNewJudgement; + Add(display); + display.FadeInFromZero(fade_duration, Easing.OutQuint); + return display; + } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs deleted file mode 100644 index fbd6aedd45..0000000000 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplayOverlay.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Screens.Play.HitErrorDisplay -{ - public class HitErrorDisplayOverlay : Container - { - private const int fade_duration = 200; - private const int margin = 10; - - private readonly Bindable type = new Bindable(); - - private readonly HitWindows hitWindows; - - private readonly ScoreProcessor processor; - - private BarHitErrorDisplay leftDisplay; - - private BarHitErrorDisplay rightDisplay; - - public HitErrorDisplayOverlay(ScoreProcessor processor) - { - this.processor = processor; - hitWindows = processor.CreateHitWindows(); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Bindable workingBeatmap) - { - config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - type.BindValueChanged(typeChanged, true); - } - - private void typeChanged(ValueChangedEvent type) - { - switch (type.NewValue) - { - case ScoreMeterType.None: - removeLeftDisplay(); - removeRightDisplay(); - break; - - case ScoreMeterType.HitErrorBoth: - addLeftDisplay(); - addRightDisplay(); - break; - - case ScoreMeterType.HitErrorLeft: - addLeftDisplay(); - removeRightDisplay(); - break; - - case ScoreMeterType.HitErrorRight: - addRightDisplay(); - removeLeftDisplay(); - break; - } - } - - private void addLeftDisplay() - { - if (leftDisplay != null) - return; - - leftDisplay = createNew(); - } - - private void addRightDisplay() - { - if (rightDisplay != null) - return; - - rightDisplay = createNew(true); - } - - private void removeRightDisplay() - { - if (rightDisplay == null) - return; - - processor.NewJudgement -= rightDisplay.OnNewJudgement; - - rightDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); - rightDisplay = null; - } - - private void removeLeftDisplay() - { - if (leftDisplay == null) - return; - - processor.NewJudgement -= leftDisplay.OnNewJudgement; - - leftDisplay.FadeOut(fade_duration, Easing.OutQuint).Expire(); - leftDisplay = null; - } - - private BarHitErrorDisplay createNew(bool reversed = false) - { - var display = new BarHitErrorDisplay(hitWindows, reversed) - { - Margin = new MarginPadding(margin), - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Alpha = 0, - }; - - processor.NewJudgement += display.OnNewJudgement; - Add(display); - display.FadeInFromZero(fade_duration, Easing.OutQuint); - return display; - } - } -} diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs new file mode 100644 index 0000000000..848e892eaa --- /dev/null +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Screens.Play.HitErrorDisplay +{ + public abstract class HitErrorMeter : CompositeDrawable + { + protected readonly HitWindows HitWindows; + + protected HitErrorMeter(HitWindows hitWindows) + { + HitWindows = hitWindows; + } + + public abstract void OnNewJudgement(JudgementResult newJudgement); + } +} From 6640161bc1fde5e88fff7e1084c83468670660ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 15:51:36 +0900 Subject: [PATCH 111/162] Simplify event propagation --- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 16 ++-- .../Play/HitErrorDisplay/HitErrorDisplay.cs | 90 ++++++------------- 2 files changed, 36 insertions(+), 70 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 3ec3740816..b00f3f6f5f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly List judgementOffsets = new List(); private readonly double maxHitWindows; - public BarHitErrorMeter(HitWindows hitWindows, bool reversed = false) + public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; @@ -54,23 +54,23 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { judgementsContainer = new Container { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, bar = new FillFlowContainer { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Width = bar_width, RelativeSizeAxes = Axes.Y, Direction = FillDirection.Vertical, }, new Container { - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, - Icon = reversed ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } }, diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs index 5c884f3f53..eaaf8e810c 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs @@ -1,13 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Play.HitErrorDisplay @@ -19,25 +22,33 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); - private readonly HitWindows hitWindows; + private HitWindows hitWindows; private readonly ScoreProcessor processor; - private BarHitErrorMeter leftMeter; - - private BarHitErrorMeter rightMeter; - public HitErrorDisplay(ScoreProcessor processor) { this.processor = processor; - hitWindows = processor.CreateHitWindows(); + processor.NewJudgement += onNewJudgement; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + processor.NewJudgement -= onNewJudgement; + } + + private void onNewJudgement(JudgementResult result) + { + foreach (var c in Children) + c.OnNewJudgement(result); } [BackgroundDependencyLoader] private void load(OsuConfigManager config, Bindable workingBeatmap) { config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows.SetDifficulty(workingBeatmap.Value.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows = workingBeatmap.Value.Beatmap.HitObjects.First().HitWindows; } protected override void LoadComplete() @@ -48,82 +59,37 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private void typeChanged(ValueChangedEvent type) { + Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); + switch (type.NewValue) { - case ScoreMeterType.None: - removeLeftDisplay(); - removeRightDisplay(); - break; - case ScoreMeterType.HitErrorBoth: - addLeftDisplay(); - addRightDisplay(); + createBar(false); + createBar(true); break; case ScoreMeterType.HitErrorLeft: - addLeftDisplay(); - removeRightDisplay(); + createBar(false); break; case ScoreMeterType.HitErrorRight: - addRightDisplay(); - removeLeftDisplay(); + createBar(true); break; } } - private void addLeftDisplay() + private void createBar(bool rightAligned) { - if (leftMeter != null) - return; - - leftMeter = createNew(); - } - - private void addRightDisplay() - { - if (rightMeter != null) - return; - - rightMeter = createNew(true); - } - - private void removeRightDisplay() - { - if (rightMeter == null) - return; - - processor.NewJudgement -= rightMeter.OnNewJudgement; - - rightMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); - rightMeter = null; - } - - private void removeLeftDisplay() - { - if (leftMeter == null) - return; - - processor.NewJudgement -= leftMeter.OnNewJudgement; - - leftMeter.FadeOut(fade_duration, Easing.OutQuint).Expire(); - leftMeter = null; - } - - private BarHitErrorMeter createNew(bool reversed = false) - { - var display = new BarHitErrorMeter(hitWindows, reversed) + var display = new BarHitErrorMeter(hitWindows, rightAligned) { Margin = new MarginPadding(margin), - Anchor = reversed ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = reversed ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, Alpha = 0, }; - processor.NewJudgement += display.OnNewJudgement; Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); - return display; } } } From b03b520818eb2e9474fa7f7ea7daf01c287263b5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:13:21 +0300 Subject: [PATCH 112/162] Move Absing from the APIKudosuHistory --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 6 ------ .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 19ce11aa13..67ff20e6c2 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -13,12 +13,6 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset CreatedAt; [JsonProperty("amount")] - private int amount - { - //We can receive negative values. However "action" is enough to build needed items - set => Amount = Math.Abs(value); - } - public int Amount; [JsonProperty("post")] diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index fb7d597012..94733324ba 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; +using System; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -56,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {historyItem.Amount} kudosu", t => + linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; From 71c844facdff1219fc5b0a6e1189b81e6058c8d5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:22:49 +0300 Subject: [PATCH 113/162] Remove unwanted spacings --- .../Kudosu/DrawableKudosuHistoryItem.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 94733324ba..408468fa73 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using System; +using osuTK; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -37,6 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, + Spacing = new Vector2(3), }, date = new DrawableDate(historyItem.CreatedAt) { @@ -57,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu", t => + linkFlowContainer.AddText($@"{Math.Abs(historyItem.Amount)} kudosu", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; @@ -75,40 +77,40 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - return @" from obtaining votes in modding post of"; + return @"from obtaining votes in modding post of"; case KudosuAction.Give: - return $@" from {userLink()} for a post at"; + return $@"from {userLink()} for a post at"; case KudosuAction.Reset: return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: - return @" from losing votes in modding post of"; + return @"from losing votes in modding post of"; case KudosuAction.DenyKudosuReset: - return @" from modding post"; + return @"from modding post"; case KudosuAction.Revoke: return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: - return @" from kudosu deny repeal of modding post"; + return @"from kudosu deny repeal of modding post"; case KudosuAction.DeleteReset: - return @" from modding post deletion of"; + return @"from modding post deletion of"; case KudosuAction.RestoreGive: - return @" from modding post restoration of"; + return @"from modding post restoration of"; case KudosuAction.RecalculateGive: - return @" from votes recalculation in modding post of"; + return @"from votes recalculation in modding post of"; case KudosuAction.RecalculateReset: - return @" from votes recalculation in modding post of"; + return @"from votes recalculation in modding post of"; default: - return @" from unknown event "; + return @"from unknown event"; } } From 72dbeaec1632ce6a94c71bc82141d447a1f19d4f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 30 Aug 2019 10:26:11 +0300 Subject: [PATCH 114/162] Fix the comment --- osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 67ff20e6c2..f2297f7a10 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -44,7 +44,7 @@ namespace osu.Game.Online.API.Requests.Responses { set { - //We will receive something like "foo.bar" or just "foo" + //We will receive something like "event.action" or just "action" string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); From 5f3e638499c1d8fde4362239bdbfef91b9add024 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 16:40:24 +0900 Subject: [PATCH 115/162] Make test useful --- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index aac9e206c3..28c5f0ae08 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -30,8 +30,14 @@ namespace osu.Game.Tests.Visual.Gameplay public TestSceneBarHitErrorMeter() { - recreateDisplay(new OsuHitWindows(), 5); - AddStep("New random judgement", () => newJudgement()); + var hitWindows = new OsuHitWindows(); + + recreateDisplay(hitWindows, 5); + + AddRepeatStep("New random judgement", () => newJudgement(), 40); + + AddRepeatStep("New max negative", () => newJudgement(-hitWindows.Meh), 20); + AddRepeatStep("New max positive", () => newJudgement(hitWindows.Meh), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); } @@ -122,11 +128,11 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void newJudgement(float offset = 0) + private void newJudgement(double offset = 0) { meter?.OnNewJudgement(new JudgementResult(new Judgement()) { - TimeOffset = offset == 0 ? RNG.Next(-70, 70) : offset, + TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, }); } From a73d672c2f3c0397d80400d8c3e96d4b22b55d2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 16:40:39 +0900 Subject: [PATCH 116/162] Tidy up judgement line logic (and fix it displaying at the wrong place) --- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 119 +++++++++--------- .../Play/HitErrorDisplay/HitErrorMeter.cs | 2 +- 2 files changed, 63 insertions(+), 58 deletions(-) diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index b00f3f6f5f..ffaada6ff4 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -8,42 +9,46 @@ using osu.Game.Rulesets.Judgements; using osuTK.Graphics; using osuTK; using osu.Framework.Graphics.Sprites; -using System.Collections.Generic; using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; -using System.Linq; namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorMeter : HitErrorMeter { - /// - /// The amount of which will be stored to calculate arrow position. - /// - private const int stored_judgements_amount = 5; + private readonly bool rightAligned; private const int judgement_fade_duration = 10000; - private const int arrow_move_duration = 500; + + private const int arrow_move_duration = 400; + private const int judgement_line_width = 8; + private const int bar_height = 200; + private const int bar_width = 3; + private const int spacing = 3; private readonly SpriteIcon arrow; + private readonly FillFlowContainer bar; + private readonly Container judgementsContainer; - private readonly List judgementOffsets = new List(); - private readonly double maxHitWindows; + + private readonly double maxHitWindow; public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { - maxHitWindows = HitWindows.Meh == 0 ? HitWindows.Good : HitWindows.Meh; + this.rightAligned = rightAligned; + maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); AutoSizeAxes = Axes.Both; + AddInternal(new FillFlowContainer { AutoSizeAxes = Axes.X, @@ -75,9 +80,10 @@ namespace osu.Game.Screens.Play.HitErrorDisplay RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon { - Anchor = Anchor.Centre, + Anchor = Anchor.TopCentre, Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, + Y = 0.5f, Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } @@ -93,24 +99,20 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { bar.AddRange(new[] { - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), - (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), - (maxHitWindows - HitWindows.Good) / (maxHitWindows * 2)) + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), + createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)) }); } else { bar.AddRange(new[] { - createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), - (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindows), - createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), - (HitWindows.Good - HitWindows.Great) / (maxHitWindows * 2)), + createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), + createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), + createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), }); } } @@ -122,51 +124,54 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Height = (float)height }; - public override void OnNewJudgement(JudgementResult newJudgement) + private double floatingAverage; + + public override void OnNewJudgement(JudgementResult judgement) { - if (!newJudgement.IsHit) + if (!judgement.IsHit) return; - var judgementLine = CreateJudgementLine(newJudgement); + judgementsContainer.Add(new JudgementLine + { + Y = getRelativeJudgementPosition(judgement.TimeOffset), + Anchor = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + Origin = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + }); - judgementsContainer.Add(judgementLine); - - judgementLine.FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); - - arrow.MoveToY(calculateArrowPosition(newJudgement), arrow_move_duration, Easing.OutQuint); + arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) + , arrow_move_duration, Easing.Out); } - protected virtual Container CreateJudgementLine(JudgementResult judgement) => new CircularContainer + private float getRelativeJudgementPosition(double value) => (float)((value / maxHitWindow) + 1) / 2; + + public class JudgementLine : CompositeDrawable { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - RelativeSizeAxes = Axes.X, - Height = 2, - RelativePositionAxes = Axes.Y, - Y = getRelativeJudgementPosition(judgement.TimeOffset), - Child = new Box + public JudgementLine() { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, + RelativeSizeAxes = Axes.X; + RelativePositionAxes = Axes.Y; + Height = 2; + + InternalChild = new CircularContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + } + }; } - }; - private float getRelativeJudgementPosition(double value) => (float)(value / maxHitWindows); + protected override void LoadComplete() + { + base.LoadComplete(); - private float calculateArrowPosition(JudgementResult newJudgement) - { - judgementOffsets.Add(newJudgement.TimeOffset); - - if (judgementOffsets.Count < stored_judgements_amount) - return getRelativeJudgementPosition(judgementOffsets.Average()); - - double sum = 0; - - for (int i = judgementOffsets.Count - stored_judgements_amount; i < judgementOffsets.Count; i++) - sum += judgementOffsets[i]; - - return getRelativeJudgementPosition(sum / stored_judgements_amount); + Width = 0; + this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); + this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); + } } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs index 848e892eaa..e4599eb2fc 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs @@ -16,6 +16,6 @@ namespace osu.Game.Screens.Play.HitErrorDisplay HitWindows = hitWindows; } - public abstract void OnNewJudgement(JudgementResult newJudgement); + public abstract void OnNewJudgement(JudgementResult judgement); } } From 54696eef3990a1a93f2f707653d2b1fef5071ddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 17:06:23 +0900 Subject: [PATCH 117/162] Reverse display, add animation and reduce width --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 24 +++++--- .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 59 +++++++++++-------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 28c5f0ae08..334e0d3b90 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay }; private HitErrorMeter meter; + private HitErrorMeter meter2; public TestSceneBarHitErrorMeter() { @@ -109,8 +110,8 @@ namespace osu.Game.Tests.Visual.Gameplay Add(new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, Children = new[] @@ -121,20 +122,29 @@ namespace osu.Game.Tests.Visual.Gameplay } }); - Add(meter = new BarHitErrorMeter(hitWindows) + Add(meter = new BarHitErrorMeter(hitWindows, true) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }); + + Add(meter2 = new BarHitErrorMeter(hitWindows, false) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, }); } private void newJudgement(double offset = 0) { - meter?.OnNewJudgement(new JudgementResult(new Judgement()) + var judgement = new JudgementResult(new Judgement()) { TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset, Type = HitResult.Perfect, - }); + }; + + meter.OnNewJudgement(judgement); + meter2.OnNewJudgement(judgement); } } } diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index ffaada6ff4..7fb7b3cf99 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -25,19 +25,19 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private const int arrow_move_duration = 400; - private const int judgement_line_width = 8; + private const int judgement_line_width = 6; private const int bar_height = 200; - private const int bar_width = 3; + private const int bar_width = 2; - private const int spacing = 3; + private const int spacing = 2; - private readonly SpriteIcon arrow; + private SpriteIcon arrow; - private readonly FillFlowContainer bar; + private FillFlowContainer colourBarFlow; - private readonly Container judgementsContainer; + private Container judgementsContainer; private readonly double maxHitWindow; @@ -48,34 +48,39 @@ namespace osu.Game.Screens.Play.HitErrorDisplay maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); AutoSizeAxes = Axes.Both; + } - AddInternal(new FillFlowContainer + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.X, Height = bar_height, Direction = FillDirection.Horizontal, Spacing = new Vector2(spacing, 0), + Margin = new MarginPadding(2), Children = new Drawable[] { judgementsContainer = new Container { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - bar = new FillFlowContainer + colourBarFlow = new FillFlowContainer { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = bar_width, RelativeSizeAxes = Axes.Y, Direction = FillDirection.Vertical, }, new Container { - Anchor = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, - Origin = rightAligned ? Anchor.CentreRight : Anchor.CentreLeft, + Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -84,20 +89,16 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, Y = 0.5f, - Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, + Icon = rightAligned ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, Size = new Vector2(8), } }, } - }); - } + }; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { if (HitWindows.Meh != 0) { - bar.AddRange(new[] + colourBarFlow.AddRange(new[] { createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), @@ -108,7 +109,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } else { - bar.AddRange(new[] + colourBarFlow.AddRange(new[] { createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), @@ -117,6 +118,16 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } } + protected override void LoadComplete() + { + base.LoadComplete(); + + colourBarFlow.Height = 0; + colourBarFlow.ResizeHeightTo(1, 400, Easing.OutQuint); + + arrow.FadeInFromZero(400); + } + private Box createColoredPiece(ColourInfo colour, double height) => new Box { RelativeSizeAxes = Axes.Both, @@ -134,8 +145,8 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Anchor = rightAligned ? Anchor.TopLeft : Anchor.TopRight, - Origin = rightAligned ? Anchor.TopLeft : Anchor.TopRight, + Anchor = rightAligned ? Anchor.TopRight : Anchor.TopLeft, + Origin = rightAligned ? Anchor.TopRight : Anchor.TopLeft, }); arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) From 40729356fa1d19849f15120851653c323c372388 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 17:34:02 +0900 Subject: [PATCH 118/162] Move beat divisor colour retrieval to BindableBeatDivisor --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 39 +++++++++++++++ .../Compose/Components/BeatDivisorControl.cs | 47 +++---------------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index ea3b68e3bd..055077cc4f 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -4,6 +4,9 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Graphics.Colour; +using osu.Game.Graphics; +using osuTK.Graphics; namespace osu.Game.Screens.Edit { @@ -35,5 +38,41 @@ namespace osu.Game.Screens.Edit protected override int DefaultMinValue => VALID_DIVISORS.First(); protected override int DefaultMaxValue => VALID_DIVISORS.Last(); protected override int DefaultPrecision => 1; + + /// + /// Retrieves the appropriate colour for a beat divisor. + /// + /// The beat divisor. + /// The set of colours. + /// The applicable colour from for . + public ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + { + switch (beatDivisor) + { + case 2: + return colours.BlueLight; + + case 4: + return colours.Blue; + + case 8: + return colours.BlueDarker; + + case 16: + return colours.PurpleDark; + + case 3: + return colours.YellowLight; + + case 6: + return colours.Yellow; + + case 12: + return colours.YellowDarker; + + default: + return Color4.White; + } + } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 0d16d8474b..ddcdfdaf80 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -188,6 +188,9 @@ namespace osu.Game.Screens.Edit.Compose.Components { private Marker marker; + [Resolved] + private OsuColour colours { get; set; } + private readonly BindableBeatDivisor beatDivisor; private readonly int[] availableDivisors; @@ -204,11 +207,12 @@ namespace osu.Game.Screens.Edit.Compose.Components { foreach (var t in availableDivisors) { - AddInternal(new Tick(t) + AddInternal(new Tick { Anchor = Anchor.TopLeft, Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, + Colour = beatDivisor.GetColourFor(t, colours), X = getMappedPosition(t) }); } @@ -284,11 +288,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private class Tick : CompositeDrawable { - private readonly int divisor; - - public Tick(int divisor) + public Tick() { - this.divisor = divisor; Size = new Vector2(2.5f, 10); InternalChild = new Box { RelativeSizeAxes = Axes.Both }; @@ -296,42 +297,6 @@ namespace osu.Game.Screens.Edit.Compose.Components CornerRadius = 0.5f; Masking = true; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = getColourForDivisor(divisor, colours); - } - - private ColourInfo getColourForDivisor(int divisor, OsuColour colours) - { - switch (divisor) - { - case 2: - return colours.BlueLight; - - case 4: - return colours.Blue; - - case 8: - return colours.BlueDarker; - - case 16: - return colours.PurpleDark; - - case 3: - return colours.YellowLight; - - case 6: - return colours.Yellow; - - case 12: - return colours.YellowDarker; - - default: - return Color4.White; - } - } } private class Marker : CompositeDrawable From 741bd0a5cffd4a972291054188cc139d62a7358d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:35:06 +0900 Subject: [PATCH 119/162] Fix incorrect colour sizes and simplify alignment specification --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 4 +- osu.Game/Rulesets/Objects/HitWindows.cs | 13 ++ .../Play/HitErrorDisplay/BarHitErrorMeter.cs | 172 +++++++++++++----- 3 files changed, 140 insertions(+), 49 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 334e0d3b90..8852a27f50 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -37,8 +37,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("New random judgement", () => newJudgement(), 40); - AddRepeatStep("New max negative", () => newJudgement(-hitWindows.Meh), 20); - AddRepeatStep("New max positive", () => newJudgement(hitWindows.Meh), 20); + AddRepeatStep("New max negative", () => newJudgement(-hitWindows.HalfWindowFor(HitResult.Meh)), 20); + AddRepeatStep("New max positive", () => newJudgement(hitWindows.HalfWindowFor(HitResult.Meh)), 20); AddStep("New fixed judgement (50ms)", () => newJudgement(50)); } diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index fe099aaee7..e88af67c7c 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -65,6 +65,19 @@ namespace osu.Game.Rulesets.Objects return HitResult.None; } + /// + /// Retrieves a mapping of s to their half window timing for all allowed s. + /// + /// + public IEnumerable<(HitResult result, double length)> GetAllAvailableHalfWindows() + { + for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) + { + if (IsHitResultAllowed(result)) + yield return (result, HalfWindowFor(result)); + } + } + /// /// Check whether it is possible to achieve the provided . /// diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs index 7fb7b3cf99..5a2d892d7f 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,15 +11,16 @@ using osuTK; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Colour; using osu.Game.Graphics; -using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HitErrorDisplay { public class BarHitErrorMeter : HitErrorMeter { - private readonly bool rightAligned; + private readonly Anchor alignment; private const int judgement_fade_duration = 10000; @@ -35,17 +36,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private SpriteIcon arrow; - private FillFlowContainer colourBarFlow; + private Container colourBarsEarly; + private Container colourBarsLate; private Container judgementsContainer; - private readonly double maxHitWindow; + private double maxHitWindow; public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false) : base(hitWindows) { - this.rightAligned = rightAligned; - maxHitWindow = Math.Max(Math.Max(HitWindows.Meh, HitWindows.Ok), HitWindows.Good); + alignment = rightAligned ? Anchor.x0 : Anchor.x2; AutoSizeAxes = Axes.Both; } @@ -64,23 +65,40 @@ namespace osu.Game.Screens.Play.HitErrorDisplay { judgementsContainer = new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, Width = judgement_line_width, RelativeSizeAxes = Axes.Y, }, - colourBarFlow = new FillFlowContainer + colourBars = new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, Width = bar_width, RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, + Children = new Drawable[] + { + colourBarsEarly = new Container + { + Anchor = Anchor.y1 | alignment, + Origin = alignment, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Scale = new Vector2(1, -1), + }, + colourBarsLate = new Container + { + Anchor = Anchor.y1 | alignment, + Origin = alignment, + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + }, + } }, new Container { - Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight, + Anchor = Anchor.y1 | alignment, + Origin = Anchor.y1 | alignment, AutoSizeAxes = Axes.X, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon @@ -89,53 +107,111 @@ namespace osu.Game.Screens.Play.HitErrorDisplay Origin = Anchor.Centre, RelativePositionAxes = Axes.Y, Y = 0.5f, - Icon = rightAligned ? FontAwesome.Solid.ChevronLeft : FontAwesome.Solid.ChevronRight, + Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, Size = new Vector2(8), } }, } }; - if (HitWindows.Meh != 0) - { - colourBarFlow.AddRange(new[] - { - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow.Opacity(0), colours.Yellow), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), - createColoredPiece(colours.Green, (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(ColourInfo.GradientVertical(colours.Yellow, colours.Yellow.Opacity(0)), (maxHitWindow - HitWindows.Good) / (maxHitWindow * 2)) - }); - } - else - { - colourBarFlow.AddRange(new[] - { - createColoredPiece(ColourInfo.GradientVertical(colours.Green.Opacity(0), colours.Green), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - createColoredPiece(colours.BlueLight, HitWindows.Great / maxHitWindow), - createColoredPiece(ColourInfo.GradientVertical(colours.Green, colours.Green.Opacity(0)), (HitWindows.Good - HitWindows.Great) / (maxHitWindow * 2)), - }); - } + createColourBars(colours); } protected override void LoadComplete() { base.LoadComplete(); - colourBarFlow.Height = 0; - colourBarFlow.ResizeHeightTo(1, 400, Easing.OutQuint); + colourBars.Height = 0; + colourBars.ResizeHeightTo(1, 800, Easing.OutQuint); - arrow.FadeInFromZero(400); + arrow.Alpha = 0.01f; + arrow.Delay(200).FadeInFromZero(600); } - private Box createColoredPiece(ColourInfo colour, double height) => new Box + private void createColourBars(OsuColour colours) { - RelativeSizeAxes = Axes.Both, - Colour = colour, - Height = (float)height - }; + var windows = HitWindows.GetAllAvailableHalfWindows().ToArray(); + + maxHitWindow = windows.First().length; + + for (var i = 0; i < windows.Length; i++) + { + var (result, length) = windows[i]; + + colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); + colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0)); + } + + // a little nub to mark the centre point. + var centre = createColourBar(windows.Last().result, 0.01f); + centre.Anchor = centre.Origin = Anchor.y1 | alignment; + centre.Width = 1.5f; + colourBars.Add(centre); + + Color4 getColour(HitResult result) + { + switch (result) + { + case HitResult.Meh: + return colours.Yellow; + + case HitResult.Ok: + return colours.Green; + + case HitResult.Good: + return colours.GreenLight; + + case HitResult.Great: + return colours.Blue; + + default: + return colours.BlueLight; + } + } + + Drawable createColourBar(HitResult result, float height, bool first = false) + { + var colour = getColour(result); + + if (first) + { + // the first bar needs gradient rendering. + const float gradient_start = 0.8f; + + return new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = getColour(result), + Height = height * gradient_start + }, + new Box + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(colour, colour.Opacity(0)), + Y = gradient_start, + Height = height * (1 - gradient_start) + }, + } + }; + } + + return new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Height = height + }; + } + } private double floatingAverage; + private Container colourBars; public override void OnNewJudgement(JudgementResult judgement) { @@ -145,11 +221,12 @@ namespace osu.Game.Screens.Play.HitErrorDisplay judgementsContainer.Add(new JudgementLine { Y = getRelativeJudgementPosition(judgement.TimeOffset), - Anchor = rightAligned ? Anchor.TopRight : Anchor.TopLeft, - Origin = rightAligned ? Anchor.TopRight : Anchor.TopLeft, + Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, + Origin = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, }); - arrow.MoveToY(getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) + arrow.MoveToY( + getRelativeJudgementPosition(floatingAverage = floatingAverage * 0.9 + judgement.TimeOffset * 0.1) , arrow_move_duration, Easing.Out); } @@ -180,6 +257,7 @@ namespace osu.Game.Screens.Play.HitErrorDisplay base.LoadComplete(); Width = 0; + this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); } From 8fc177b743d8a1b3c5300379b597c5487650cb31 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:46:42 +0900 Subject: [PATCH 120/162] Fix namespacing and hitwindow source --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 2 +- .../HitErrorDisplay.cs | 22 ++++++++++--------- .../HitErrorMeters}/BarHitErrorMeter.cs | 18 +++++++-------- .../HitErrorMeters}/HitErrorMeter.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 8 +++---- 5 files changed, 26 insertions(+), 26 deletions(-) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD}/HitErrorDisplay.cs (87%) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD/HitErrorMeters}/BarHitErrorMeter.cs (99%) rename osu.Game/Screens/Play/{HitErrorDisplay => HUD/HitErrorMeters}/HitErrorMeter.cs (91%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 8852a27f50..98826331e1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Screens.Play.HitErrorDisplay; using System; using System.Collections.Generic; using osu.Game.Rulesets.Judgements; @@ -16,6 +15,7 @@ using osu.Framework.MathUtils; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Screens.Play.HUD.HitErrorMeters; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs similarity index 87% rename from osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs rename to osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index eaaf8e810c..0dcb1fee2b 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -1,19 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Game.Beatmaps; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play.HUD.HitErrorMeters; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD { public class HitErrorDisplay : Container { @@ -22,13 +21,17 @@ namespace osu.Game.Screens.Play.HitErrorDisplay private readonly Bindable type = new Bindable(); - private HitWindows hitWindows; + private readonly HitWindows hitWindows; private readonly ScoreProcessor processor; - public HitErrorDisplay(ScoreProcessor processor) + public HitErrorDisplay(ScoreProcessor processor, HitWindows hitWindows) { this.processor = processor; + this.hitWindows = hitWindows; + + RelativeSizeAxes = Axes.Both; + processor.NewJudgement += onNewJudgement; } @@ -45,10 +48,9 @@ namespace osu.Game.Screens.Play.HitErrorDisplay } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, Bindable workingBeatmap) + private void load(OsuConfigManager config) { config.BindWith(OsuSetting.ScoreMeter, type); - hitWindows = workingBeatmap.Value.Beatmap.HitObjects.First().HitWindows; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs similarity index 99% rename from osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs rename to osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 5a2d892d7f..22cccf30d7 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -2,21 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Judgements; -using osuTK.Graphics; -using osuTK; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Objects; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public class BarHitErrorMeter : HitErrorMeter { diff --git a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs similarity index 91% rename from osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs rename to osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index e4599eb2fc..da1d9fff0d 100644 --- a/osu.Game/Screens/Play/HitErrorDisplay/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -5,7 +5,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; -namespace osu.Game.Screens.Play.HitErrorDisplay +namespace osu.Game.Screens.Play.HUD.HitErrorMeters { public abstract class HitErrorMeter : CompositeDrawable { diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 79392221e4..21d5ae557f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -33,7 +34,7 @@ namespace osu.Game.Screens.Play public readonly HealthDisplay HealthDisplay; public readonly SongProgress Progress; public readonly ModDisplay ModDisplay; - public readonly HitErrorDisplay.HitErrorDisplay HitErrorDisplay; + public readonly HitErrorDisplay HitErrorDisplay; public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; @@ -258,10 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay.HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay.HitErrorDisplay(scoreProcessor) - { - RelativeSizeAxes = Axes.Both, - }; + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.First().HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 171f88da409964efc0b56908fc30a0824ee20da9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 09:47:18 +0000 Subject: [PATCH 121/162] Bump ppy.osu.Framework from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8e6ce2d1ba..330018d5cb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -15,7 +15,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 47cc6ec97a..298fbc6704 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,7 +118,7 @@ - + From c3abf0ccb7112f654910ef867a658986e517fa54 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 18:50:38 +0900 Subject: [PATCH 122/162] Improve visuals --- .../Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 22cccf30d7..4e32be3cda 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { private readonly Anchor alignment; - private const int judgement_fade_duration = 10000; - private const int arrow_move_duration = 400; private const int judgement_line_width = 6; @@ -34,6 +32,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private const int spacing = 2; + private const float chevron_size = 8; + private SpriteIcon arrow; private Container colourBarsEarly; @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { Anchor = Anchor.y1 | alignment, Origin = Anchor.y1 | alignment, - AutoSizeAxes = Axes.X, + Width = chevron_size, RelativeSizeAxes = Axes.Y, Child = arrow = new SpriteIcon { @@ -108,7 +108,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativePositionAxes = Axes.Y, Y = 0.5f, Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft, - Size = new Vector2(8), + Size = new Vector2(chevron_size), } }, } @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters colourBars.Height = 0; colourBars.ResizeHeightTo(1, 800, Easing.OutQuint); - arrow.Alpha = 0.01f; + arrow.Alpha = 0; arrow.Delay(200).FadeInFromZero(600); } @@ -234,11 +234,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters public class JudgementLine : CompositeDrawable { + private const int judgement_fade_duration = 10000; + public JudgementLine() { RelativeSizeAxes = Axes.X; RelativePositionAxes = Axes.Y; - Height = 2; + Height = 3; InternalChild = new CircularContainer { @@ -258,7 +260,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Width = 0; - this.ResizeWidthTo(1, 150, Easing.OutElasticHalf); + this.ResizeWidthTo(1, 200, Easing.OutElasticHalf); this.FadeTo(0.8f, 150).Then().FadeOut(judgement_fade_duration, Easing.OutQuint).Expire(); } } From b639ce8e5d237c845adec63f26f5993df4140be8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 10:01:16 +0000 Subject: [PATCH 123/162] Bump ppy.osu.Framework.iOS from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 47cc6ec97a..afb0b9217e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 815220de039ef25606591f5a0e0f7f1122952870 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 10:01:35 +0000 Subject: [PATCH 124/162] Bump ppy.osu.Framework.Android from 2019.830.0 to 2019.830.1 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.830.0 to 2019.830.1. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.830.0...2019.830.1) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 2c3c8bcaad..93a9a073a4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -61,6 +61,6 @@ - + From 80671cefd7db5e83b1059fdccaa3bc5a475bd603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 19:14:07 +0900 Subject: [PATCH 125/162] Final visual polish --- .../HUD/HitErrorMeters/BarHitErrorMeter.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 4e32be3cda..51f9be4792 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -93,6 +93,22 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters RelativeSizeAxes = Axes.Both, Height = 0.5f, }, + new SpriteIcon + { + Y = -10, + Size = new Vector2(10), + Icon = FontAwesome.Solid.ShippingFast, + Anchor = Anchor.y0 | alignment, + Origin = Anchor.y0 | alignment, + }, + new SpriteIcon + { + Y = 10, + Size = new Vector2(10), + Icon = FontAwesome.Solid.Bicycle, + Anchor = Anchor.y2 | alignment, + Origin = Anchor.y2 | alignment, + } } }, new Container @@ -144,8 +160,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters // a little nub to mark the centre point. var centre = createColourBar(windows.Last().result, 0.01f); - centre.Anchor = centre.Origin = Anchor.y1 | alignment; - centre.Width = 1.5f; + centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2); + centre.Width = 2.5f; colourBars.Add(centre); Color4 getColour(HitResult result) From 665fc95d49c595b751d44ca5cf5bae91ae5bda58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 19:37:20 +0900 Subject: [PATCH 126/162] Handle no hitobjects / no hitwindows (osu!catch) --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 3 +++ osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 0dcb1fee2b..2e28d17b80 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -63,6 +63,9 @@ namespace osu.Game.Screens.Play.HUD { Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); + if (hitWindows == null) + return; + switch (type.NewValue) { case ScoreMeterType.HitErrorBoth: diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 21d5ae557f..285737f7a8 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.First().HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault().HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From fab12fa9cd00043a9b820d7511ca2fcddb729d8d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:34:28 +0900 Subject: [PATCH 127/162] Centre align the icons Seems to look better this way. --- .../Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 51f9be4792..7d3b0ae141 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -98,16 +98,16 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters Y = -10, Size = new Vector2(10), Icon = FontAwesome.Solid.ShippingFast, - Anchor = Anchor.y0 | alignment, - Origin = Anchor.y0 | alignment, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, }, new SpriteIcon { Y = 10, Size = new Vector2(10), Icon = FontAwesome.Solid.Bicycle, - Anchor = Anchor.y2 | alignment, - Origin = Anchor.y2 | alignment, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, } } }, From 8b4976ad92399c19acffa4275710eaffe3d9ff02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:37:18 +0900 Subject: [PATCH 128/162] Remove unnecessary intermediate OD tests --- .../Gameplay/TestSceneBarHitErrorMeter.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index 98826331e1..d317c6551f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -46,14 +46,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestOsu() { AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new OsuHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new OsuHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new OsuHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new OsuHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new OsuHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new OsuHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new OsuHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new OsuHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10)); } @@ -61,14 +53,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestTaiko() { AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new TaikoHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new TaikoHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new TaikoHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new TaikoHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new TaikoHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new TaikoHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new TaikoHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new TaikoHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10)); } @@ -76,14 +60,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestMania() { AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new ManiaHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new ManiaHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new ManiaHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new ManiaHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new ManiaHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new ManiaHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new ManiaHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new ManiaHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10)); } @@ -91,14 +67,6 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestCatch() { AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1)); - AddStep("OD 2", () => recreateDisplay(new CatchHitWindows(), 2)); - AddStep("OD 3", () => recreateDisplay(new CatchHitWindows(), 3)); - AddStep("OD 4", () => recreateDisplay(new CatchHitWindows(), 4)); - AddStep("OD 5", () => recreateDisplay(new CatchHitWindows(), 5)); - AddStep("OD 6", () => recreateDisplay(new CatchHitWindows(), 6)); - AddStep("OD 7", () => recreateDisplay(new CatchHitWindows(), 7)); - AddStep("OD 8", () => recreateDisplay(new CatchHitWindows(), 8)); - AddStep("OD 9", () => recreateDisplay(new CatchHitWindows(), 9)); AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10)); } From 6fb8a6cdbe894343c98a54e115c97a2451264717 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:47:13 +0900 Subject: [PATCH 129/162] Fix testcases not working for OD10 --- .../Visual/Gameplay/TestSceneBarHitErrorMeter.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs index d317c6551f..f20440249b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs @@ -28,12 +28,11 @@ namespace osu.Game.Tests.Visual.Gameplay private HitErrorMeter meter; private HitErrorMeter meter2; + private HitWindows hitWindows; public TestSceneBarHitErrorMeter() { - var hitWindows = new OsuHitWindows(); - - recreateDisplay(hitWindows, 5); + recreateDisplay(new OsuHitWindows(), 5); AddRepeatStep("New random judgement", () => newJudgement(), 40); @@ -72,7 +71,9 @@ namespace osu.Game.Tests.Visual.Gameplay private void recreateDisplay(HitWindows hitWindows, float overallDifficulty) { - hitWindows.SetDifficulty(overallDifficulty); + this.hitWindows = hitWindows; + + hitWindows?.SetDifficulty(overallDifficulty); Clear(); @@ -84,9 +85,9 @@ namespace osu.Game.Tests.Visual.Gameplay AutoSizeAxes = Axes.Both, Children = new[] { - new SpriteText { Text = $@"Great: {hitWindows.Great}" }, - new SpriteText { Text = $@"Good: {hitWindows.Good}" }, - new SpriteText { Text = $@"Meh: {hitWindows.Meh}" }, + new SpriteText { Text = $@"Great: {hitWindows?.Great}" }, + new SpriteText { Text = $@"Good: {hitWindows?.Good}" }, + new SpriteText { Text = $@"Meh: {hitWindows?.Meh}" }, } }); From dfccc6036109226ca4be14cd92ca38f6fd3e6d5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:47:21 +0900 Subject: [PATCH 130/162] Reorder HitErrorDisplay --- osu.Game/Screens/Play/HUD/HitErrorDisplay.cs | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs index 2e28d17b80..cdfa0e993b 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorDisplay.cs @@ -35,18 +35,6 @@ namespace osu.Game.Screens.Play.HUD processor.NewJudgement += onNewJudgement; } - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - processor.NewJudgement -= onNewJudgement; - } - - private void onNewJudgement(JudgementResult result) - { - foreach (var c in Children) - c.OnNewJudgement(result); - } - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -59,6 +47,12 @@ namespace osu.Game.Screens.Play.HUD type.BindValueChanged(typeChanged, true); } + private void onNewJudgement(JudgementResult result) + { + foreach (var c in Children) + c.OnNewJudgement(result); + } + private void typeChanged(ValueChangedEvent type) { Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint)); @@ -96,5 +90,11 @@ namespace osu.Game.Screens.Play.HUD Add(display); display.FadeInFromZero(fade_duration, Easing.OutQuint); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + processor.NewJudgement -= onNewJudgement; + } } } From fc813347ac522fad92e2b88b022ab2561957b6ee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 30 Aug 2019 19:54:36 +0900 Subject: [PATCH 131/162] Make JudgementLine private --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index 7d3b0ae141..d5d3cb528e 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters private float getRelativeJudgementPosition(double value) => (float)((value / maxHitWindow) + 1) / 2; - public class JudgementLine : CompositeDrawable + private class JudgementLine : CompositeDrawable { private const int judgement_fade_duration = 10000; From f1db6c7039c1bbc037c36c0449a599f9a6694497 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 30 Aug 2019 20:18:21 +0900 Subject: [PATCH 132/162] Fix likely nullref --- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 285737f7a8..8e642ea552 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -259,7 +259,7 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20, Right = 10 }, }; - protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault().HitWindows); + protected virtual HitErrorDisplay CreateHitErrorDisplayOverlay() => new HitErrorDisplay(scoreProcessor, drawableRuleset.Objects.FirstOrDefault()?.HitWindows); protected virtual PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); From 2876588193897bc9128bb62041c0e6adbc13941b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2019 13:36:31 +0000 Subject: [PATCH 133/162] Bump NUnit3TestAdapter from 3.15.0 to 3.15.1 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.15.0 to 3.15.1. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.15...V3.15.1) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 7c282f449b..c527a81f51 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 4dcfc1b81f..af10d5e06e 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 197309c7c4..c331c811d2 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a5db1625d9..d2a0a8fa6f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 4a9d88f3a6..84f67c9319 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2a8bd393da..bba3c92245 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 1ddf292ad687878c0acf8ef247a0060ba08a5d5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:20:50 +0900 Subject: [PATCH 134/162] Fix vertical alignment of hit error display ticks Wasn't correctly centered before. --- osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs index d5d3cb528e..594dd64e52 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/BarHitErrorMeter.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters { Y = getRelativeJudgementPosition(judgement.TimeOffset), Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, - Origin = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2, + Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2), }); arrow.MoveToY( From f89981e1a330c3105e7fe87481f7a0df6721fa32 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:23:15 +0900 Subject: [PATCH 135/162] Fix legacy skin text reading from the wrong source Regressed with ruleset legacy skin implementation. --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 3c508f34e0..d1bd0f0a88 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return !hasFont(font) ? null - : new LegacySpriteText(this, font) + : new LegacySpriteText(source, font) { // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size Scale = new Vector2(0.96f), @@ -117,6 +117,6 @@ namespace osu.Game.Rulesets.Osu.Skinning public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; - private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; + private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; } } From 12eeec36fca2dccda14043caab9802aa9cdc9754 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 12:33:29 +0900 Subject: [PATCH 136/162] Fix ruleset skins incorrectly providing configuration defaults --- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/DefaultSkinConfiguration.cs | 28 +++++++++++++++++++ osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Skinning/SkinConfiguration.cs | 15 ++++------ 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Skinning/DefaultSkinConfiguration.cs diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 6072bb64ed..ec957566cb 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -13,7 +13,7 @@ namespace osu.Game.Skinning public DefaultSkin() : base(SkinInfo.Default) { - Configuration = new SkinConfiguration(); + Configuration = new DefaultSkinConfiguration(); } public override Drawable GetDrawableComponent(string componentName) => null; diff --git a/osu.Game/Skinning/DefaultSkinConfiguration.cs b/osu.Game/Skinning/DefaultSkinConfiguration.cs new file mode 100644 index 0000000000..722b35f102 --- /dev/null +++ b/osu.Game/Skinning/DefaultSkinConfiguration.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK.Graphics; + +namespace osu.Game.Skinning +{ + /// + /// A skin configuration pre-populated with sane defaults. + /// + public class DefaultSkinConfiguration : SkinConfiguration + { + public DefaultSkinConfiguration() + { + HitCircleFont = "default"; + + ComboColours.AddRange(new[] + { + new Color4(17, 136, 170, 255), + new Color4(102, 136, 0, 255), + new Color4(204, 102, 0, 255), + new Color4(121, 9, 13, 255) + }); + + CursorExpand = true; + } + } +} diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 1572c588e8..56b8aa19c2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -34,7 +34,7 @@ namespace osu.Game.Skinning using (StreamReader reader = new StreamReader(stream)) Configuration = new LegacySkinDecoder().Decode(reader); else - Configuration = new SkinConfiguration(); + Configuration = new DefaultSkinConfiguration(); Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 93b599f9f6..d585c58ef1 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -7,21 +7,18 @@ using osuTK.Graphics; namespace osu.Game.Skinning { + /// + /// An empty skin configuration. + /// public class SkinConfiguration : IHasComboColours, IHasCustomColours { public readonly SkinInfo SkinInfo = new SkinInfo(); - public List ComboColours { get; set; } = new List - { - new Color4(17, 136, 170, 255), - new Color4(102, 136, 0, 255), - new Color4(204, 102, 0, 255), - new Color4(121, 9, 13, 255) - }; + public List ComboColours { get; set; } = new List(); public Dictionary CustomColours { get; set; } = new Dictionary(); - public string HitCircleFont { get; set; } = "default"; + public string HitCircleFont { get; set; } public int HitCircleOverlap { get; set; } @@ -29,6 +26,6 @@ namespace osu.Game.Skinning public float? SliderPathRadius { get; set; } - public bool? CursorExpand { get; set; } = true; + public bool? CursorExpand { get; set; } } } From d2a3e0581b750c030d197573e6923815ca2e60f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 13:27:03 +0900 Subject: [PATCH 137/162] Fix legacy decoder using wrong configuration --- osu.Game/Skinning/LegacySkinDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index ecb112955c..0160755eed 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -5,14 +5,14 @@ using osu.Game.Beatmaps.Formats; namespace osu.Game.Skinning { - public class LegacySkinDecoder : LegacyDecoder + public class LegacySkinDecoder : LegacyDecoder { public LegacySkinDecoder() : base(1) { } - protected override void ParseLine(SkinConfiguration skin, Section section, string line) + protected override void ParseLine(DefaultSkinConfiguration skin, Section section, string line) { line = StripComments(line); From 2988624f1fd66896c72cb286984a733ebd4bfb02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 16:52:41 +0900 Subject: [PATCH 138/162] Add fallback for safety --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 3c508f34e0..97db2d7c8e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Skinning public SampleChannel GetSample(ISampleInfo sample) => null; public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; + => configuration.Value is TConfiguration conf ? query.Invoke(conf) : source.GetValue(query); private bool hasFont(string fontName) => GetTexture($"{fontName}-0") != null; } From 3da5eb6c8b3642980a8f2e0197d0a644122bae13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 31 Aug 2019 16:56:32 +0900 Subject: [PATCH 139/162] Add source lookups for safety --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index d1bd0f0a88..f9fbdcd0e9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -110,9 +110,9 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; } - public Texture GetTexture(string componentName) => null; + public Texture GetTexture(string componentName) => source.GetTexture(componentName); - public SampleChannel GetSample(ISampleInfo sample) => null; + public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => configuration.Value is TConfiguration conf ? query.Invoke(conf) : default; From cbbc6aad6eefdf4bcac3b1fba363dce0e66879d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 31 Aug 2019 21:32:02 +0900 Subject: [PATCH 140/162] Make method static --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 2 +- osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 055077cc4f..2aeb1ef04b 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit /// The beat divisor. /// The set of colours. /// The applicable colour from for . - public ColourInfo GetColourFor(int beatDivisor, OsuColour colours) + public static ColourInfo GetColourFor(int beatDivisor, OsuColour colours) { switch (beatDivisor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index ddcdfdaf80..4d89e43ee5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Anchor = Anchor.TopLeft, Origin = Anchor.TopCentre, RelativePositionAxes = Axes.X, - Colour = beatDivisor.GetColourFor(t, colours), + Colour = BindableBeatDivisor.GetColourFor(t, colours), X = getMappedPosition(t) }); } From 1b4ae5a4a495917257be26a88a0b48a57cc231e4 Mon Sep 17 00:00:00 2001 From: pi1024e Date: Thu, 22 Aug 2019 22:37:01 -0400 Subject: [PATCH 141/162] Spelling fixes --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index d722c7a98a..0303293c41 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -165,7 +165,7 @@ namespace osu.Game.Online.API } // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. - // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests + // Without this, we will end up circulating this Connecting loop multiple times and queuing up many web requests // before actually going online. while (State > APIState.Offline && State < APIState.Online) Thread.Sleep(500); diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 4fa1a81123..c069f82134 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(delegate { - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (menuMusic.Value) { track.Restart(); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 9d0a5cd05b..6984959e9c 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu private const float visualiser_rounds = 5; /// - /// How much should each bar go down each milisecond (based on a full bar). + /// How much should each bar go down each millisecond (based on a full bar). /// private const float decay_per_milisecond = 0.0024f; @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Menu private IShader shader; private Texture texture; - //Asuming the logo is a circle, we don't need a second dimension. + //Assuming the logo is a circle, we don't need a second dimension. private float size; private Color4 colour; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 0c5bf12bdb..d37cfe32db 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Menu } /// - /// Schedule a new extenral animation. Handled queueing and finishing previous animations in a sane way. + /// Schedule a new external animation. Handled queuing and finishing previous animations in a sane way. /// /// The animation to be performed /// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared. From 5695bb670e62db544b536942bfd52db23554b461 Mon Sep 17 00:00:00 2001 From: pi1024e Date: Fri, 30 Aug 2019 13:48:45 -0400 Subject: [PATCH 142/162] change back to queuing --- osu.Game/Online/API/APIAccess.cs | 2 +- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 0303293c41..d722c7a98a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -165,7 +165,7 @@ namespace osu.Game.Online.API } // The Success callback event is fired on the main thread, so we should wait for that to run before proceeding. - // Without this, we will end up circulating this Connecting loop multiple times and queuing up many web requests + // Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests // before actually going online. while (State > APIState.Offline && State < APIState.Online) Thread.Sleep(500); diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index d37cfe32db..534400e720 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -229,7 +229,7 @@ namespace osu.Game.Screens.Menu } /// - /// Schedule a new external animation. Handled queuing and finishing previous animations in a sane way. + /// Schedule a new external animation. Handled queueing and finishing previous animations in a sane way. /// /// The animation to be performed /// If true, the new animation is delayed until all previous transforms finish. If false, existing transformed are cleared. From aff4dab9aa342955a44e53335c6899fed8fbe3b3 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 03:17:55 +0300 Subject: [PATCH 143/162] Ensure playing track of beatmap selected only if a track change occurred --- osu.Game/Screens/Select/SongSelect.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index edb0e6deb8..d0cb5986a8 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -414,7 +414,11 @@ namespace osu.Game.Screens.Select { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); + WorkingBeatmap previous = Beatmap.Value; + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous); + + if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track) + ensurePlayingSelected(); if (beatmap != null) { @@ -425,8 +429,6 @@ namespace osu.Game.Screens.Select } } - if (this.IsCurrentScreen()) - ensurePlayingSelected(); UpdateBeatmap(Beatmap.Value); } } From 7d955839be8fc02b466ab53000598e98c36afb9c Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 04:22:24 +0300 Subject: [PATCH 144/162] Instantly move rank graph tooltip --- .../Overlays/Profile/Header/Components/RankGraph.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 24ed0cc022..56405483af 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -283,18 +283,7 @@ namespace osu.Game.Overlays.Profile.Header.Components return true; } - private bool instantMove = true; - - public void Move(Vector2 pos) - { - if (instantMove) - { - Position = pos; - instantMove = false; - } - else - this.MoveTo(pos, 200, Easing.OutQuint); - } + public void Move(Vector2 pos) => Position = pos; protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); From a155814bc48b5547b63f50ea60a4ae67bc377bd8 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sun, 1 Sep 2019 06:07:25 +0300 Subject: [PATCH 145/162] Implement instant movement properly --- .../Profile/Header/Components/RankGraph.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 56405483af..c6d96c5917 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -283,9 +283,24 @@ namespace osu.Game.Overlays.Profile.Header.Components return true; } - public void Move(Vector2 pos) => Position = pos; + private bool instantMove = true; - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() + { + instantMove |= !IsPresent; + this.FadeIn(200, Easing.OutQuint); + } protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); } From d1eafafa51b6632c5facea6e9b952e29fdef3441 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Sep 2019 19:57:12 +0900 Subject: [PATCH 146/162] Allow searching channels by topics Closes #5939 --- osu.Game/Overlays/Chat/Selection/ChannelListItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 4d77e5f93d..cb0639d85d 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection private Color4 topicColour; private Color4 hoverColour; - public IEnumerable FilterTerms => new[] { channel.Name }; + public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic }; public bool MatchingFilter { From 7ca51d3866657302a2c30f90b5e7a757bdb3fe93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 11:20:50 +0900 Subject: [PATCH 147/162] Fix resume overlay being drawn below cursor Closes #5905. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 11 +++++++---- osu.Game/Screens/Play/Player.cs | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 021bd515b5..0ee9196fb8 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -215,10 +215,6 @@ namespace osu.Game.Rulesets.UI continueResume(); } - public ResumeOverlay ResumeOverlay { get; private set; } - - protected virtual ResumeOverlay CreateResumeOverlay() => null; - /// /// Creates and adds the visual representation of a to this . /// @@ -389,6 +385,13 @@ namespace osu.Game.Rulesets.UI /// public abstract GameplayCursorContainer Cursor { get; } + /// + /// An optional overlay used when resuming gameplay from a paused state. + /// + public ResumeOverlay ResumeOverlay { get; protected set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; + /// /// Sets a replay to be used, overriding local input. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index b487f3e61b..3f1603eabe 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -178,6 +178,7 @@ namespace osu.Game.Screens.Play }, // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) { HoldToQuit = From 2bd074883a834305efbf5a89610e2c442a08dc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 12:36:08 +0900 Subject: [PATCH 148/162] Fix OsuLegacySkin fallback logic being incorrect Fixes skin fonts not being applied. This is a temporary fix as configuration retrieval from skins will see a complete rewrite over the coming days. --- osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs index 904064e2f0..ea7257d258 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkin.cs @@ -115,7 +115,13 @@ namespace osu.Game.Rulesets.Osu.Skinning public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); public TValue GetValue(Func query) where TConfiguration : SkinConfiguration - => configuration.Value is TConfiguration conf ? query.Invoke(conf) : source.GetValue(query); + { + TValue val; + if (configuration.Value is TConfiguration conf && (val = query.Invoke(conf)) != null) + return val; + + return source.GetValue(query); + } private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; } From cad68bb82fce1bfdaed3ca85b092b39f484cb222 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 12:54:59 +0900 Subject: [PATCH 149/162] Add autoplay helper property --- osu.Game/Tests/Visual/PlayerTestScene.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 1ab20ecd48..599cb060b1 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets; @@ -40,6 +41,8 @@ namespace osu.Game.Tests.Visual protected virtual bool AllowFail => false; + protected virtual bool Autoplay => false; + private void loadPlayer() { var beatmap = CreateBeatmap(ruleset.RulesetInfo); @@ -53,6 +56,16 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } - protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + protected virtual Player CreatePlayer(Ruleset ruleset) + { + if (Autoplay) + { + var mod = ruleset.GetAutoplayMod(); + if (mod != null) + Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); + } + + return new TestPlayer(false, false); + } } } From 2945fef62d5afbd53ad3022bd9304d34d108fe0d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:04:30 +0900 Subject: [PATCH 150/162] Expose HasCompleted from ScoreProcessor --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index e47df6b473..3b7e457990 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether all s have been processed. /// - protected virtual bool HasCompleted => false; + public virtual bool HasCompleted => false; /// /// Whether this ScoreProcessor has already triggered the failed state. @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Scoring private const double combo_portion = 0.7; private const double max_score = 1000000; - protected sealed override bool HasCompleted => JudgedHits == MaxHits; + public sealed override bool HasCompleted => JudgedHits == MaxHits; protected int MaxHits { get; private set; } protected int JudgedHits { get; private set; } From fc668d8a74898586c8a774fd1bd50f36032c7746 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:24:39 +0900 Subject: [PATCH 151/162] Move autoplay mod to a less overridable location --- osu.Game/Tests/Visual/PlayerTestScene.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 599cb060b1..ccd996098c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -52,12 +52,6 @@ namespace osu.Game.Tests.Visual if (!AllowFail) Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; - Player = CreatePlayer(ruleset); - LoadScreen(Player); - } - - protected virtual Player CreatePlayer(Ruleset ruleset) - { if (Autoplay) { var mod = ruleset.GetAutoplayMod(); @@ -65,7 +59,10 @@ namespace osu.Game.Tests.Visual Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray(); } - return new TestPlayer(false, false); + Player = CreatePlayer(ruleset); + LoadScreen(Player); } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } From 5b685c4cd2929f759be82a973f3f187f1642d50f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:25:39 +0900 Subject: [PATCH 152/162] Fix swell ticks having non-zero time offsets --- .../TestSceneSwellJudgements.cs | 74 +++++++++++++++++++ .../Objects/Drawables/DrawableSwellTick.cs | 6 +- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs new file mode 100644 index 0000000000..f27e329e8e --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Judgements; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneSwellJudgements : PlayerTestScene + { + protected new TestPlayer Player => (TestPlayer)base.Player; + + public TestSceneSwellJudgements() + : base(new TaikoRuleset()) + { + } + + [Test] + public void TestZeroTickTimeOffsets() + { + AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted); + AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0)); + } + + protected override bool Autoplay => true; + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }, + HitObjects = + { + new Swell + { + StartTime = 1000, + Duration = 1000, + } + } + }; + + return beatmap; + } + + protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(); + + protected class TestPlayer : Player + { + public readonly List Results = new List(); + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public TestPlayer() + : base(false, false) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => Results.Add(r); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 8b27d78101..4833d420f7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -14,7 +14,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } - public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type); + public void TriggerResult(HitResult type) + { + HitObject.StartTime = Time.Current; + ApplyResult(r => r.Type = type); + } protected override void CheckForResult(bool userTriggered, double timeOffset) { From 1df422e59135964b243ac168ba15bf8fd843a722 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 13:30:08 +0900 Subject: [PATCH 153/162] Hide taiko swell ticks --- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 8b27d78101..c2e8c08e7e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables @@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { } + protected override void UpdateInitialTransforms() => this.FadeOut(); + public void TriggerResult(HitResult type) => ApplyResult(r => r.Type = type); protected override void CheckForResult(bool userTriggered, double timeOffset) From 6603cbd74d2a5d4d0450af0953ea39452844ec89 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 13:41:14 +0900 Subject: [PATCH 154/162] No language doesn't mean "Other" --- osu.Game/Overlays/BeatmapSet/Info.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 72db03a5a6..df077a65a9 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.BeatmapSet source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Other"; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Unspecified"; }; } From d4c12881f5fd4417d6d108da0719001619fd0893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 13:45:13 +0900 Subject: [PATCH 155/162] Remove unnecessary over-complication and fix transitions --- osu.Game/Overlays/BeatmapSet/Info.cs | 30 ++++++---------------------- 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index df077a65a9..16d6236051 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -83,24 +83,12 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, + Direction = FillDirection.Full, Children = new[] { source = new MetadataSection("Source"), - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Width = 0.5f, - FillMode = FillMode.Fit, - Children = new Drawable[] - { - genre = new MetadataSection("Genre"), - language = new MetadataSection("Language"), - } - }, + genre = new MetadataSection("Genre") { Width = 0.5f }, + language = new MetadataSection("Language") { Width = 0.5f }, tags = new MetadataSection("Tags"), }, }, @@ -132,8 +120,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Metadata.Source ?? string.Empty; tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? "Unspecified"; - language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? "Unspecified"; + genre.Text = b.NewValue?.OnlineInfo?.Genre?.Name ?? string.Empty; + language.Text = b.NewValue?.OnlineInfo?.Language?.Name ?? string.Empty; }; } @@ -154,7 +142,7 @@ namespace osu.Game.Overlays.BeatmapSet { if (string.IsNullOrEmpty(value)) { - this.FadeOut(transition_duration); + Hide(); return; } @@ -164,12 +152,6 @@ namespace osu.Game.Overlays.BeatmapSet } } - public Color4 TextColour - { - get => textFlow.Colour; - set => textFlow.Colour = value; - } - public MetadataSection(string title) { RelativeSizeAxes = Axes.X; From 8f8d35bd15f27f5613bfb9506c96266288081dee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 15:02:16 +0900 Subject: [PATCH 156/162] Delay initial hitobject state computation --- .../Objects/Drawables/DrawableHitObject.cs | 9 ++++++ .../Scrolling/ScrollingHitObjectContainer.cs | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4a6f261905..a24476418c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -278,6 +279,14 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + /// + /// Schedules an to this . + /// + /// + /// Only provided temporarily until hitobject pooling is implemented. + /// + protected internal new ScheduledDelegate Schedule(Action action) => base.Schedule(action); + private double? lifetimeStart; public override double LifetimeStart diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 1df8c8218f..107d55ff0d 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -86,13 +87,34 @@ namespace osu.Game.Rulesets.UI.Scrolling scrollingInfo.Algorithm.Reset(); foreach (var obj in Objects) + { + computeLifetimeStartRecursive(obj); computeInitialStateRecursive(obj); + } + initialStateCache.Validate(); } } - private void computeInitialStateRecursive(DrawableHitObject hitObject) + private void computeLifetimeStartRecursive(DrawableHitObject hitObject) { + hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); + + foreach (var obj in hitObject.NestedHitObjects) + computeLifetimeStartRecursive(obj); + } + + private readonly Dictionary hitObjectInitialStateCache = new Dictionary(); + + // Cant use AddOnce() since the delegate is re-constructed every invocation + private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => + { + if (!hitObjectInitialStateCache.TryGetValue(hitObject, out var cached)) + cached = hitObjectInitialStateCache[hitObject] = new Cached(); + + if (cached.IsValid) + return; + double endTime = hitObject.HitObject.StartTime; if (hitObject.HitObject is IHasEndTime e) @@ -113,7 +135,6 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - hitObject.LifetimeStart = scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, timeRange.Value); hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength); foreach (var obj in hitObject.NestedHitObjects) @@ -123,7 +144,9 @@ namespace osu.Game.Rulesets.UI.Scrolling // Nested hitobjects don't need to scroll, but they do need accurate positions updatePosition(obj, hitObject.HitObject.StartTime); } - } + + cached.Validate(); + }); protected override void UpdateAfterChildrenLife() { From d74e1b9b6473613784be40adcf3202ae3f3908f1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 2 Sep 2019 15:06:43 +0900 Subject: [PATCH 157/162] Remove from dictionary on Remove() --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 107d55ff0d..bd1f496dfa 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -51,8 +51,13 @@ namespace osu.Game.Rulesets.UI.Scrolling public override bool Remove(DrawableHitObject hitObject) { var result = base.Remove(hitObject); + if (result) + { initialStateCache.Invalidate(); + hitObjectInitialStateCache.Remove(hitObject); + } + return result; } From c06908adf7c653f98fddda7d08f55e50a290b642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:37:38 +0900 Subject: [PATCH 158/162] Fix spacing specifications --- .../Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 408468fa73..9b81e8c573 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Both, - Spacing = new Vector2(3), + Spacing = new Vector2(0, 3), }, date = new DrawableDate(historyItem.CreatedAt) { @@ -59,14 +59,15 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu if (!string.IsNullOrEmpty(prefix)) { linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@"{Math.Abs(historyItem.Amount)} kudosu", t => + linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu ", t => { t.Font = t.Font.With(italics: true); t.Colour = colours.Blue; }); } - linkFlowContainer.AddLinks(formattedSource.Text + " ", formattedSource.Links); + linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links); + linkFlowContainer.AddText(" "); linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } From da4507037333adcda31a9512f101b667cddb77ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:44:21 +0900 Subject: [PATCH 159/162] Group common prefixes together --- .../Kudosu/DrawableKudosuHistoryItem.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 9b81e8c573..ff64ea5648 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -120,29 +120,17 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - return @"Received"; - case KudosuAction.Give: + case KudosuAction.AllowKudosuGive: + case KudosuAction.RestoreGive: + case KudosuAction.RecalculateGive: return @"Received"; - case KudosuAction.VoteReset: - return @"Lost"; - case KudosuAction.DenyKudosuReset: return @"Denied"; - case KudosuAction.AllowKudosuGive: - return @"Received"; - case KudosuAction.DeleteReset: - return @"Lost"; - - case KudosuAction.RestoreGive: - return @"Received"; - - case KudosuAction.RecalculateGive: - return @"Received"; - + case KudosuAction.VoteReset: case KudosuAction.RecalculateReset: return @"Lost"; From 3d551b08a96a1d9be3130a9b5bf27613fced44bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:57:23 +0900 Subject: [PATCH 160/162] Rename legacy actions --- .../Visual/Online/TestSceneKudosuHistory.cs | 6 +++--- .../API/Requests/GetUserKudosuHistoryRequest.cs | 16 ++++++++-------- .../Sections/Kudosu/DrawableKudosuHistoryItem.cs | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 8badfeaa23..a4f3bf65e6 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 5, CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), - Action = KudosuAction.Give, + Action = KudosuAction.ForumGive, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 2", @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 8, CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), - Action = KudosuAction.Reset, + Action = KudosuAction.ForumReset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 3", @@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Online { Amount = 7, CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), - Action = KudosuAction.Revoke, + Action = KudosuAction.ForumRevoke, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 4", diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index dd6f2ccf22..32aa0c15fa 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -21,16 +21,16 @@ namespace osu.Game.Online.API.Requests public enum KudosuAction { - Give, - VoteGive, - Reset, - VoteReset, - DenyKudosuReset, - Revoke, AllowKudosuGive, DeleteReset, - RestoreGive, + DenyKudosuReset, + ForumGive, + ForumReset, + ForumRevoke, RecalculateGive, - RecalculateReset + RecalculateReset, + RestoreGive, + VoteGive, + VoteReset, } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index ff64ea5648..5229269def 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -80,10 +80,10 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.VoteGive: return @"from obtaining votes in modding post of"; - case KudosuAction.Give: + case KudosuAction.ForumGive: return $@"from {userLink()} for a post at"; - case KudosuAction.Reset: + case KudosuAction.ForumReset: return $@"Kudosu reset by {userLink()} for the post"; case KudosuAction.VoteReset: @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu case KudosuAction.DenyKudosuReset: return @"from modding post"; - case KudosuAction.Revoke: + case KudosuAction.ForumRevoke: return $@"Denied kudosu by {userLink()} for the post"; case KudosuAction.AllowKudosuGive: @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu switch (historyItem.Action) { case KudosuAction.VoteGive: - case KudosuAction.Give: + case KudosuAction.ForumGive: case KudosuAction.AllowKudosuGive: case KudosuAction.RestoreGive: case KudosuAction.RecalculateGive: From 0985b1679f86de6a4a96d34edb0e84cb30a55dac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 15:57:55 +0900 Subject: [PATCH 161/162] Move enum to response class --- .../Visual/Online/TestSceneKudosuHistory.cs | 1 - .../API/Requests/GetUserKudosuHistoryRequest.cs | 15 --------------- .../API/Requests/Responses/APIKudosuHistory.cs | 15 +++++++++++++++ .../Sections/Kudosu/DrawableKudosuHistoryItem.cs | 1 - 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index a4f3bf65e6..84152e40fa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -10,7 +10,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.API.Requests; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Tests.Visual.Online diff --git a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs index 32aa0c15fa..e90e297672 100644 --- a/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserKudosuHistoryRequest.cs @@ -18,19 +18,4 @@ namespace osu.Game.Online.API.Requests protected override string Target => $"users/{userId}/kudosu"; } - - public enum KudosuAction - { - AllowKudosuGive, - DeleteReset, - DenyKudosuReset, - ForumGive, - ForumReset, - ForumRevoke, - RecalculateGive, - RecalculateReset, - RestoreGive, - VoteGive, - VoteReset, - } } diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index f2297f7a10..25b11a6cf9 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -53,4 +53,19 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; } + + public enum KudosuAction + { + AllowKudosuGive, + DeleteReset, + DenyKudosuReset, + ForumGive, + ForumReset, + ForumRevoke, + RecalculateGive, + RecalculateReset, + RestoreGive, + VoteGive, + VoteReset, + } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 5229269def..6efe9825ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using System; using osuTK; From d21d68b36c4271d611db888c1917e09ee93b95ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Sep 2019 16:34:11 +0900 Subject: [PATCH 162/162] Refactor to match web implementation 1:1 --- .../Visual/Online/TestSceneKudosuHistory.cs | 33 +++-- .../Requests/Responses/APIKudosuHistory.cs | 44 ++++-- .../Kudosu/DrawableKudosuHistoryItem.cs | 138 +++++++++--------- 3 files changed, 122 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index 84152e40fa..325d657f0e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -58,7 +58,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 10, CreatedAt = new DateTimeOffset(new DateTime(2011, 11, 11)), - Action = KudosuAction.DenyKudosuReset, + Source = KudosuSource.DenyKudosu, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 1", @@ -74,7 +75,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 5, CreatedAt = new DateTimeOffset(new DateTime(2012, 10, 11)), - Action = KudosuAction.ForumGive, + Source = KudosuSource.Forum, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 2", @@ -90,7 +92,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 8, CreatedAt = new DateTimeOffset(new DateTime(2013, 9, 11)), - Action = KudosuAction.ForumReset, + Source = KudosuSource.Forum, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 3", @@ -106,7 +109,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 7, CreatedAt = new DateTimeOffset(new DateTime(2014, 8, 11)), - Action = KudosuAction.ForumRevoke, + Source = KudosuSource.Forum, + Action = KudosuAction.Revoke, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 4", @@ -122,7 +126,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 100, CreatedAt = new DateTimeOffset(new DateTime(2015, 7, 11)), - Action = KudosuAction.VoteGive, + Source = KudosuSource.Vote, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 5", @@ -138,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 20, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.VoteReset, + Source = KudosuSource.Vote, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 6", @@ -154,7 +160,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 11, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.AllowKudosuGive, + Source = KudosuSource.AllowKudosu, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 7", @@ -170,7 +177,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 24, CreatedAt = new DateTimeOffset(new DateTime(2014, 6, 11)), - Action = KudosuAction.DeleteReset, + Source = KudosuSource.Delete, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 8", @@ -186,7 +194,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 12, CreatedAt = new DateTimeOffset(new DateTime(2016, 6, 11)), - Action = KudosuAction.RestoreGive, + Source = KudosuSource.Restore, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 9", @@ -202,7 +211,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 2, CreatedAt = new DateTimeOffset(new DateTime(2012, 6, 11)), - Action = KudosuAction.RecalculateGive, + Source = KudosuSource.Recalculate, + Action = KudosuAction.Give, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 10", @@ -218,7 +228,8 @@ namespace osu.Game.Tests.Visual.Online { Amount = 32, CreatedAt = new DateTimeOffset(new DateTime(2019, 8, 11)), - Action = KudosuAction.RecalculateReset, + Source = KudosuSource.Recalculate, + Action = KudosuAction.Reset, Post = new APIKudosuHistory.ModdingPost { Title = @"Random post 11", diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 25b11a6cf9..d596ddc560 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; +using System.Linq; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses @@ -39,33 +39,45 @@ namespace osu.Game.Online.API.Requests.Responses public string Username; } + public KudosuSource Source; + + public KudosuAction Action; + [JsonProperty("action")] private string action { set { - //We will receive something like "event.action" or just "action" - string parsed = value.Contains(".") ? value.Split('.')[0].Pascalize() + value.Split('.')[1].Pascalize() : value.Pascalize(); + // incoming action may contain a prefix. if it doesn't, it's a legacy forum event. - Action = (KudosuAction)Enum.Parse(typeof(KudosuAction), parsed); + string[] split = value.Split('.'); + + if (split.Length > 1) + Enum.TryParse(split.First().Replace("_", ""), true, out Source); + else + Source = KudosuSource.Forum; + + Enum.TryParse(split.Last(), true, out Action); } } + } - public KudosuAction Action; + public enum KudosuSource + { + Unknown, + AllowKudosu, + Delete, + DenyKudosu, + Forum, + Recalculate, + Restore, + Vote } public enum KudosuAction { - AllowKudosuGive, - DeleteReset, - DenyKudosuReset, - ForumGive, - ForumReset, - ForumRevoke, - RecalculateGive, - RecalculateReset, - RestoreGive, - VoteGive, - VoteReset, + Give, + Reset, + Revoke, } } diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 6efe9825ec..d0cfe9fa54 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -51,91 +51,97 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private void load() { date.Colour = colours.GreySeafoamLighter; - - string prefix = getPrefix(historyItem); - var formattedSource = MessageFormatter.FormatText(getSource(historyItem)); - - if (!string.IsNullOrEmpty(prefix)) - { - linkFlowContainer.AddText(prefix); - linkFlowContainer.AddText($@" {Math.Abs(historyItem.Amount)} kudosu ", t => - { - t.Font = t.Font.With(italics: true); - t.Colour = colours.Blue; - }); - } - + var formattedSource = MessageFormatter.FormatText(getString(historyItem)); linkFlowContainer.AddLinks(formattedSource.Text, formattedSource.Links); - linkFlowContainer.AddText(" "); - linkFlowContainer.AddLink(historyItem.Post.Title, historyItem.Post.Url); } - private string getSource(APIKudosuHistory historyItem) + private string getString(APIKudosuHistory item) { - string userLink() => $"[{historyItem.Giver?.Url} {historyItem.Giver?.Username}]"; + string amount = $"{Math.Abs(item.Amount)} kudosu"; + string post = $"[{item.Post.Title}]({item.Post.Url})"; - switch (historyItem.Action) + switch (item.Source) { - case KudosuAction.VoteGive: - return @"from obtaining votes in modding post of"; + case KudosuSource.AllowKudosu: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from kudosu deny repeal of modding post {post}"; + } - case KudosuAction.ForumGive: - return $@"from {userLink()} for a post at"; + break; - case KudosuAction.ForumReset: - return $@"Kudosu reset by {userLink()} for the post"; + case KudosuSource.DenyKudosu: + switch (item.Action) + { + case KudosuAction.Reset: + return $"Denied {amount} from modding post {post}"; + } - case KudosuAction.VoteReset: - return @"from losing votes in modding post of"; + break; - case KudosuAction.DenyKudosuReset: - return @"from modding post"; + case KudosuSource.Delete: + switch (item.Action) + { + case KudosuAction.Reset: + return $"Lost {amount} from modding post deletion of {post}"; + } - case KudosuAction.ForumRevoke: - return $@"Denied kudosu by {userLink()} for the post"; + break; - case KudosuAction.AllowKudosuGive: - return @"from kudosu deny repeal of modding post"; + case KudosuSource.Restore: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from modding post restoration of {post}"; + } - case KudosuAction.DeleteReset: - return @"from modding post deletion of"; + break; - case KudosuAction.RestoreGive: - return @"from modding post restoration of"; + case KudosuSource.Vote: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from obtaining votes in modding post of {post}"; - case KudosuAction.RecalculateGive: - return @"from votes recalculation in modding post of"; + case KudosuAction.Reset: + return $"Lost {amount} from losing votes in modding post of {post}"; + } - case KudosuAction.RecalculateReset: - return @"from votes recalculation in modding post of"; + break; - default: - return @"from unknown event"; + case KudosuSource.Recalculate: + switch (item.Action) + { + case KudosuAction.Give: + return $"Received {amount} from votes recalculation in modding post of {post}"; + + case KudosuAction.Reset: + return $"Lost {amount} from votes recalculation in modding post of {post}"; + } + + break; + + case KudosuSource.Forum: + + string giver = $"[{item.Giver?.Username}]({item.Giver?.Url})"; + + switch (historyItem.Action) + { + case KudosuAction.Give: + return $"Received {amount} from {giver} for a post at {post}"; + + case KudosuAction.Reset: + return $"Kudosu reset by {giver} for the post {post}"; + + case KudosuAction.Revoke: + return $"Denied kudosu by {giver} for the post {post}"; + } + + break; } - } - private string getPrefix(APIKudosuHistory historyItem) - { - switch (historyItem.Action) - { - case KudosuAction.VoteGive: - case KudosuAction.ForumGive: - case KudosuAction.AllowKudosuGive: - case KudosuAction.RestoreGive: - case KudosuAction.RecalculateGive: - return @"Received"; - - case KudosuAction.DenyKudosuReset: - return @"Denied"; - - case KudosuAction.DeleteReset: - case KudosuAction.VoteReset: - case KudosuAction.RecalculateReset: - return @"Lost"; - - default: - return null; - } + return $"Unknown event ({amount} change)"; } } }