From d9633fee64a270364f618fc415d5d48d93a808e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:47:09 +0900 Subject: [PATCH 01/47] Rename request --- .../Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs | 2 +- ...PlaylistScoresRequest.cs => IndexPlaylistScoresRequest.cs} | 4 ++-- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Online/Multiplayer/{GetRoomPlaylistScoresRequest.cs => IndexPlaylistScoresRequest.cs} (82%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 0023866124..37d31264b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { switch (request) { - case GetRoomPlaylistScoresRequest r: + case IndexPlaylistScoresRequest r: if (delay == 0) success(); else diff --git a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs similarity index 82% rename from osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs rename to osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 3d3bd20ff3..c435dc6030 100644 --- a/osu.Game/Online/Multiplayer/GetRoomPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -7,12 +7,12 @@ using osu.Game.Online.API; namespace osu.Game.Online.Multiplayer { - public class GetRoomPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; - public GetRoomPlaylistScoresRequest(int roomId, int playlistItemId) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { this.roomId = roomId; this.playlistItemId = playlistItemId; diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index f367d44347..b90c7252c4 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); + var req = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); req.Success += r => { From ec33a6ea8791b6878912b26dd9209063a45f5719 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:47:40 +0900 Subject: [PATCH 02/47] Add additional responses --- .../Online/Multiplayer/MultiplayerScores.cs | 39 +++++++++++++++++++ .../Multiplayer/MultiplayerScoresAround.cs | 25 ++++++++++++ .../Multiplayer/MultiplayerScoresSort.cs | 14 +++++++ 3 files changed, 78 insertions(+) create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScores.cs create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs create mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs new file mode 100644 index 0000000000..f944a8999c --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -0,0 +1,39 @@ +// 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 Newtonsoft.Json; +using osu.Game.Online.API.Requests; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// An object which contains scores and related data for fetching next pages. + /// + public class MultiplayerScores + { + /// + /// To be used for fetching the next page. + /// + [JsonProperty("cursor")] + public Cursor Cursor { get; set; } + + /// + /// The scores. + /// + [JsonProperty("scores")] + public List Scores { get; set; } + + /// + /// The total scores in the playlist item. Only provided via . + /// + [JsonProperty("total")] + public int? TotalScores { get; set; } + + /// + /// The user's score, if any. Only provided via . + /// + [JsonProperty("user_score")] + public MultiplayerScore UserScore { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs new file mode 100644 index 0000000000..e83cc1b753 --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// An object which stores scores higher and lower than the user's score. + /// + public class MultiplayerScoresAround + { + /// + /// Scores sorted "higher" than the user's score, depending on the sorting order. + /// + [JsonProperty("higher")] + public MultiplayerScores Higher { get; set; } + + /// + /// Scores sorted "lower" than the user's score, depending on the sorting order. + /// + [JsonProperty("lower")] + public MultiplayerScores Lower { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs new file mode 100644 index 0000000000..decb1c4dfe --- /dev/null +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs @@ -0,0 +1,14 @@ +// 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.Online.Multiplayer +{ + /// + /// Sorting option for indexing multiplayer scores. + /// + public enum MultiplayerScoresSort + { + Ascending, + Descending + } +} From 634efe31f843f53a8a86ae01319705b748398449 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:51:54 +0900 Subject: [PATCH 03/47] Inherit ResponseWithCursor --- osu.Game/Online/Multiplayer/MultiplayerScores.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index f944a8999c..6f74fc8984 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -10,14 +10,8 @@ namespace osu.Game.Online.Multiplayer /// /// An object which contains scores and related data for fetching next pages. /// - public class MultiplayerScores + public class MultiplayerScores : ResponseWithCursor { - /// - /// To be used for fetching the next page. - /// - [JsonProperty("cursor")] - public Cursor Cursor { get; set; } - /// /// The scores. /// From c75955e3819f5b7c8ec3a77af1bce267f2008633 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:52:25 +0900 Subject: [PATCH 04/47] Add responses to MultiplayerScore --- osu.Game/Online/Multiplayer/MultiplayerScore.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScore.cs b/osu.Game/Online/Multiplayer/MultiplayerScore.cs index 3bbf19b11f..1793ba72ef 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScore.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScore.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.API; @@ -47,6 +48,19 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("ended_at")] public DateTimeOffset EndedAt { get; set; } + /// + /// The position of this score, starting at 1. + /// + [JsonProperty("position")] + public int? Position { get; set; } + + /// + /// Any scores in the room around this score. + /// + [JsonProperty("scores_around")] + [CanBeNull] + public MultiplayerScoresAround ScoresAround { get; set; } + public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem) { var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); From 334fb7d4753386c6d534efe27e80e421a3b8a94f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 18:54:41 +0900 Subject: [PATCH 05/47] Add additional params to index request --- .../Multiplayer/IndexPlaylistScoresRequest.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index c435dc6030..b43614bf6c 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -3,19 +3,49 @@ using System.Collections.Generic; using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Extensions; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Online.Multiplayer { + /// + /// Returns a list of scores for the specified playlist item. + /// public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; + private readonly Cursor cursor; + private readonly MultiplayerScoresSort? sort; - public IndexPlaylistScoresRequest(int roomId, int playlistItemId) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId, Cursor cursor = null, MultiplayerScoresSort? sort = null) { this.roomId = roomId; this.playlistItemId = playlistItemId; + this.cursor = cursor; + this.sort = sort; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.AddCursor(cursor); + + switch (sort) + { + case MultiplayerScoresSort.Ascending: + req.AddParameter("sort", "scores_asc"); + break; + + case MultiplayerScoresSort.Descending: + req.AddParameter("sort", "scores_desc"); + break; + } + + return req; } protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; From f8401a76a25a59706226eee625dc479d13116c10 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 19:40:00 +0900 Subject: [PATCH 06/47] Use show/index requests in results screen --- .../ShowPlaylistUserScoreRequest.cs | 23 +++++++ .../Multi/Ranking/TimeshiftResultsScreen.cs | 63 +++++++++++++++++-- 2 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.cs new file mode 100644 index 0000000000..936b8bbe89 --- /dev/null +++ b/osu.Game/Online/Multiplayer/ShowPlaylistUserScoreRequest.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.Game.Online.API; + +namespace osu.Game.Online.Multiplayer +{ + public class ShowPlaylistUserScoreRequest : APIRequest + { + private readonly int roomId; + private readonly int playlistItemId; + private readonly long userId; + + public ShowPlaylistUserScoreRequest(int roomId, int playlistItemId, long userId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.userId = userId; + } + + protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; + } +} diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index b90c7252c4..47aab02b1a 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -21,6 +22,11 @@ namespace osu.Game.Screens.Multi.Ranking private readonly PlaylistItem playlistItem; private LoadingSpinner loadingLayer; + private Cursor higherScoresCursor; + private Cursor lowerScoresCursor; + + [Resolved] + private IAPIProvider api { get; set; } public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) : base(score, allowRetry) @@ -44,17 +50,62 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - var req = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); + // This performs two requests: + // 1. A request to show the user's score. + // 2. If (1) fails, a request to index the room. - req.Success += r => + var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); + + userScoreReq.Success += userScore => { - scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); - loadingLayer.Hide(); + var allScores = new List { userScore }; + + if (userScore.ScoresAround?.Higher != null) + { + allScores.AddRange(userScore.ScoresAround.Higher.Scores); + higherScoresCursor = userScore.ScoresAround.Higher.Cursor; + } + + if (userScore.ScoresAround?.Lower != null) + { + allScores.AddRange(userScore.ScoresAround.Lower.Scores); + lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; + } + + success(allScores); }; - req.Failure += _ => loadingLayer.Hide(); + userScoreReq.Failure += _ => + { + // Fallback to a normal index. + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); + indexReq.Success += r => success(r.Scores); + indexReq.Failure += __ => loadingLayer.Hide(); + api.Queue(indexReq); + }; - return req; + return userScoreReq; + + void success(List scores) + { + var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) + { + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); + } + + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + scoresCallback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + + loadingLayer.Hide(); + } } } } From 568fb51ce239c8f2fbe26ab63d83ea1a508bb65e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:42 +0900 Subject: [PATCH 07/47] Remove RoomPlaylistScores intermediate class --- .../Multiplayer/TestSceneTimeshiftResultsScreen.cs | 2 +- .../Online/Multiplayer/IndexPlaylistScoresRequest.cs | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 37d31264b6..44ca676c4f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer void success() { - r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores }); + r.TriggerSuccess(new MultiplayerScores { Scores = roomScores }); roomsReceived = true; } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index b43614bf6c..d23208d338 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -1,8 +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 System.Collections.Generic; -using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; @@ -13,7 +11,7 @@ namespace osu.Game.Online.Multiplayer /// /// Returns a list of scores for the specified playlist item. /// - public class IndexPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; @@ -50,10 +48,4 @@ namespace osu.Game.Online.Multiplayer protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; } - - public class RoomPlaylistScores - { - [JsonProperty("scores")] - public List Scores { get; set; } - } } From b7790de66fbc2d11126fb0c0dadf17090c647aa0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:48 +0900 Subject: [PATCH 08/47] Fix incorrect sort param --- osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index d23208d338..7273c0eea6 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -35,11 +35,11 @@ namespace osu.Game.Online.Multiplayer switch (sort) { case MultiplayerScoresSort.Ascending: - req.AddParameter("sort", "scores_asc"); + req.AddParameter("sort", "score_asc"); break; case MultiplayerScoresSort.Descending: - req.AddParameter("sort", "scores_desc"); + req.AddParameter("sort", "score_desc"); break; } From 46ea775cfb36d15a3dc7a13098bd62c18cbb7987 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 22 Jul 2020 20:24:55 +0900 Subject: [PATCH 09/47] Implement paging --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 84 +++++++++++++++---- osu.Game/Screens/Ranking/ResultsScreen.cs | 41 +++++++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 + 3 files changed, 109 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 47aab02b1a..75a61b92ee 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -72,40 +73,93 @@ namespace osu.Game.Screens.Multi.Ranking lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; } - success(allScores); + performSuccessCallback(scoresCallback, allScores); }; userScoreReq.Failure += _ => { // Fallback to a normal index. var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); - indexReq.Success += r => success(r.Scores); + + indexReq.Success += r => + { + performSuccessCallback(scoresCallback, r.Scores); + lowerScoresCursor = r.Cursor; + }; + indexReq.Failure += __ => loadingLayer.Hide(); + api.Queue(indexReq); }; return userScoreReq; + } - void success(List scores) + protected override APIRequest FetchNextPage(int direction, Action> scoresCallback) + { + Debug.Assert(direction == 1 || direction == -1); + + Cursor cursor; + MultiplayerScoresSort sort; + + switch (direction) { - var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + case -1: + cursor = higherScoresCursor; + sort = MultiplayerScoresSort.Ascending; + break; - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) + default: + cursor = lowerScoresCursor; + sort = MultiplayerScoresSort.Descending; + break; + } + + if (cursor == null) + return null; + + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, cursor, sort); + + indexReq.Success += r => + { + switch (direction) { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); + case -1: + higherScoresCursor = r.Cursor; + break; + + default: + lowerScoresCursor = r.Cursor; + break; } - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - scoresCallback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + performSuccessCallback(scoresCallback, r.Scores); + }; - loadingLayer.Hide(); + indexReq.Failure += _ => loadingLayer.Hide(); + + return indexReq; + } + + private void performSuccessCallback(Action> callback, List scores) + { + var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); + + // Select a score if we don't already have one selected. + // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). + if (SelectedScore.Value == null) + { + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + }); } + + // Invoke callback to add the scores. Exclude the user's current score which was added previously. + callback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + + loadingLayer.Hide(); } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 44458d8c8e..c5512822b2 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -164,11 +164,7 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = FetchScores(scores => Schedule(() => - { - foreach (var s in scores) - addScore(s); - })); + var req = FetchScores(fetchScoresCallback); if (req != null) api.Queue(req); @@ -176,6 +172,29 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } + private APIRequest nextPageRequest; + + protected override void Update() + { + base.Update(); + + if (hasAnyScores && nextPageRequest == null) + { + if (scorePanelList.IsScrolledToStart) + nextPageRequest = FetchNextPage(-1, fetchScoresCallback); + else if (scorePanelList.IsScrolledToEnd) + nextPageRequest = FetchNextPage(1, fetchScoresCallback); + + if (nextPageRequest != null) + { + nextPageRequest.Success += () => nextPageRequest = null; + nextPageRequest.Failure += _ => nextPageRequest = null; + + api.Queue(nextPageRequest); + } + } + } + /// /// Performs a fetch/refresh of scores to be displayed. /// @@ -183,6 +202,18 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + + private bool hasAnyScores; + + private void fetchScoresCallback(IEnumerable scores) => Schedule(() => + { + foreach (var s in scores) + addScore(s); + + hasAnyScores = true; + }); + public override void OnEntering(IScreen last) { base.OnEntering(last); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0f8bc82ac0..aba8314732 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,6 +26,10 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= 100; + + public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(100); + /// /// An action to be invoked if a is clicked while in an expanded state. /// From 54d2f2c8cd2e837d7b0e046f4bf8df91317eb802 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:34:09 +0900 Subject: [PATCH 10/47] Delay loading of cover backgrounds in score panels --- .../Ranking/Contracted/ContractedPanelMiddleContent.cs | 5 ++++- osu.Game/Screens/Ranking/ScorePanel.cs | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 8cd0e7025e..3ffb205d09 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -70,11 +70,14 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("444") }, - new UserCoverBackground + new DelayedLoadUnloadWrapper(() => new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) + }, 300, 5000) + { + RelativeSizeAxes = Axes.Both }, new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 5da432d5b2..7ac98604f4 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -146,11 +146,14 @@ namespace osu.Game.Screens.Ranking Children = new[] { middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }, - new UserCoverBackground + new DelayedLoadUnloadWrapper(() => new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = Score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) + }, 300, 5000) + { + RelativeSizeAxes = Axes.Both }, } }, From 42e88c53d75a03f37fe8699ca2512c0477fb8c75 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:50:55 +0900 Subject: [PATCH 11/47] Embed behaviour into UserCoverBackground --- osu.Game/Overlays/Profile/ProfileHeader.cs | 7 ++++++- .../Contracted/ContractedPanelMiddleContent.cs | 5 +---- osu.Game/Screens/Ranking/ScorePanel.cs | 7 ++----- osu.Game/Users/UserCoverBackground.cs | 11 +++++++++++ osu.Game/Users/UserPanel.cs | 10 ++-------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2e5f1071f2..55474c9d3e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Profile Masking = true, Children = new Drawable[] { - coverContainer = new UserCoverBackground + coverContainer = new ProfileCoverBackground { RelativeSizeAxes = Axes.Both, }, @@ -100,5 +100,10 @@ namespace osu.Game.Overlays.Profile IconTexture = "Icons/profile"; } } + + private class ProfileCoverBackground : UserCoverBackground + { + protected override double LoadDelay => 0; + } } } diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 3ffb205d09..8cd0e7025e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -70,14 +70,11 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("444") }, - new DelayedLoadUnloadWrapper(() => new UserCoverBackground + new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) - }, 300, 5000) - { - RelativeSizeAxes = Axes.Both }, new FillFlowContainer { diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 7ac98604f4..24d193e9a7 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -146,15 +146,12 @@ namespace osu.Game.Screens.Ranking Children = new[] { middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both }, - new DelayedLoadUnloadWrapper(() => new UserCoverBackground + new UserCoverBackground { RelativeSizeAxes = Axes.Both, User = Score.User, Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) - }, 300, 5000) - { - RelativeSizeAxes = Axes.Both - }, + } } }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 748d9bd939..34bbf6892e 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.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.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -23,6 +24,16 @@ namespace osu.Game.Users protected override Drawable CreateDrawable(User user) => new Cover(user); + protected override double LoadDelay => 300; + + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 5000; + + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + [LongRunningLoad] private class Cover : CompositeDrawable { diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 94c0c31cfc..57a87a713d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -25,7 +24,7 @@ namespace osu.Game.Users protected Action ViewProfile { get; private set; } - protected DelayedLoadUnloadWrapper Background { get; private set; } + protected Drawable Background { get; private set; } protected UserPanel(User user) { @@ -56,17 +55,12 @@ namespace osu.Game.Users RelativeSizeAxes = Axes.Both, Colour = ColourProvider?.Background5 ?? Colours.Gray1 }, - Background = new DelayedLoadUnloadWrapper(() => new UserCoverBackground + Background = new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, User = User, - }, 300, 5000) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, }, CreateLayout() }); From 9f6446d83685fb4d6052930129ae7b68f7781f50 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 20:58:13 +0900 Subject: [PATCH 12/47] Add xmldocs + refactoring --- osu.Game/Screens/Ranking/ResultsScreen.cs | 17 +++++++++++------ osu.Game/Screens/Ranking/ScorePanelList.cs | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c5512822b2..254ab76f5b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -50,6 +50,9 @@ namespace osu.Game.Screens.Ranking private ScorePanelList scorePanelList; private Container detachedPanelContainer; + private bool fetchedInitialScores; + private APIRequest nextPageRequest; + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; @@ -172,13 +175,11 @@ namespace osu.Game.Screens.Ranking statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } - private APIRequest nextPageRequest; - protected override void Update() { base.Update(); - if (hasAnyScores && nextPageRequest == null) + if (fetchedInitialScores && nextPageRequest == null) { if (scorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); @@ -202,16 +203,20 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + /// + /// Performs a fetch of the next page of scores. This is invoked every frame until a non-null is returned. + /// + /// The fetch direction. -1 to fetch scores greater than the current start of the list, and 1 to fetch scores lower than the current end of the list. + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; - private bool hasAnyScores; - private void fetchScoresCallback(IEnumerable scores) => Schedule(() => { foreach (var s in scores) addScore(s); - hasAnyScores = true; + fetchedInitialScores = true; }); public override void OnEntering(IScreen last) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index aba8314732..b2e1e91831 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,9 +26,20 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; - public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= 100; + /// + /// Minimum distance from either end point of the list that the list can be considered scrolled to the end point. + /// + private const float scroll_endpoint_distance = 100; - public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(100); + /// + /// Whether this can be scrolled and is currently scrolled to the start. + /// + public bool IsScrolledToStart => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.Current <= scroll_endpoint_distance; + + /// + /// Whether this can be scrolled and is currently scrolled to the end. + /// + public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); /// /// An action to be invoked if a is clicked while in an expanded state. From db91d1de50e9ab92f7df1ec45abbea93696766af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 21:40:11 +0900 Subject: [PATCH 13/47] Use response params in next page request --- .../Multiplayer/IndexPlaylistScoresRequest.cs | 25 +++++----- .../Online/Multiplayer/IndexScoresParams.cs | 20 ++++++++ .../Online/Multiplayer/MultiplayerScores.cs | 6 +++ .../Multi/Ranking/TimeshiftResultsScreen.cs | 49 ++++++------------- 4 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/IndexScoresParams.cs diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 7273c0eea6..67793df344 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.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 JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; using osu.Game.Online.API; @@ -16,31 +17,31 @@ namespace osu.Game.Online.Multiplayer private readonly int roomId; private readonly int playlistItemId; private readonly Cursor cursor; - private readonly MultiplayerScoresSort? sort; + private readonly IndexScoresParams indexParams; - public IndexPlaylistScoresRequest(int roomId, int playlistItemId, Cursor cursor = null, MultiplayerScoresSort? sort = null) + public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { this.roomId = roomId; this.playlistItemId = playlistItemId; + } + + public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) + : this(roomId, playlistItemId) + { this.cursor = cursor; - this.sort = sort; + this.indexParams = indexParams; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - req.AddCursor(cursor); - - switch (sort) + if (cursor != null) { - case MultiplayerScoresSort.Ascending: - req.AddParameter("sort", "score_asc"); - break; + req.AddCursor(cursor); - case MultiplayerScoresSort.Descending: - req.AddParameter("sort", "score_desc"); - break; + foreach (var (key, value) in indexParams.Properties) + req.AddParameter(key, value.ToString()); } return req; diff --git a/osu.Game/Online/Multiplayer/IndexScoresParams.cs b/osu.Game/Online/Multiplayer/IndexScoresParams.cs new file mode 100644 index 0000000000..8160dfefaf --- /dev/null +++ b/osu.Game/Online/Multiplayer/IndexScoresParams.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.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A collection of parameters which should be passed to the index endpoint to fetch the next page. + /// + public class IndexScoresParams + { + [UsedImplicitly] + [JsonExtensionData] + public IDictionary Properties; + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 6f74fc8984..2d0f98e032 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -29,5 +29,11 @@ namespace osu.Game.Online.Multiplayer /// [JsonProperty("user_score")] public MultiplayerScore UserScore { get; set; } + + /// + /// The parameters to be used to fetch the next page. + /// + [JsonProperty("params")] + public IndexScoresParams Params { get; set; } } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 75a61b92ee..648bee385c 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -23,8 +22,8 @@ namespace osu.Game.Screens.Multi.Ranking private readonly PlaylistItem playlistItem; private LoadingSpinner loadingLayer; - private Cursor higherScoresCursor; - private Cursor lowerScoresCursor; + private MultiplayerScores higherScores; + private MultiplayerScores lowerScores; [Resolved] private IAPIProvider api { get; set; } @@ -52,8 +51,8 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { // This performs two requests: - // 1. A request to show the user's score. - // 2. If (1) fails, a request to index the room. + // 1. A request to show the user's score (and scores around). + // 2. If that fails, a request to index the room starting from the highest score. var userScoreReq = new ShowPlaylistUserScoreRequest(roomId, playlistItem.ID, api.LocalUser.Value.Id); @@ -64,13 +63,13 @@ namespace osu.Game.Screens.Multi.Ranking if (userScore.ScoresAround?.Higher != null) { allScores.AddRange(userScore.ScoresAround.Higher.Scores); - higherScoresCursor = userScore.ScoresAround.Higher.Cursor; + higherScores = userScore.ScoresAround.Higher; } if (userScore.ScoresAround?.Lower != null) { allScores.AddRange(userScore.ScoresAround.Lower.Scores); - lowerScoresCursor = userScore.ScoresAround.Lower.Cursor; + lowerScores = userScore.ScoresAround.Lower; } performSuccessCallback(scoresCallback, allScores); @@ -84,7 +83,7 @@ namespace osu.Game.Screens.Multi.Ranking indexReq.Success += r => { performSuccessCallback(scoresCallback, r.Scores); - lowerScoresCursor = r.Cursor; + lowerScores = r; }; indexReq.Failure += __ => loadingLayer.Hide(); @@ -99,39 +98,19 @@ namespace osu.Game.Screens.Multi.Ranking { Debug.Assert(direction == 1 || direction == -1); - Cursor cursor; - MultiplayerScoresSort sort; + MultiplayerScores pivot = direction == -1 ? higherScores : lowerScores; - switch (direction) - { - case -1: - cursor = higherScoresCursor; - sort = MultiplayerScoresSort.Ascending; - break; - - default: - cursor = lowerScoresCursor; - sort = MultiplayerScoresSort.Descending; - break; - } - - if (cursor == null) + if (pivot?.Cursor == null) return null; - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, cursor, sort); + var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params); indexReq.Success += r => { - switch (direction) - { - case -1: - higherScoresCursor = r.Cursor; - break; - - default: - lowerScoresCursor = r.Cursor; - break; - } + if (direction == -1) + higherScores = r; + else + lowerScores = r; performSuccessCallback(scoresCallback, r.Scores); }; From ccc377ae6af607215edc8756a1cd24881be427d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jul 2020 21:42:04 +0900 Subject: [PATCH 14/47] Remove unused enum --- .../Online/Multiplayer/MultiplayerScoresSort.cs | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs deleted file mode 100644 index decb1c4dfe..0000000000 --- a/osu.Game/Online/Multiplayer/MultiplayerScoresSort.cs +++ /dev/null @@ -1,14 +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.Online.Multiplayer -{ - /// - /// Sorting option for indexing multiplayer scores. - /// - public enum MultiplayerScoresSort - { - Ascending, - Descending - } -} From 6c7e806eacecd71ad073d160ac95dbcadaad8199 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 12:39:18 +0900 Subject: [PATCH 15/47] Include executable hash when submitting multiplayer scores --- osu.Game/OsuGameBase.cs | 14 ++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 3 +++ osu.Game/Screens/Play/Player.cs | 1 + 3 files changed, 18 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index fe5c0704b7..964a7fdd35 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Development; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.IO.Stores; @@ -97,6 +98,11 @@ namespace osu.Game public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); + /// + /// MD5 representation of the game executable. + /// + public string VersionHash { get; private set; } + public bool IsDeployedBuild => AssemblyVersion.Major > 0; public virtual string Version @@ -128,6 +134,14 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { + var assembly = Assembly.GetEntryAssembly(); + + if (assembly != null) + { + using (var str = File.OpenRead(assembly.Location)) + VersionHash = str.ComputeMD5Hash(); + } + Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 84c0d5b54e..2cc065b5ad 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -51,6 +51,9 @@ namespace osu.Game.Scoring [NotMapped] public bool Passed { get; set; } = true; + [JsonProperty("version_hash")] + public string VersionHash { get; set; } + [JsonIgnore] public virtual RulesetInfo Ruleset { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 541275cf55..5df6cf42cb 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -465,6 +465,7 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, + VersionHash = Game.VersionHash, Mods = Mods.Value.ToArray(), }; From 9e6d562872effe4ab8c763dd0a6db0b7f0be1ffe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 29 Jul 2020 13:18:40 +0900 Subject: [PATCH 16/47] Send in initial score request instead --- osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs | 5 ++++- osu.Game/Scoring/ScoreInfo.cs | 3 --- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 2 +- osu.Game/Screens/Play/Player.cs | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs index f973f96b37..2d99b12519 100644 --- a/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs +++ b/osu.Game/Online/Multiplayer/CreateRoomScoreRequest.cs @@ -11,17 +11,20 @@ namespace osu.Game.Online.Multiplayer { private readonly int roomId; private readonly int playlistItemId; + private readonly string versionHash; - public CreateRoomScoreRequest(int roomId, int playlistItemId) + public CreateRoomScoreRequest(int roomId, int playlistItemId, string versionHash) { this.roomId = roomId; this.playlistItemId = playlistItemId; + this.versionHash = versionHash; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; + req.AddParameter("version_hash", versionHash); return req; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 2cc065b5ad..84c0d5b54e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -51,9 +51,6 @@ namespace osu.Game.Scoring [NotMapped] public bool Passed { get; set; } = true; - [JsonProperty("version_hash")] - public string VersionHash { get; set; } - [JsonIgnore] public virtual RulesetInfo Ruleset { get; set; } diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index c2381fe219..da082692d7 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Multi.Play if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); - var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); + var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID, Game.VersionHash); req.Success += r => token = r.ID; req.Failure += e => { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5df6cf42cb..541275cf55 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -465,7 +465,6 @@ namespace osu.Game.Screens.Play { Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, - VersionHash = Game.VersionHash, Mods = Mods.Value.ToArray(), }; From 0f1f4b2b5c97656e80650b4560bfe4d1adb3b740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 15:36:42 +0900 Subject: [PATCH 17/47] Add pooling for mania hit explosions --- .../Skinning/LegacyHitExplosion.cs | 7 ++- osu.Game.Rulesets.Mania/UI/Column.cs | 15 ++----- .../UI/DefaultHitExplosion.cs | 28 +++++------- osu.Game.Rulesets.Mania/UI/IHitExplosion.cs | 16 +++++++ .../UI/PoolableHitExplosion.cs | 44 +++++++++++++++++++ 5 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/UI/IHitExplosion.cs create mode 100644 osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index bc93bb2615..c2b39945c2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -6,13 +6,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitExplosion : LegacyManiaColumnElement + public class LegacyHitExplosion : LegacyManiaColumnElement, IHitExplosion { private readonly IBindable direction = new Bindable(); @@ -62,10 +63,8 @@ namespace osu.Game.Rulesets.Mania.Skinning explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - protected override void LoadComplete() + public void Animate() { - base.LoadComplete(); - explosion?.FadeInFromZero(80) .Then().FadeOut(120); } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 642353bd0b..7ddac759db 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -9,9 +9,9 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Pooling; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -34,8 +34,8 @@ namespace osu.Game.Rulesets.Mania.UI public readonly Bindable Action = new Bindable(); public readonly ColumnHitObjectArea HitObjectArea; - internal readonly Container TopLevelContainer; + private readonly DrawablePool hitExplosionPool; public Container UnderlayElements => HitObjectArea.UnderlayElements; @@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Mania.UI InternalChildren = new[] { + hitExplosionPool = new DrawablePool(5), // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements background.CreateProxy(), HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both }, @@ -108,15 +109,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - var explosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, Index), _ => - new DefaultHitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)) - { - RelativeSizeAxes = Axes.Both - }; - - HitObjectArea.Explosions.Add(explosion); - - explosion.Delay(200).Expire(true); + HitObjectArea.Explosions.Add(hitExplosionPool.Get()); } public bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 7a047ed121..bac77b134c 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -15,7 +15,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.UI { - public class DefaultHitExplosion : CompositeDrawable + public class DefaultHitExplosion : CompositeDrawable, IHitExplosion { public override bool RemoveWhenNotAlive => true; @@ -123,21 +123,6 @@ namespace osu.Game.Rulesets.Mania.UI direction.BindValueChanged(onDirectionChanged, true); } - protected override void LoadComplete() - { - const double duration = 200; - - base.LoadComplete(); - - largeFaint - .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint) - .FadeOut(duration * 2); - - mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint); - - this.FadeOut(duration, Easing.Out); - } - private void onDirectionChanged(ValueChangedEvent direction) { if (direction.NewValue == ScrollingDirection.Up) @@ -151,5 +136,16 @@ namespace osu.Game.Rulesets.Mania.UI Y = -DefaultNotePiece.NOTE_HEIGHT / 2; } } + + public void Animate() + { + largeFaint + .ResizeTo(largeFaint.Size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) + .FadeOut(PoolableHitExplosion.DURATION * 2); + + mainGlow1.ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); + + this.FadeOut(PoolableHitExplosion.DURATION, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs new file mode 100644 index 0000000000..da1742e48b --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs @@ -0,0 +1,16 @@ +// 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.Rulesets.Mania.UI +{ + /// + /// Common interface for all hit explosion bodies. + /// + public interface IHitExplosion + { + /// + /// Begins animating this . + /// + void Animate(); + } +} diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs new file mode 100644 index 0000000000..43808f99a8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.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.Pooling; +using osu.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class PoolableHitExplosion : PoolableDrawable + { + public const double DURATION = 200; + + [Resolved] + private Column column { get; set; } + + private SkinnableDrawable skinnableExplosion; + + public PoolableHitExplosion() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), + _ => new DefaultHitExplosion(column.AccentColour, false /*todo */)) + { + RelativeSizeAxes = Axes.Both + }; + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(); + + this.Delay(DURATION).Then().Expire(); + } + } +} From 7f2e554ad47a6e2ac9f3d691d1d128838a49546f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 15:52:25 +0900 Subject: [PATCH 18/47] Fix animations not being reset --- .../Skinning/LegacyHitExplosion.cs | 2 ++ osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs | 15 +++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c2b39945c2..c4fcffbc22 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -65,6 +65,8 @@ namespace osu.Game.Rulesets.Mania.Skinning public void Animate() { + (explosion as IFramedAnimation)?.GotoFrame(0); + explosion?.FadeInFromZero(80) .Then().FadeOut(120); } diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index bac77b134c..3007668117 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Mania.UI { public class DefaultHitExplosion : CompositeDrawable, IHitExplosion { + private const float default_large_faint_size = 0.8f; + public override bool RemoveWhenNotAlive => true; private readonly IBindable direction = new Bindable(); @@ -54,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Both, Masking = true, // we want our size to be very small so the glow dominates it. - Size = new Vector2(0.8f), + Size = new Vector2(default_large_faint_size), Blending = BlendingParameters.Additive, EdgeEffect = new EdgeEffectParameters { @@ -140,12 +142,17 @@ namespace osu.Game.Rulesets.Mania.UI public void Animate() { largeFaint - .ResizeTo(largeFaint.Size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) + .ResizeTo(default_large_faint_size) + .Then() + .ResizeTo(default_large_faint_size * new Vector2(5, 1), PoolableHitExplosion.DURATION, Easing.OutQuint) .FadeOut(PoolableHitExplosion.DURATION * 2); - mainGlow1.ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); + mainGlow1 + .ScaleTo(1) + .Then() + .ScaleTo(1.4f, PoolableHitExplosion.DURATION, Easing.OutQuint); - this.FadeOut(PoolableHitExplosion.DURATION, Easing.Out); + this.FadeOutFromOne(PoolableHitExplosion.DURATION, Easing.Out); } } } From 00821e7b653b58f55de16c9e9cfdb87b2846f85c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 16:14:19 +0900 Subject: [PATCH 19/47] Re-implement support for small ticks --- .../Skinning/LegacyHitExplosion.cs | 3 +- osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- .../UI/DefaultHitExplosion.cs | 43 +++++++++++-------- osu.Game.Rulesets.Mania/UI/IHitExplosion.cs | 5 ++- .../UI/PoolableHitExplosion.cs | 13 ++++-- 5 files changed, 41 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index c4fcffbc22..12747924de 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; @@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Skinning explosion.Anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } - public void Animate() + public void Animate(JudgementResult result) { (explosion as IFramedAnimation)?.GotoFrame(0); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 7ddac759db..255ce4c064 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.UI if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; - HitObjectArea.Explosions.Add(hitExplosionPool.Get()); + HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); } public bool OnPressed(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs index 3007668117..225269cf48 100644 --- a/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/DefaultHitExplosion.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Utils; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.UI.Scrolling; using osuTK; @@ -21,31 +23,30 @@ namespace osu.Game.Rulesets.Mania.UI public override bool RemoveWhenNotAlive => true; + [Resolved] + private Column column { get; set; } + private readonly IBindable direction = new Bindable(); - private readonly CircularContainer largeFaint; - private readonly CircularContainer mainGlow1; + private CircularContainer largeFaint; + private CircularContainer mainGlow1; - public DefaultHitExplosion(Color4 objectColour, bool isSmall = false) + public DefaultHitExplosion() { Origin = Anchor.Centre; RelativeSizeAxes = Axes.X; Height = DefaultNotePiece.NOTE_HEIGHT; + } - // scale roughly in-line with visual appearance of notes - Scale = new Vector2(1f, 0.6f); - - if (isSmall) - Scale *= 0.5f; - + [BackgroundDependencyLoader] + private void load(IScrollingInfo scrollingInfo) + { const float angle_variangle = 15; // should be less than 45 - const float roundness = 80; - const float initial_height = 10; - var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1); + var colour = Interpolation.ValueAt(0.4f, column.AccentColour, Color4.White, 0, 1); InternalChildren = new Drawable[] { @@ -61,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f), + Colour = Interpolation.ValueAt(0.1f, column.AccentColour, Color4.White, 0, 1).Opacity(0.3f), Roundness = 160, Radius = 200, }, @@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1), + Colour = Interpolation.ValueAt(0.6f, column.AccentColour, Color4.White, 0, 1), Roundness = 20, Radius = 50, }, @@ -116,11 +117,7 @@ namespace osu.Game.Rulesets.Mania.UI }, } }; - } - [BackgroundDependencyLoader] - private void load(IScrollingInfo scrollingInfo) - { direction.BindTo(scrollingInfo.Direction); direction.BindValueChanged(onDirectionChanged, true); } @@ -139,8 +136,16 @@ namespace osu.Game.Rulesets.Mania.UI } } - public void Animate() + public void Animate(JudgementResult result) { + // scale roughly in-line with visual appearance of notes + Vector2 scale = new Vector2(1, 0.6f); + + if (result.Judgement is HoldNoteTickJudgement) + scale *= 0.5f; + + this.ScaleTo(scale); + largeFaint .ResizeTo(default_large_faint_size) .Then() diff --git a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs index da1742e48b..3252dcc276 100644 --- a/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/IHitExplosion.cs @@ -1,6 +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 osu.Game.Rulesets.Judgements; + namespace osu.Game.Rulesets.Mania.UI { /// @@ -11,6 +13,7 @@ namespace osu.Game.Rulesets.Mania.UI /// /// Begins animating this . /// - void Animate(); + /// The type of that caused this explosion. + void Animate(JudgementResult result); } } diff --git a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs index 43808f99a8..64b7d7d550 100644 --- a/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/PoolableHitExplosion.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI @@ -12,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.UI { public const double DURATION = 200; + public JudgementResult Result { get; private set; } + [Resolved] private Column column { get; set; } @@ -25,18 +28,22 @@ namespace osu.Game.Rulesets.Mania.UI [BackgroundDependencyLoader] private void load() { - InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), - _ => new DefaultHitExplosion(column.AccentColour, false /*todo */)) + InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion()) { RelativeSizeAxes = Axes.Both }; } + public void Apply(JudgementResult result) + { + Result = result; + } + protected override void PrepareForUse() { base.PrepareForUse(); - (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(); + (skinnableExplosion?.Drawable as IHitExplosion)?.Animate(Result); this.Delay(DURATION).Then().Expire(); } From e5ebd211569f8f0fe050142272c6e52ca64fb4b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jul 2020 16:25:17 +0900 Subject: [PATCH 20/47] Fix test scene and add pooling support --- .../Skinning/TestSceneHitExplosion.cs | 50 ++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs index a692c0b697..0c56f7bcf4 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHitExplosion.cs @@ -1,23 +1,27 @@ // 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.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Skinning; +using osu.Game.Rulesets.Objects; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests.Skinning { [TestFixture] public class TestSceneHitExplosion : ManiaSkinnableTestScene { + private readonly List> hitExplosionPools = new List>(); + public TestSceneHitExplosion() { int runcount = 0; @@ -29,28 +33,40 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning if (runcount % 15 > 12) return; - CreatedDrawables.OfType().ForEach(c => + int poolIndex = 0; + + foreach (var c in CreatedDrawables.OfType()) { - c.Add(new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, 0), - _ => new DefaultHitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); - }); + c.Add(hitExplosionPools[poolIndex].Get(e => + { + e.Apply(new JudgementResult(new HitObject(), runcount % 6 == 0 ? new HoldNoteTickJudgement() : new ManiaJudgement())); + + e.Anchor = Anchor.Centre; + e.Origin = Anchor.Centre; + })); + + poolIndex++; + } }, 100); } [BackgroundDependencyLoader] private void load() { - SetContents(() => new ColumnTestContainer(0, ManiaAction.Key1) + SetContents(() => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.Y, - Y = -0.25f, - Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), + var pool = new DrawablePool(5); + hitExplosionPools.Add(pool); + + return new ColumnTestContainer(0, ManiaAction.Key1) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Y, + Y = -0.25f, + Size = new Vector2(Column.COLUMN_WIDTH, DefaultNotePiece.NOTE_HEIGHT), + Child = pool + }; }); } } From d4496eb9824ad8bbc14da66d00b14c037fc54c69 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 30 Jul 2020 04:51:09 +0300 Subject: [PATCH 21/47] Update ShowMoreButton in line with web --- .../Visual/Online/TestSceneShowMoreButton.cs | 20 ++--- .../Graphics/UserInterface/ShowMoreButton.cs | 84 ++++++++++++++----- .../Comments/Buttons/CommentRepliesButton.cs | 15 ++-- .../Comments/CommentsShowMoreButton.cs | 11 --- .../Profile/Sections/PaginatedContainer.cs | 5 +- .../Profile/Sections/ProfileShowMoreButton.cs | 19 ----- 6 files changed, 76 insertions(+), 78 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs index 273f593c32..18ac415126 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -4,19 +4,22 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public class TestSceneShowMoreButton : OsuTestScene { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + public TestSceneShowMoreButton() { - TestButton button = null; + ShowMoreButton button = null; int fireCount = 0; - Add(button = new TestButton + Add(button = new ShowMoreButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -46,16 +49,5 @@ namespace osu.Game.Tests.Visual.Online AddAssert("action fired twice", () => fireCount == 2); AddAssert("is in loading state", () => button.IsLoading); } - - private class TestButton : ShowMoreButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colors) - { - IdleColour = colors.YellowDark; - HoverColour = colors.Yellow; - ChevronIconColour = colors.Red; - } - } } } diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index c9cd9f1158..f4ab53d305 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -1,13 +1,15 @@ // 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.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osuTK; -using osuTK.Graphics; using System.Collections.Generic; namespace osu.Game.Graphics.UserInterface @@ -16,14 +18,6 @@ namespace osu.Game.Graphics.UserInterface { private const int duration = 200; - private Color4 chevronIconColour; - - protected Color4 ChevronIconColour - { - get => chevronIconColour; - set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value; - } - public string Text { get => text.Text; @@ -32,22 +26,28 @@ namespace osu.Game.Graphics.UserInterface protected override IEnumerable EffectTargets => new[] { background }; - private ChevronIcon leftChevron; - private ChevronIcon rightChevron; + private ChevronIcon leftIcon; + private ChevronIcon rightIcon; private SpriteText text; private Box background; private FillFlowContainer textContainer; public ShowMoreButton() { - Height = 30; - Width = 140; + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Background2; + HoverColour = colourProvider.Background1; } protected override Drawable CreateContent() => new CircularContainer { Masking = true, - RelativeSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Both, Children = new Drawable[] { background = new Box @@ -56,22 +56,36 @@ namespace osu.Game.Graphics.UserInterface }, textContainer = new FillFlowContainer { + AlwaysPresent = true, Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Spacing = new Vector2(7), + Spacing = new Vector2(10), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5 + }, Children = new Drawable[] { - leftChevron = new ChevronIcon(), + leftIcon = new ChevronIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, text = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = "show more".ToUpper(), }, - rightChevron = new ChevronIcon(), + rightIcon = new ChevronIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } } } } @@ -81,17 +95,41 @@ namespace osu.Game.Graphics.UserInterface protected override void OnLoadFinished() => textContainer.FadeIn(duration, Easing.OutQuint); - private class ChevronIcon : SpriteIcon + protected override bool OnHover(HoverEvent e) { - private const int icon_size = 8; + base.OnHover(e); + leftIcon.FadeHoverColour(); + rightIcon.FadeHoverColour(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + leftIcon.FadeIdleColour(); + rightIcon.FadeIdleColour(); + } + + public class ChevronIcon : SpriteIcon + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } public ChevronIcon() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(icon_size); + Size = new Vector2(7.5f); Icon = FontAwesome.Solid.ChevronDown; } + + [BackgroundDependencyLoader] + private void load() + { + Colour = colourProvider.Foreground1; + } + + public void FadeHoverColour() => this.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + + public void FadeIdleColour() => this.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 53438ca421..202f3ddd7b 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -5,12 +5,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; +using static osu.Game.Graphics.UserInterface.ShowMoreButton; namespace osu.Game.Overlays.Comments.Buttons { @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Comments.Buttons [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly SpriteIcon icon; + private readonly ChevronIcon icon; private readonly Box background; private readonly OsuSpriteText text; @@ -68,12 +68,10 @@ namespace osu.Game.Overlays.Comments.Buttons AlwaysPresent = true, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, - icon = new SpriteIcon + icon = new ChevronIcon { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(7.5f), - Icon = FontAwesome.Solid.ChevronDown + Origin = Anchor.CentreLeft } } } @@ -88,7 +86,6 @@ namespace osu.Game.Overlays.Comments.Buttons private void load() { background.Colour = colourProvider.Background2; - icon.Colour = colourProvider.Foreground1; } protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); @@ -99,7 +96,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHover(e); background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); - icon.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + icon.FadeHoverColour(); return true; } @@ -107,7 +104,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHoverLost(e); background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); - icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + icon.FadeIdleColour(); } } } diff --git a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs b/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs index d2ff7ecb1f..adf64eabb1 100644 --- a/osu.Game/Overlays/Comments/CommentsShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/CommentsShowMoreButton.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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Graphics.UserInterface; @@ -11,16 +10,6 @@ namespace osu.Game.Overlays.Comments { public readonly BindableInt Current = new BindableInt(); - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Height = 20; - - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - protected override void LoadComplete() { Current.BindValueChanged(onCurrentChanged, true); diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index a30ff786fb..9720469548 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -14,12 +14,13 @@ using osu.Game.Users; using System.Collections.Generic; using System.Linq; using System.Threading; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Profile.Sections { public abstract class PaginatedContainer : FillFlowContainer { - private readonly ProfileShowMoreButton moreButton; + private readonly ShowMoreButton moreButton; private readonly OsuSpriteText missingText; private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; @@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Profile.Sections RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), }, - moreButton = new ProfileShowMoreButton + moreButton = new ShowMoreButton { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs deleted file mode 100644 index 426ebeebe6..0000000000 --- a/osu.Game/Overlays/Profile/Sections/ProfileShowMoreButton.cs +++ /dev/null @@ -1,19 +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.Allocation; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Overlays.Profile.Sections -{ - public class ProfileShowMoreButton : ShowMoreButton - { - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; - ChevronIconColour = colourProvider.Foreground1; - } - } -} From 45ddc7a2e9f9d1b05404831c172ff479920031a5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 30 Jul 2020 05:02:01 +0300 Subject: [PATCH 22/47] Rename ShowMoreButton in comments namespace to ShowMoreRepliesButton --- .../Buttons/{ShowMoreButton.cs => ShowMoreRepliesButton.cs} | 4 ++-- osu.Game/Overlays/Comments/DrawableComment.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Comments/Buttons/{ShowMoreButton.cs => ShowMoreRepliesButton.cs} (93%) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs similarity index 93% rename from osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs rename to osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs index 2c363564d2..c115a8bb8f 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowMoreButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowMoreRepliesButton.cs @@ -12,13 +12,13 @@ using osu.Framework.Allocation; namespace osu.Game.Overlays.Comments.Buttons { - public class ShowMoreButton : LoadingButton + public class ShowMoreRepliesButton : LoadingButton { protected override IEnumerable EffectTargets => new[] { text }; private OsuSpriteText text; - public ShowMoreButton() + public ShowMoreRepliesButton() { AutoSizeAxes = Axes.Both; LoadingAnimationSize = new Vector2(8); diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 9c0a48ec29..31aa41e967 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Comments private FillFlowContainer childCommentsVisibilityContainer; private FillFlowContainer childCommentsContainer; private LoadRepliesButton loadRepliesButton; - private ShowMoreButton showMoreButton; + private ShowMoreRepliesButton showMoreButton; private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; @@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments Top = 10 } }, - showMoreButton = new ShowMoreButton + showMoreButton = new ShowMoreRepliesButton { Action = () => RepliesRequested(this, ++currentPage) } From e5991d6e1487cd3f9a3596494fc732a0f36a5155 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 13:49:04 +0900 Subject: [PATCH 23/47] Change method structure for hover/unhover state setting (shouldn't be called "Fade") --- osu.Game/Graphics/UserInterface/ShowMoreButton.cs | 13 ++++++------- .../Comments/Buttons/CommentRepliesButton.cs | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs index f4ab53d305..924c7913f3 100644 --- a/osu.Game/Graphics/UserInterface/ShowMoreButton.cs +++ b/osu.Game/Graphics/UserInterface/ShowMoreButton.cs @@ -98,16 +98,16 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { base.OnHover(e); - leftIcon.FadeHoverColour(); - rightIcon.FadeHoverColour(); + leftIcon.SetHoveredState(true); + rightIcon.SetHoveredState(true); return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - leftIcon.FadeIdleColour(); - rightIcon.FadeIdleColour(); + leftIcon.SetHoveredState(false); + rightIcon.SetHoveredState(false); } public class ChevronIcon : SpriteIcon @@ -127,9 +127,8 @@ namespace osu.Game.Graphics.UserInterface Colour = colourProvider.Foreground1; } - public void FadeHoverColour() => this.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); - - public void FadeIdleColour() => this.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + public void SetHoveredState(bool hovered) => + this.FadeColour(hovered ? colourProvider.Light1 : colourProvider.Foreground1, 200, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 202f3ddd7b..57bf2af4d2 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHover(e); background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); - icon.FadeHoverColour(); + icon.SetHoveredState(true); return true; } @@ -104,7 +104,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHoverLost(e); background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); - icon.FadeIdleColour(); + icon.SetHoveredState(false); } } } From 7071f9a3af6fd281cf76c5aacef45959df5c3e87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 14:24:21 +0900 Subject: [PATCH 24/47] 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 7e6f1469f5..61314bdc10 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5ac54a853f..d6aeca1f53 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8b2d1346be..9b8d70ab6d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d8bb52800fe5e351dd730cd2d37f707b1a447c3b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 14:31:05 +0900 Subject: [PATCH 25/47] Update framework again (github deploy failed) --- 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 61314bdc10..0d951f58e6 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d6aeca1f53..92e7080fed 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9b8d70ab6d..973c1d5b89 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 1dfd2112c60d2f468a74675f383b4125334d11cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 15:32:08 +0900 Subject: [PATCH 26/47] Source hash from osu.Game.dll rather than executable --- osu.Game/OsuGameBase.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 964a7fdd35..278f2d849f 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -134,13 +134,8 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { - var assembly = Assembly.GetEntryAssembly(); - - if (assembly != null) - { - using (var str = File.OpenRead(assembly.Location)) - VersionHash = str.ComputeMD5Hash(); - } + using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location)) + VersionHash = str.ComputeMD5Hash(); Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly)); From cf697cc276e7aa466d068566b4250009291efdd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 15:32:40 +0900 Subject: [PATCH 27/47] Update framework again (fix audio component disposal issue) --- 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 0d951f58e6..0ac766926c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 92e7080fed..1ececa448c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 973c1d5b89..ef5ba10d17 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 6b9102b2a43665b82c92bcb3a3a643e8d23acce9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Jul 2020 17:58:49 +0900 Subject: [PATCH 28/47] Add osu!catch banana catching sounds --- osu.Game.Rulesets.Catch/Objects/Banana.cs | 21 +++++++++++++++++++ .../Objects/BananaShower.cs | 12 ++++++++--- .../Objects/Drawables/DrawableBanana.cs | 7 +++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index 0b3d1d23e0..4ecfb7b16d 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -1,6 +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.Collections.Generic; +using osu.Game.Audio; using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; @@ -8,8 +10,27 @@ namespace osu.Game.Rulesets.Catch.Objects { public class Banana : Fruit { + /// + /// Index of banana in current shower. + /// + public int BananaIndex; + public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override Judgement CreateJudgement() => new CatchBananaJudgement(); + + private static readonly List samples = new List { new BananaHitSampleInfo() }; + + public Banana() + { + Samples = samples; + } + + private class BananaHitSampleInfo : HitSampleInfo + { + private static string[] lookupNames { get; } = { "metronomelow", "catch-banana" }; + + public override IEnumerable LookupNames => lookupNames; + } } } diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 04a995c77e..89c51459a6 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -30,15 +30,21 @@ namespace osu.Game.Rulesets.Catch.Objects if (spacing <= 0) return; - for (double i = StartTime; i <= EndTime; i += spacing) + double time = StartTime; + int i = 0; + + while (time <= EndTime) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new Banana { - Samples = Samples, - StartTime = i + StartTime = time, + BananaIndex = i, }); + + time += spacing; + i++; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs index 01b76ceed9..a865984d45 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs @@ -40,6 +40,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1); } + public override void PlaySamples() + { + base.PlaySamples(); + if (Samples != null) + Samples.Frequency.Value = 0.77f + ((Banana)HitObject).BananaIndex * 0.006f; + } + private Color4 getBananaColour() { switch (RNG.Next(0, 3)) From 23ab6f8f946739bb788cc480f894b57611e78647 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Jul 2020 21:10:13 +0900 Subject: [PATCH 29/47] Fix dynamic compilation loading wrong ruleset versions --- osu.Game/Rulesets/RulesetStore.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 58a2ba056e..837796287a 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -65,11 +65,15 @@ namespace osu.Game.Rulesets // the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies. // this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name // already loaded in the AppDomain. - foreach (var curAsm in AppDomain.CurrentDomain.GetAssemblies()) - { - if (asm.Name.Equals(curAsm.GetName().Name, StringComparison.Ordinal)) - return curAsm; - } + var domainAssembly = AppDomain.CurrentDomain.GetAssemblies() + // Given name is always going to be equally-or-more qualified than the assembly name. + .Where(a => args.Name.Contains(a.GetName().Name, StringComparison.Ordinal)) + // Pick the greatest assembly version. + .OrderBy(a => a.GetName().Version) + .LastOrDefault(); + + if (domainAssembly != null) + return domainAssembly; return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName); } From 5af45bcdcc008b8bc61d3919037b6cd150532893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 20:10:41 +0200 Subject: [PATCH 30/47] Expand tests to cover non-bank sample lookups --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 737946e1e0..a70b08a0d3 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -67,9 +67,11 @@ namespace osu.Game.Tests.Gameplay /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin: /// normal-hitnormal2 /// normal-hitnormal + /// hitnormal /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { SetupSkins(expectedSample, expectedSample); @@ -83,9 +85,11 @@ namespace osu.Game.Tests.Gameplay /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: /// normal-hitnormal2 /// normal-hitnormal + /// hitnormal /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { SetupSkins(string.Empty, expectedSample); @@ -145,6 +149,7 @@ namespace osu.Game.Tests.Gameplay /// [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] public void TestControlPointCustomSampleFromBeatmap(string sampleName) { SetupSkins(sampleName, sampleName); From 566c5310bf954c1b7b5b71dceb63d4a904d81162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 21:34:57 +0200 Subject: [PATCH 31/47] Add test coverage for taiko sample lookups --- ...o-hitobject-beatmap-custom-sample-bank.osu | 10 ++++ .../TestSceneTaikoHitObjectSamples.cs | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..f9755782c2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Resources/SampleLookups/taiko-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 1 + +[TimingPoints] +0,300,4,1,2,100,1,0 + +[HitObjects] +444,320,1000,5,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs new file mode 100644 index 0000000000..7089ea6619 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples))); + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromBeatmap(string expectedSample) + { + SetupSkins(expectedSample, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal")] + [TestCase("normal-hitnormal")] + [TestCase("hitnormal")] + public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) + { + SetupSkins(string.Empty, expectedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertUserLookup(expectedSample); + } + + [TestCase("taiko-normal-hitnormal2")] + [TestCase("normal-hitnormal2")] + public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample) + { + SetupSkins(string.Empty, unwantedSample); + + CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu"); + + AssertNoLookup(unwantedSample); + } + } +} From 2df5fafea0ed7a537eb51e2a830f851702f90dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 21:39:45 +0200 Subject: [PATCH 32/47] Add failing test case --- .../Gameplay/TestSceneHitObjectSamples.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index a70b08a0d3..c3acc2ebe7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -82,12 +82,11 @@ namespace osu.Game.Tests.Gameplay } /// - /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin when the beatmap does not contain the sample: - /// normal-hitnormal2 + /// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the user skin + /// (ignoring the custom sample set index) when the beatmap skin does not contain the sample: /// normal-hitnormal /// hitnormal /// - [TestCase("normal-hitnormal2")] [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) @@ -99,6 +98,23 @@ namespace osu.Game.Tests.Gameplay AssertUserLookup(expectedSample); } + /// + /// Tests that a hitobject which provides a custom sample set of 2 does not retrieve a normal-hitnormal2 sample from the user skin + /// if the beatmap skin does not contain the sample. + /// User skins in stable ignore the custom sample set index when performing lookups. + /// + [Test] + public void TestUserSkinLookupIgnoresSampleBank() + { + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(string.Empty, unwanted_sample); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + + AssertNoLookup(unwanted_sample); + } + /// /// Tests that a hitobject which provides a sample file retrieves the sample file from the beatmap skin. /// @@ -183,7 +199,7 @@ namespace osu.Game.Tests.Gameplay string[] expectedSamples = { "normal-hitnormal2", - "normal-hitwhistle2" + "normal-hitwhistle" // user skin lookups ignore custom sample set index }; SetupSkins(expectedSamples[0], expectedSamples[1]); From 2bb436fd3c6dd6a6d172c462ed264ae8bc963faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 28 Jul 2020 23:52:09 +0200 Subject: [PATCH 33/47] Do not use custom sample banks outside of beatmap skin --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 1 + osu.Game/Skinning/LegacySkin.cs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 87bca856a3..d647bc4a2d 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -14,6 +14,7 @@ namespace osu.Game.Skinning public class LegacyBeatmapSkin : LegacySkin { protected override bool AllowManiaSkin => false; + protected override bool UseCustomSampleBanks => true; public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, AudioManager audioManager) : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), audioManager, beatmap.Path) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 3bbeff9918..187d601812 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -38,6 +38,12 @@ namespace osu.Game.Skinning protected virtual bool AllowManiaSkin => hasKeyTexture.Value; + /// + /// Whether this skin can use samples with a custom bank (custom sample set in stable terminology). + /// Added in order to match sample lookup logic from stable (in stable, only the beatmap skin could use samples with a custom sample bank). + /// + protected virtual bool UseCustomSampleBanks => false; + public new LegacySkinConfiguration Configuration { get => base.Configuration as LegacySkinConfiguration; @@ -337,7 +343,12 @@ namespace osu.Game.Skinning public override SampleChannel GetSample(ISampleInfo sampleInfo) { - foreach (var lookup in sampleInfo.LookupNames) + var lookupNames = sampleInfo.LookupNames; + + if (sampleInfo is HitSampleInfo hitSample) + lookupNames = getLegacyLookupNames(hitSample); + + foreach (var lookup in lookupNames) { var sample = Samples?.Get(lookup); @@ -361,5 +372,18 @@ namespace osu.Game.Skinning string lastPiece = componentName.Split('/').Last(); yield return componentName.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; } + + private IEnumerable getLegacyLookupNames(HitSampleInfo hitSample) + { + var lookupNames = hitSample.LookupNames; + + if (!UseCustomSampleBanks && !string.IsNullOrEmpty(hitSample.Suffix)) + // for compatibility with stable, exclude the lookup names with the custom sample bank suffix, if they are not valid for use in this skin. + // using .EndsWith() is intentional as it ensures parity in all edge cases + // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). + lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + + return lookupNames; + } } } From 971eafde2b05e51231208eff01f326664f6a7a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 30 Jul 2020 22:07:07 +0200 Subject: [PATCH 34/47] Move fallback to non-bank samples to centralise hackery --- osu.Game/Skinning/LegacySkin.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 187d601812..fc04383a64 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -356,10 +356,6 @@ namespace osu.Game.Skinning return sample; } - if (sampleInfo is HitSampleInfo hsi) - // Try fallback to non-bank samples. - return Samples?.Get(hsi.Name); - return null; } @@ -383,6 +379,11 @@ namespace osu.Game.Skinning // (see LegacyTaikoSampleInfo for an example of one - prioritising the taiko prefix should still apply, but the sample bank should not). lookupNames = hitSample.LookupNames.Where(name => !name.EndsWith(hitSample.Suffix)); + // also for compatibility, try falling back to non-bank samples (so-called "universal" samples) as the last resort. + // going forward specifying banks shall always be required, even for elements that wouldn't require it on stable, + // which is why this is done locally here. + lookupNames = lookupNames.Append(hitSample.Name); + return lookupNames; } } From 6452d62249f1252034fd7c8d85c3929e692c1336 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 12:52:03 +0900 Subject: [PATCH 35/47] 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 0ac766926c..13b4b6ebbb 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1ececa448c..745555e0e2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ef5ba10d17..f1080f0c8b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 62ba214dad3d9cd99b760a1bed13e04ea78bd97a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 16:21:47 +0900 Subject: [PATCH 36/47] Use OrderByDescending --- osu.Game/Rulesets/RulesetStore.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 837796287a..dd43092c0d 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -69,8 +69,8 @@ namespace osu.Game.Rulesets // Given name is always going to be equally-or-more qualified than the assembly name. .Where(a => args.Name.Contains(a.GetName().Name, StringComparison.Ordinal)) // Pick the greatest assembly version. - .OrderBy(a => a.GetName().Version) - .LastOrDefault(); + .OrderByDescending(a => a.GetName().Version) + .FirstOrDefault(); if (domainAssembly != null) return domainAssembly; From 88e179d8aa684a1ef8b9971f74cad059f20e6ce3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:40:58 +0900 Subject: [PATCH 37/47] Split out index-only response --- .../TestSceneTimeshiftResultsScreen.cs | 2 +- .../Multiplayer/IndexPlaylistScoresRequest.cs | 2 +- .../Multiplayer/IndexedMultiplayerScores.cs | 27 +++++++++++++++++++ .../Online/Multiplayer/MultiplayerScores.cs | 12 --------- 4 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 44ca676c4f..8b6d6694c3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Multiplayer void success() { - r.TriggerSuccess(new MultiplayerScores { Scores = roomScores }); + r.TriggerSuccess(new IndexedMultiplayerScores { Scores = roomScores }); roomsReceived = true; } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 67793df344..91f24933e1 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs @@ -12,7 +12,7 @@ namespace osu.Game.Online.Multiplayer /// /// Returns a list of scores for the specified playlist item. /// - public class IndexPlaylistScoresRequest : APIRequest + public class IndexPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; diff --git a/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs b/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs new file mode 100644 index 0000000000..e237b7e3fb --- /dev/null +++ b/osu.Game/Online/Multiplayer/IndexedMultiplayerScores.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using Newtonsoft.Json; + +namespace osu.Game.Online.Multiplayer +{ + /// + /// A object returned via a . + /// + public class IndexedMultiplayerScores : MultiplayerScores + { + /// + /// The total scores in the playlist item. + /// + [JsonProperty("total")] + public int? TotalScores { get; set; } + + /// + /// The user's score, if any. + /// + [JsonProperty("user_score")] + [CanBeNull] + public MultiplayerScore UserScore { get; set; } + } +} diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 2d0f98e032..8b8dd9e48d 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -18,18 +18,6 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("scores")] public List Scores { get; set; } - /// - /// The total scores in the playlist item. Only provided via . - /// - [JsonProperty("total")] - public int? TotalScores { get; set; } - - /// - /// The user's score, if any. Only provided via . - /// - [JsonProperty("user_score")] - public MultiplayerScore UserScore { get; set; } - /// /// The parameters to be used to fetch the next page. /// From eadef53e68cd1612f8bb0b0a6d7260f5631ad07f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:43:40 +0900 Subject: [PATCH 38/47] Add more annotations --- osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs index e83cc1b753..2ac62d0300 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScoresAround.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScoresAround.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 JetBrains.Annotations; using Newtonsoft.Json; namespace osu.Game.Online.Multiplayer @@ -14,12 +15,14 @@ namespace osu.Game.Online.Multiplayer /// Scores sorted "higher" than the user's score, depending on the sorting order. /// [JsonProperty("higher")] + [CanBeNull] public MultiplayerScores Higher { get; set; } /// /// Scores sorted "lower" than the user's score, depending on the sorting order. /// [JsonProperty("lower")] + [CanBeNull] public MultiplayerScores Lower { get; set; } } } From 6d728d27fc7d150c313ec442b810772f174995cf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 17:58:48 +0900 Subject: [PATCH 39/47] Cleanup/consolidate indexing request --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 648bee385c..0ef4953e83 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -75,21 +76,8 @@ namespace osu.Game.Screens.Multi.Ranking performSuccessCallback(scoresCallback, allScores); }; - userScoreReq.Failure += _ => - { - // Fallback to a normal index. - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID); - - indexReq.Success += r => - { - performSuccessCallback(scoresCallback, r.Scores); - lowerScores = r; - }; - - indexReq.Failure += __ => loadingLayer.Hide(); - - api.Queue(indexReq); - }; + // On failure, fallback to a normal index. + userScoreReq.Failure += _ => api.Queue(createIndexRequest(scoresCallback)); return userScoreReq; } @@ -103,16 +91,30 @@ namespace osu.Game.Screens.Multi.Ranking if (pivot?.Cursor == null) return null; - var indexReq = new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params); + return createIndexRequest(scoresCallback, pivot); + } + + /// + /// Creates a with an optional score pivot. + /// + /// Does not queue the request. + /// The callback to perform with the resulting scores. + /// An optional score pivot to retrieve scores around. Can be null to retrieve scores from the highest score. + /// The indexing . + private APIRequest createIndexRequest(Action> scoresCallback, [CanBeNull] MultiplayerScores pivot = null) + { + var indexReq = pivot != null + ? new IndexPlaylistScoresRequest(roomId, playlistItem.ID, pivot.Cursor, pivot.Params) + : new IndexPlaylistScoresRequest(roomId, playlistItem.ID); indexReq.Success += r => { - if (direction == -1) - higherScores = r; - else + if (pivot == lowerScores) lowerScores = r; + else + higherScores = r; - performSuccessCallback(scoresCallback, r.Scores); + performSuccessCallback(scoresCallback, r.Scores, pivot); }; indexReq.Failure += _ => loadingLayer.Hide(); @@ -120,7 +122,13 @@ namespace osu.Game.Screens.Multi.Ranking return indexReq; } - private void performSuccessCallback(Action> callback, List scores) + /// + /// Transforms returned into s, ensure the is put into a sane state, and invokes a given success callback. + /// + /// The callback to invoke with the final s. + /// The s that were retrieved from s. + /// An optional pivot around which the scores were retrieved. + private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) { var scoreInfos = new List(scores.Select(s => s.CreateScoreInfo(playlistItem))); @@ -136,7 +144,7 @@ namespace osu.Game.Screens.Multi.Ranking } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback?.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); loadingLayer.Hide(); } From 9966d4f3b3b5da3c364157ab38dbadd0343152ae Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 19:57:05 +0900 Subject: [PATCH 40/47] Add more loading spinners --- .../Multi/Ranking/TimeshiftResultsScreen.cs | 81 ++++++++++++++++--- osu.Game/Screens/Ranking/ResultsScreen.cs | 30 +++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 10 +++ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 0ef4953e83..87de9fd72a 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -22,7 +22,9 @@ namespace osu.Game.Screens.Multi.Ranking private readonly int roomId; private readonly PlaylistItem playlistItem; - private LoadingSpinner loadingLayer; + private LoadingSpinner leftLoadingLayer; + private LoadingSpinner centreLoadingLayer; + private LoadingSpinner rightLoadingLayer; private MultiplayerScores higherScores; private MultiplayerScores lowerScores; @@ -39,13 +41,29 @@ namespace osu.Game.Screens.Multi.Ranking [BackgroundDependencyLoader] private void load() { - AddInternal(loadingLayer = new LoadingLayer + AddInternal(new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - X = -10, - State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, - Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y } + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y }, + Children = new Drawable[] + { + leftLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + }, + centreLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, + }, + rightLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.Centre, + }, + } }); } @@ -91,6 +109,11 @@ namespace osu.Game.Screens.Multi.Ranking if (pivot?.Cursor == null) return null; + if (pivot == higherScores) + leftLoadingLayer.Show(); + else + rightLoadingLayer.Show(); + return createIndexRequest(scoresCallback, pivot); } @@ -114,10 +137,10 @@ namespace osu.Game.Screens.Multi.Ranking else higherScores = r; - performSuccessCallback(scoresCallback, r.Scores, pivot); + performSuccessCallback(scoresCallback, r.Scores, r); }; - indexReq.Failure += _ => loadingLayer.Hide(); + indexReq.Failure += _ => hideLoadingSpinners(pivot); return indexReq; } @@ -146,7 +169,45 @@ namespace osu.Game.Screens.Multi.Ranking // Invoke callback to add the scores. Exclude the user's current score which was added previously. callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); - loadingLayer.Hide(); + hideLoadingSpinners(pivot); + } + + private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) + { + centreLoadingLayer.Hide(); + + if (pivot == lowerScores) + rightLoadingLayer.Hide(); + else if (pivot == higherScores) + leftLoadingLayer.Hide(); + } + + private class PanelListLoadingSpinner : LoadingSpinner + { + private readonly ScorePanelList list; + + /// + /// Creates a new . + /// + /// The list to track. + /// Whether the spinner should have a surrounding black box for visibility. + public PanelListLoadingSpinner(ScorePanelList list, bool withBox = true) + : base(withBox) + { + this.list = list; + } + + protected override void Update() + { + base.Update(); + + float panelOffset = list.DrawWidth / 2 - ScorePanel.EXPANDED_WIDTH; + + if ((Anchor & Anchor.x0) > 0) + X = (float)(panelOffset - list.Current); + else if ((Anchor & Anchor.x2) > 0) + X = (float)(list.ScrollableExtent - list.Current - panelOffset); + } } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 254ab76f5b..2506a63cc0 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -37,7 +37,8 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); public readonly ScoreInfo Score; - private readonly bool allowRetry; + + protected ScorePanelList ScorePanelList { get; private set; } [Resolved(CanBeNull = true)] private Player player { get; set; } @@ -47,12 +48,13 @@ namespace osu.Game.Screens.Ranking private StatisticsPanel statisticsPanel; private Drawable bottomPanel; - private ScorePanelList scorePanelList; private Container detachedPanelContainer; private bool fetchedInitialScores; private APIRequest nextPageRequest; + private readonly bool allowRetry; + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; @@ -87,7 +89,7 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Score = { BindTarget = SelectedScore } }, - scorePanelList = new ScorePanelList + ScorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, SelectedScore = { BindTarget = SelectedScore }, @@ -145,7 +147,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - scorePanelList.AddScore(Score); + ScorePanelList.AddScore(Score); if (player != null && allowRetry) { @@ -181,9 +183,9 @@ namespace osu.Game.Screens.Ranking if (fetchedInitialScores && nextPageRequest == null) { - if (scorePanelList.IsScrolledToStart) + if (ScorePanelList.IsScrolledToStart) nextPageRequest = FetchNextPage(-1, fetchScoresCallback); - else if (scorePanelList.IsScrolledToEnd) + else if (ScorePanelList.IsScrolledToEnd) nextPageRequest = FetchNextPage(1, fetchScoresCallback); if (nextPageRequest != null) @@ -249,7 +251,7 @@ namespace osu.Game.Screens.Ranking private void addScore(ScoreInfo score) { - var panel = scorePanelList.AddScore(score); + var panel = ScorePanelList.AddScore(score); if (detachedPanel != null) panel.Alpha = 0; @@ -262,11 +264,11 @@ namespace osu.Game.Screens.Ranking if (state.NewValue == Visibility.Visible) { // Detach the panel in its original location, and move into the desired location in the local container. - var expandedPanel = scorePanelList.GetPanelForScore(SelectedScore.Value); + var expandedPanel = ScorePanelList.GetPanelForScore(SelectedScore.Value); var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft; // Detach and move into the local container. - scorePanelList.Detach(expandedPanel); + ScorePanelList.Detach(expandedPanel); detachedPanelContainer.Add(expandedPanel); // Move into its original location in the local container first, then to the final location. @@ -276,9 +278,9 @@ namespace osu.Game.Screens.Ranking .MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint); // Hide contracted panels. - foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) contracted.FadeOut(150, Easing.OutQuint); - scorePanelList.HandleInput = false; + ScorePanelList.HandleInput = false; // Dim background. Background.FadeTo(0.1f, 150); @@ -291,7 +293,7 @@ namespace osu.Game.Screens.Ranking // Remove from the local container and re-attach. detachedPanelContainer.Remove(detachedPanel); - scorePanelList.Attach(detachedPanel); + ScorePanelList.Attach(detachedPanel); // Move into its original location in the attached container first, then to the final location. var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); @@ -300,9 +302,9 @@ namespace osu.Game.Screens.Ranking .MoveTo(new Vector2(0, origLocation.Y), 150, Easing.OutQuint); // Show contracted panels. - foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + foreach (var contracted in ScorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) contracted.FadeIn(150, Easing.OutQuint); - scorePanelList.HandleInput = true; + ScorePanelList.HandleInput = true; // Un-dim background. Background.FadeTo(0.5f, 150); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index b2e1e91831..10bd99c8ce 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -41,6 +41,16 @@ namespace osu.Game.Screens.Ranking /// public bool IsScrolledToEnd => flow.Count > 0 && scroll.ScrollableExtent > 0 && scroll.IsScrolledToEnd(scroll_endpoint_distance); + /// + /// The current scroll position. + /// + public double Current => scroll.Current; + + /// + /// The scrollable extent. + /// + public double ScrollableExtent => scroll.ScrollableExtent; + /// /// An action to be invoked if a is clicked while in an expanded state. /// From 4d2a677080beaed2a64c6ece28c663cf4d9f3aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 20:33:18 +0900 Subject: [PATCH 41/47] Fix next track starting before previous one is paused Closes #9651. --- osu.Game/Overlays/MusicController.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 546f7a1ec4..212d4d4850 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -262,7 +262,11 @@ namespace osu.Game.Overlays { if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - beatmap.Value.Track.Restart(); + + // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). + // we probably want to move this to a central method for switching to a new working beatmap in the future. + Schedule(() => beatmap.Value.Track.Restart()); + return true; } From 8e8a11bb72f558c0fb8144390bdab7e5d928ea73 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 20:55:26 +0900 Subject: [PATCH 42/47] Add APIRequest.TriggerFailure() for testing --- osu.Game/Online/API/APIRequest.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 2115326cc2..6912d9b629 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -136,6 +136,11 @@ namespace osu.Game.Online.API Success?.Invoke(); } + internal void TriggerFailure(Exception e) + { + Failure?.Invoke(e); + } + public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); public void Fail(Exception e) @@ -166,7 +171,7 @@ namespace osu.Game.Online.API } Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network); - pendingFailure = () => Failure?.Invoke(e); + pendingFailure = () => TriggerFailure(e); checkAndScheduleFailure(); } From 2b77f99f56af81771980d77e8e5e57f02ff60f59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 20:55:44 +0900 Subject: [PATCH 43/47] Initialise some response parameters --- osu.Game/Online/API/Requests/Cursor.cs | 2 +- osu.Game/Online/Multiplayer/IndexScoresParams.cs | 2 +- osu.Game/Online/Multiplayer/MultiplayerScores.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/Cursor.cs b/osu.Game/Online/API/Requests/Cursor.cs index f21445ca32..3de8db770c 100644 --- a/osu.Game/Online/API/Requests/Cursor.cs +++ b/osu.Game/Online/API/Requests/Cursor.cs @@ -15,6 +15,6 @@ namespace osu.Game.Online.API.Requests { [UsedImplicitly] [JsonExtensionData] - public IDictionary Properties; + public IDictionary Properties { get; set; } = new Dictionary(); } } diff --git a/osu.Game/Online/Multiplayer/IndexScoresParams.cs b/osu.Game/Online/Multiplayer/IndexScoresParams.cs index 8160dfefaf..a511e9a780 100644 --- a/osu.Game/Online/Multiplayer/IndexScoresParams.cs +++ b/osu.Game/Online/Multiplayer/IndexScoresParams.cs @@ -15,6 +15,6 @@ namespace osu.Game.Online.Multiplayer { [UsedImplicitly] [JsonExtensionData] - public IDictionary Properties; + public IDictionary Properties { get; set; } = new Dictionary(); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerScores.cs b/osu.Game/Online/Multiplayer/MultiplayerScores.cs index 8b8dd9e48d..7b9dcff828 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerScores.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerScores.cs @@ -16,12 +16,12 @@ namespace osu.Game.Online.Multiplayer /// The scores. /// [JsonProperty("scores")] - public List Scores { get; set; } + public List Scores { get; set; } = new List(); /// /// The parameters to be used to fetch the next page. /// [JsonProperty("params")] - public IndexScoresParams Params { get; set; } + public IndexScoresParams Params { get; set; } = new IndexScoresParams(); } } From a4a4c8761241eabdf82a35fe02686aa5453c1d6e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:21:48 +0900 Subject: [PATCH 44/47] Fix incorrect score id being used --- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 87de9fd72a..b0cf63a7a9 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Multi.Ranking } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.ID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); hideLoadingSpinners(pivot); } From 17018ffa8b616fe8da85c7acce17ad3744c9c018 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:33:04 +0900 Subject: [PATCH 45/47] Fix potentially triggering new requests too early --- osu.Game/Screens/Ranking/ResultsScreen.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2506a63cc0..c95cf1066e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -190,8 +190,9 @@ namespace osu.Game.Screens.Ranking if (nextPageRequest != null) { - nextPageRequest.Success += () => nextPageRequest = null; - nextPageRequest.Failure += _ => nextPageRequest = null; + // Scheduled after children to give the list a chance to update its scroll position and not potentially trigger a second request too early. + nextPageRequest.Success += () => ScheduleAfterChildren(() => nextPageRequest = null); + nextPageRequest.Failure += _ => ScheduleAfterChildren(() => nextPageRequest = null); api.Queue(nextPageRequest); } From f1e721e396988d5410b32fa3402b612f59dca23d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 31 Jul 2020 21:39:50 +0900 Subject: [PATCH 46/47] Rewrite test scene and add more tests --- .../TestSceneTimeshiftResultsScreen.cs | 344 +++++++++++++++--- .../Multiplayer/IndexPlaylistScoresRequest.cs | 31 +- .../Multi/Ranking/TimeshiftResultsScreen.cs | 23 +- 3 files changed, 329 insertions(+), 69 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 8b6d6694c3..628d08a314 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -3,14 +3,24 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Net; using System.Threading.Tasks; +using JetBrains.Annotations; +using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Multi.Ranking; +using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -18,43 +28,134 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneTimeshiftResultsScreen : ScreenTestScene { - private bool roomsReceived; + private const int scores_per_result = 10; + + private TestResultsScreen resultsScreen; + private int currentScoreId; + private bool requestComplete; [SetUp] public void Setup() => Schedule(() => { - roomsReceived = false; + currentScoreId = 0; + requestComplete = false; bindHandler(); }); [Test] - public void TestShowResultsWithScore() + public void TestShowWithUserScore() { - createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - AddWaitStep("wait for display", 5); + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(userScore: userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] - public void TestShowResultsNullScore() + public void TestShowNullUserScore() { - createResults(null); - AddWaitStep("wait for display", 5); + createResults(); + waitForDisplay(); + + AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } [Test] - public void TestShowResultsNullScoreWithDelay() + public void TestShowUserScoreWithDelay() + { + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(3000, userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + } + + [Test] + public void TestShowNullUserScoreWithDelay() { AddStep("bind delayed handler", () => bindHandler(3000)); - createResults(null); - AddUntilStep("wait for rooms to be received", () => roomsReceived); - AddWaitStep("wait for display", 5); + + createResults(); + waitForDisplay(); + + AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); } - private void createResults(ScoreInfo score) + [Test] + public void TestFetchWhenScrolledToTheRight() + { + createResults(); + waitForDisplay(); + + AddStep("bind delayed handler", () => bindHandler(3000)); + + for (int i = 0; i < 2; i++) + { + int beforePanelCount = 0; + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden); + } + } + + [Test] + public void TestFetchWhenScrolledToTheLeft() + { + ScoreInfo userScore = null; + + AddStep("bind user score info handler", () => + { + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + bindHandler(userScore: userScore); + }); + + createResults(() => userScore); + waitForDisplay(); + + AddStep("bind delayed handler", () => bindHandler(3000)); + + for (int i = 0; i < 2; i++) + { + int beforePanelCount = 0; + + AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); + AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToStart(false)); + + AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); + waitForDisplay(); + + AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); + AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); + } + } + + private void createResults(Func getScore = null) { AddStep("load results", () => { - LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem + LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, Ruleset = { Value = new OsuRuleset().RulesetInfo } @@ -62,62 +163,213 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private void bindHandler(double delay = 0) + private void waitForDisplay() { - var roomScores = new List(); + AddUntilStep("wait for request to complete", () => requestComplete); + AddWaitStep("wait for display", 5); + } - for (int i = 0; i < 10; i++) + private void bindHandler(double delay = 0, ScoreInfo userScore = null, bool failRequests = false) => ((DummyAPIAccess)API).HandleRequest = request => + { + requestComplete = false; + + if (failRequests) { - roomScores.Add(new MultiplayerScore + triggerFail(request, delay); + return; + } + + switch (request) + { + case ShowPlaylistUserScoreRequest s: + if (userScore == null) + triggerFail(s, delay); + else + triggerSuccess(s, createUserResponse(userScore), delay); + break; + + case IndexPlaylistScoresRequest i: + triggerSuccess(i, createIndexResponse(i), delay); + break; + } + }; + + private void triggerSuccess(APIRequest req, T result, double delay) + where T : class + { + if (delay == 0) + success(); + else + { + Task.Run(async () => { - ID = i, - Accuracy = 0.9 - 0.01 * i, - EndedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(i)), + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + Schedule(success); + }); + } + + void success() + { + requestComplete = true; + req.TriggerSuccess(result); + } + } + + private void triggerFail(APIRequest req, double delay) + { + if (delay == 0) + fail(); + else + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + Schedule(fail); + }); + } + + void fail() + { + requestComplete = true; + req.TriggerFailure(new WebException("Failed.")); + } + } + + private MultiplayerScore createUserResponse([NotNull] ScoreInfo userScore) + { + var multiplayerUserScore = new MultiplayerScore + { + ID = (int)(userScore.OnlineScoreID ?? currentScoreId++), + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, + Passed = userScore.Passed, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore, + User = userScore.User, + Statistics = userScore.Statistics, + ScoresAround = new MultiplayerScoresAround + { + Higher = new MultiplayerScores(), + Lower = new MultiplayerScores() + } + }; + + for (int i = 1; i <= scores_per_result; i++) + { + multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, Passed = true, - Rank = ScoreRank.B, - MaxCombo = 999, - TotalScore = 999999 - i * 1000, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore - i, User = new User { Id = 2, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, - Statistics = + Statistics = userScore.Statistics + }); + + multiplayerUserScore.ScoresAround.Higher.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = userScore.Accuracy, + EndedAt = userScore.Date, + Passed = true, + Rank = userScore.Rank, + MaxCombo = userScore.MaxCombo, + TotalScore = userScore.TotalScore + i, + User = new User + { + Id = 2, + Username = $"peppy{i}", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Statistics = userScore.Statistics + }); + } + + addCursor(multiplayerUserScore.ScoresAround.Lower); + addCursor(multiplayerUserScore.ScoresAround.Higher); + + return multiplayerUserScore; + } + + private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req) + { + var result = new IndexedMultiplayerScores(); + + long startTotalScore = req.Cursor?.Properties["total_score"].ToObject() ?? 1000000; + string sort = req.IndexParams?.Properties["sort"].ToObject() ?? "score_desc"; + + for (int i = 1; i <= scores_per_result; i++) + { + result.Scores.Add(new MultiplayerScore + { + ID = currentScoreId++, + Accuracy = 1, + EndedAt = DateTimeOffset.Now, + Passed = true, + Rank = ScoreRank.X, + MaxCombo = 1000, + TotalScore = startTotalScore + (sort == "score_asc" ? i : -i), + User = new User + { + Id = 2, + Username = $"peppy{i}", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Statistics = new Dictionary { { HitResult.Miss, 1 }, { HitResult.Meh, 50 }, { HitResult.Good, 100 }, - { HitResult.Great, 300 }, + { HitResult.Great, 300 } } }); } - ((DummyAPIAccess)API).HandleRequest = request => + addCursor(result); + + return result; + } + + private void addCursor(MultiplayerScores scores) + { + scores.Cursor = new Cursor { - switch (request) + Properties = new Dictionary { - case IndexPlaylistScoresRequest r: - if (delay == 0) - success(); - else - { - Task.Run(async () => - { - await Task.Delay(TimeSpan.FromMilliseconds(delay)); - Schedule(success); - }); - } + { "total_score", JToken.FromObject(scores.Scores[^1].TotalScore) }, + { "score_id", JToken.FromObject(scores.Scores[^1].ID) }, + } + }; - void success() - { - r.TriggerSuccess(new IndexedMultiplayerScores { Scores = roomScores }); - roomsReceived = true; - } - - break; + scores.Params = new IndexScoresParams + { + Properties = new Dictionary + { + { "sort", JToken.FromObject(scores.Scores[^1].TotalScore > scores.Scores[^2].TotalScore ? "score_asc" : "score_desc") } } }; } + + private class TestResultsScreen : TimeshiftResultsScreen + { + public new LoadingSpinner LeftSpinner => base.LeftSpinner; + public new LoadingSpinner CentreSpinner => base.CentreSpinner; + public new LoadingSpinner RightSpinner => base.RightSpinner; + public new ScorePanelList ScorePanelList => base.ScorePanelList; + + public TestResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + : base(score, roomId, playlistItem, allowRetry) + { + } + } } } diff --git a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs index 91f24933e1..684d0aecd8 100644 --- a/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.cs +++ b/osu.Game/Online/Multiplayer/IndexPlaylistScoresRequest.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.Diagnostics; using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -14,39 +15,45 @@ namespace osu.Game.Online.Multiplayer /// public class IndexPlaylistScoresRequest : APIRequest { - private readonly int roomId; - private readonly int playlistItemId; - private readonly Cursor cursor; - private readonly IndexScoresParams indexParams; + public readonly int RoomId; + public readonly int PlaylistItemId; + + [CanBeNull] + public readonly Cursor Cursor; + + [CanBeNull] + public readonly IndexScoresParams IndexParams; public IndexPlaylistScoresRequest(int roomId, int playlistItemId) { - this.roomId = roomId; - this.playlistItemId = playlistItemId; + RoomId = roomId; + PlaylistItemId = playlistItemId; } public IndexPlaylistScoresRequest(int roomId, int playlistItemId, [NotNull] Cursor cursor, [NotNull] IndexScoresParams indexParams) : this(roomId, playlistItemId) { - this.cursor = cursor; - this.indexParams = indexParams; + Cursor = cursor; + IndexParams = indexParams; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - if (cursor != null) + if (Cursor != null) { - req.AddCursor(cursor); + Debug.Assert(IndexParams != null); - foreach (var (key, value) in indexParams.Properties) + req.AddCursor(Cursor); + + foreach (var (key, value) in IndexParams.Properties) req.AddParameter(key, value.ToString()); } return req; } - protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; + protected override string Target => $@"rooms/{RoomId}/playlist/{PlaylistItemId}/scores"; } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index b0cf63a7a9..e212bd4a82 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -22,9 +22,10 @@ namespace osu.Game.Screens.Multi.Ranking private readonly int roomId; private readonly PlaylistItem playlistItem; - private LoadingSpinner leftLoadingLayer; - private LoadingSpinner centreLoadingLayer; - private LoadingSpinner rightLoadingLayer; + protected LoadingSpinner LeftSpinner { get; private set; } + protected LoadingSpinner CentreSpinner { get; private set; } + protected LoadingSpinner RightSpinner { get; private set; } + private MultiplayerScores higherScores; private MultiplayerScores lowerScores; @@ -47,18 +48,18 @@ namespace osu.Game.Screens.Multi.Ranking Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y }, Children = new Drawable[] { - leftLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + LeftSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, }, - centreLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + CentreSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.Centre, Origin = Anchor.Centre, State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, }, - rightLoadingLayer = new PanelListLoadingSpinner(ScorePanelList) + RightSpinner = new PanelListLoadingSpinner(ScorePanelList) { Anchor = Anchor.CentreRight, Origin = Anchor.Centre, @@ -110,9 +111,9 @@ namespace osu.Game.Screens.Multi.Ranking return null; if (pivot == higherScores) - leftLoadingLayer.Show(); + LeftSpinner.Show(); else - rightLoadingLayer.Show(); + RightSpinner.Show(); return createIndexRequest(scoresCallback, pivot); } @@ -174,12 +175,12 @@ namespace osu.Game.Screens.Multi.Ranking private void hideLoadingSpinners([CanBeNull] MultiplayerScores pivot = null) { - centreLoadingLayer.Hide(); + CentreSpinner.Hide(); if (pivot == lowerScores) - rightLoadingLayer.Hide(); + RightSpinner.Hide(); else if (pivot == higherScores) - leftLoadingLayer.Hide(); + LeftSpinner.Hide(); } private class PanelListLoadingSpinner : LoadingSpinner From e8f75a78e8ce4d820d72d05efacc6cec70f51264 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 31 Jul 2020 22:02:12 +0900 Subject: [PATCH 47/47] Also fix second instance of same execution --- osu.Game/Overlays/MusicController.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 212d4d4850..a990f9a6ab 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -236,8 +236,8 @@ namespace osu.Game.Overlays { if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - beatmap.Value.Track.Restart(); + restartTrack(); return PreviousTrackResult.Previous; } @@ -263,16 +263,20 @@ namespace osu.Game.Overlays if (beatmap is Bindable working) working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); - // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). - // we probably want to move this to a central method for switching to a new working beatmap in the future. - Schedule(() => beatmap.Value.Track.Restart()); - + restartTrack(); return true; } return false; } + private void restartTrack() + { + // if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase). + // we probably want to move this to a central method for switching to a new working beatmap in the future. + Schedule(() => beatmap.Value.Track.Restart()); + } + private WorkingBeatmap current; private TrackChangeDirection? queuedDirection;