From 817369903a71151096671b2017c7b54afe5802c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Oct 2021 17:48:56 +0900 Subject: [PATCH 01/15] Rename API score classes --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Visual/Online/TestSceneScoresContainer.cs | 30 +++++++++---------- .../Online/API/Requests/GetScoresRequest.cs | 6 ++-- .../API/Requests/GetUserScoresRequest.cs | 2 +- ...{APILegacyScoreInfo.cs => APIScoreInfo.cs} | 2 +- ...egacyScores.cs => APIScoreWithPosition.cs} | 16 ++-------- .../Requests/Responses/APIScoresCollection.cs | 17 +++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 8 ++--- 9 files changed, 46 insertions(+), 39 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APILegacyScoreInfo.cs => APIScoreInfo.cs} (99%) rename osu.Game/Online/API/Requests/Responses/{APILegacyScores.cs => APIScoreWithPosition.cs} (55%) create mode 100644 osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5e2374cbcb..20dec46101 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreInfo getScoreInfo(bool replayAvailable) { - return new APILegacyScoreInfo + return new APIScoreInfo { OnlineScoreID = 2553163309, OnlineRulesetID = 0, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 0cb8cc22ec..991be33917 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -43,11 +43,11 @@ namespace osu.Game.Tests.Visual.Online } }; - var allScores = new APILegacyScores + var allScores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -162,9 +162,9 @@ namespace osu.Game.Tests.Visual.Online } }; - var myBestScore = new APILegacyUserTopScoreInfo + var myBestScore = new APIScoreWithPosition { - Score = new APILegacyScoreInfo + Score = new APIScoreInfo { User = new User { @@ -185,9 +185,9 @@ namespace osu.Game.Tests.Visual.Online Position = 1337, }; - var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo + var myBestScoreWithNullPosition = new APIScoreWithPosition { - Score = new APILegacyScoreInfo + Score = new APIScoreInfo { User = new User { @@ -208,11 +208,11 @@ namespace osu.Game.Tests.Visual.Online Position = null, }; - var oneScore = new APILegacyScores + var oneScore = new APIScoresCollection { - Scores = new List + Scores = new List { - new APILegacyScoreInfo + new APIScoreInfo { User = new User { @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Online private class TestScoresContainer : ScoresContainer { - public new APILegacyScores Scores + public new APIScoresCollection Scores { set => base.Scores = value; } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index f3bf690ed5..11b0cdddd8 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -13,7 +13,7 @@ using System.Diagnostics; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmapInfo; private readonly BeatmapLeaderboardScope scope; @@ -36,11 +36,11 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - private void onSuccess(APILegacyScores r) + private void onSuccess(APIScoresCollection r) { Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); - foreach (APILegacyScoreInfo score in r.Scores) + foreach (APIScoreInfo score in r.Scores) { score.BeatmapInfo = beatmapInfo; score.OnlineRulesetID = ruleset.ID.Value; diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 7b4d66e7b2..e13ac8e539 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs similarity index 99% rename from osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index aaf2dccc82..c977d27a68 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -15,7 +15,7 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScoreInfo + public class APIScoreInfo { public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs similarity index 55% rename from osu.Game/Online/API/Requests/Responses/APILegacyScores.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 009639c1dc..9c97ad4f05 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -1,29 +1,19 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.Rulesets; using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScores - { - [JsonProperty(@"scores")] - public List Scores; - - [JsonProperty(@"userScore")] - public APILegacyUserTopScoreInfo UserScore; - } - - public class APILegacyUserTopScoreInfo + public class APIScoreWithPosition { [JsonProperty(@"position")] public int? Position; [JsonProperty(@"score")] - public APILegacyScoreInfo Score; + public APIScoreInfo Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs new file mode 100644 index 0000000000..5304664bf8 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScoresCollection + { + [JsonProperty(@"scores")] + public List Scores; + + [JsonProperty(@"userScore")] + public APIScoreWithPosition UserScore; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 82657afc86..3a3566aad9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private CancellationTokenSource loadCancellationSource; - protected APILegacyScores Scores + protected APIScoresCollection Scores { set => Schedule(() => { diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 7c04b331c2..1fb6100a28 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -15,7 +15,7 @@ using osu.Framework.Localisation; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List items) { if (VisiblePages == 0) drawableItemIndex = 0; @@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest() => + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) + protected override Drawable CreateDrawableItem(APIScoreInfo model) { switch (type) { From 49b5de64befad30c60a1d2fae2adc760e8d8fbab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Oct 2021 17:57:17 +0900 Subject: [PATCH 02/15] Extract interface --- osu.Game/Scoring/IScoreInfo.cs | 42 ++++++++++++++++++++++++++++++++++ osu.Game/Scoring/ScoreInfo.cs | 4 ++-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Scoring/IScoreInfo.cs diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs new file mode 100644 index 0000000000..c783a77203 --- /dev/null +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Users; + +namespace osu.Game.Scoring +{ + public interface IScoreInfo : IHasOnlineID + { + int TotalScore { get; set; } + + int MaxCombo { get; set; } + + User User { get; set; } + + long OnlineScoreID { get; set; } + + bool Replay { get; set; } + + DateTimeOffset Date { get; set; } + + BeatmapInfo BeatmapInfo { get; set; } + + double Accuracy { get; set; } + + double? PP { get; set; } + + BeatmapMetadata Metadata { get; set; } + + Dictionary Statistics { get; set; } + + int OnlineRulesetID { get; set; } + + string[] Mods { get; set; } + + public ScoreRank Rank { get; set; } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 8eaec0a477..0afec89450 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -19,7 +19,7 @@ using osu.Game.Utils; namespace osu.Game.Scoring { - public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable, IHasOnlineID + public class ScoreInfo : IScoreInfo, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable, IDeepCloneable { public int ID { get; set; } @@ -54,7 +54,7 @@ namespace osu.Game.Scoring public bool Passed { get; set; } = true; [JsonIgnore] - public virtual RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; private Mod[] mods; From 1944c255a78575a0e075642f66e1ca3655fd2d80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Oct 2021 18:23:52 +0900 Subject: [PATCH 03/15] Implement score interfaces --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Online/API/Requests/GetScoresRequest.cs | 10 +- .../API/Requests/Responses/APIScoreInfo.cs | 113 ++++++++++-------- .../Responses/APIScoreWithPosition.cs | 1 + osu.Game/Scoring/IScoreInfo.cs | 31 +++-- osu.Game/Scoring/ScoreInfo.cs | 7 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 7 files changed, 95 insertions(+), 71 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 20dec46101..b84ac6c2f1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Gameplay { return new APIScoreInfo { - OnlineScoreID = 2553163309, + OnlineID = 2553163309, OnlineRulesetID = 0, Replay = replayAvailable, User = new User diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 11b0cdddd8..9037869c77 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -38,20 +38,20 @@ namespace osu.Game.Online.API.Requests private void onSuccess(APIScoresCollection r) { - Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); + Debug.Assert(ruleset.OnlineID >= 0); foreach (APIScoreInfo score in r.Scores) { - score.BeatmapInfo = beatmapInfo; - score.OnlineRulesetID = ruleset.ID.Value; + score.Beatmap = beatmapInfo; + score.OnlineRulesetID = ruleset.OnlineID; } var userScore = r.UserScore; if (userScore != null) { - userScore.Score.BeatmapInfo = beatmapInfo; - userScore.Score.OnlineRulesetID = ruleset.ID.Value; + userScore.Score.Beatmap = beatmapInfo; + userScore.Score.OnlineRulesetID = ruleset.OnlineID; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index c977d27a68..568c6bea7f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -9,14 +9,71 @@ using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo + public class APIScoreInfo : IScoreInfo { + [JsonProperty(@"score")] + public long TotalScore { get; set; } + + [JsonProperty(@"max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty(@"user")] + public User User { get; set; } + + public bool HasReplay { get; set; } + + [JsonProperty(@"id")] + public long OnlineID { get; set; } + + [JsonProperty(@"replay")] + public bool Replay { get; set; } + + [JsonProperty(@"created_at")] + public DateTimeOffset Date { get; set; } + + [JsonProperty(@"beatmap")] + public IBeatmapInfo Beatmap { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty(@"beatmapset")] + public APIBeatmapSet BeatmapSet + { + set + { + // in the deserialisation case we need to ferry this data across. + if (Beatmap is APIBeatmap apiBeatmap) + apiBeatmap.BeatmapSet = value; + } + } + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } + + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + + [JsonProperty(@"mods")] + public string[] Mods { get; set; } + + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + + // TODO: nuke public ScoreInfo CreateScoreInfo(RulesetStore rulesets) { var ruleset = rulesets.GetRuleset(OnlineRulesetID); @@ -34,10 +91,10 @@ namespace osu.Game.Online.API.Requests.Responses MaxCombo = MaxCombo, User = User, Accuracy = Accuracy, - OnlineScoreID = OnlineScoreID, + OnlineScoreID = OnlineID, Date = Date, PP = PP, - BeatmapInfo = BeatmapInfo, + BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, @@ -81,57 +138,19 @@ namespace osu.Game.Online.API.Requests.Responses return scoreInfo; } - [JsonProperty(@"score")] - public int TotalScore { get; set; } - - [JsonProperty(@"max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty(@"user")] - public User User { get; set; } - - [JsonProperty(@"id")] - public long OnlineScoreID { get; set; } - - [JsonProperty(@"replay")] - public bool Replay { get; set; } - - [JsonProperty(@"created_at")] - public DateTimeOffset Date { get; set; } - - [JsonProperty(@"beatmap")] - public BeatmapInfo BeatmapInfo { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - [JsonProperty(@"beatmapset")] - public BeatmapMetadata Metadata + public APIBeatmapSet Metadata { set { - // extract the set ID to its correct place. - BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID }; - value.ID = 0; - - BeatmapInfo.Metadata = value; + // in the deserialisation case we need to ferry this data across. + if (Beatmap is APIBeatmap apiBeatmap) + apiBeatmap.BeatmapSet = value; } } - [JsonProperty(@"statistics")] - public Dictionary Statistics { get; set; } + public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID }; - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - - [JsonProperty(@"mods")] - public string[] Mods { get; set; } - - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } + Dictionary IScoreInfo.Statistics => new Dictionary(); // TODO: implement... maybe. hitresults have weird mappings per ruleset it would seem. } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 9c97ad4f05..89bb416393 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -16,6 +16,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIScoreInfo Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + { var score = Score.CreateScoreInfo(rulesets); score.Position = Position; diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index c783a77203..b230babf3c 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -5,38 +5,37 @@ using System; using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Users; namespace osu.Game.Scoring { public interface IScoreInfo : IHasOnlineID { - int TotalScore { get; set; } + User User { get; } - int MaxCombo { get; set; } + long TotalScore { get; } - User User { get; set; } + int MaxCombo { get; } - long OnlineScoreID { get; set; } + double Accuracy { get; } - bool Replay { get; set; } + bool HasReplay { get; } - DateTimeOffset Date { get; set; } + DateTimeOffset Date { get; } - BeatmapInfo BeatmapInfo { get; set; } + double? PP { get; } - double Accuracy { get; set; } + IBeatmapInfo Beatmap { get; } - double? PP { get; set; } + Dictionary Statistics { get; } - BeatmapMetadata Metadata { get; set; } + IRulesetInfo Ruleset { get; } - Dictionary Statistics { get; set; } + public ScoreRank Rank { get; } - int OnlineRulesetID { get; set; } - - string[] Mods { get; set; } - - public ScoreRank Rank { get; set; } + // Mods is currently missing from this interface as the `IMod` class has properties which can't be fulfilled by `APIMod`, + // but also doesn't expose `Settings`. We can consider how to implement this in the future if required. } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 0afec89450..3b09669926 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -57,6 +57,7 @@ namespace osu.Game.Scoring public RulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; + private Mod[] mods; [JsonIgnore] @@ -160,7 +161,7 @@ namespace osu.Game.Scoring public DateTimeOffset Date { get; set; } [JsonProperty("statistics")] - public Dictionary Statistics = new Dictionary(); + public Dictionary Statistics { get; set; } = new Dictionary(); [JsonIgnore] [Column("Statistics")] @@ -273,5 +274,9 @@ namespace osu.Game.Scoring } public long OnlineID => OnlineScoreID ?? -1; + + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; + IRulesetInfo IScoreInfo.Ruleset => Ruleset; + bool IScoreInfo.HasReplay => Files.Any(); } } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 5e582a8dcb..684c7574cb 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return getScoreRequest; } From 54073d8a1e4b6f9294590c37b54c0eb87bd89b9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 13:02:19 +0900 Subject: [PATCH 04/15] Isolate score submissions model and remove serialisation from `ScoreInfo` --- .../Online/Solo/SubmitSoloScoreRequest.cs | 64 ++++++++++++++++++- osu.Game/Scoring/ScoreInfo.cs | 43 ++----------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 85fa3eeb34..184dbc911d 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -1,12 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Net.Http; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Framework.IO.Network; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Online.Solo { @@ -16,13 +21,13 @@ namespace osu.Game.Online.Solo private readonly int beatmapId; - private readonly ScoreInfo scoreInfo; + private readonly SubmittableScore score; public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) { this.beatmapId = beatmapId; this.scoreId = scoreId; - this.scoreInfo = scoreInfo; + score = new SubmittableScore(scoreInfo); } protected override WebRequest CreateWebRequest() @@ -32,7 +37,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); @@ -42,4 +47,57 @@ namespace osu.Game.Online.Solo protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}"; } + + /// + /// A class specifically for sending scores to the API during score submission. + /// This is used instead of due to marginally different serialisation naming requirements. + /// + public class SubmittableScore + { + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; } + + [JsonProperty("total_score")] + public long TotalScore { get; } + + [JsonProperty("accuracy")] + public double Accuracy { get; } + + [JsonProperty(@"pp")] + public double? PP { get; } + + [JsonProperty("max_combo")] + public int MaxCombo { get; } + + [JsonProperty("ruleset_id")] + public int RulesetID { get; } + + [JsonProperty("passed")] + public bool Passed { get; } + + // Used for API serialisation/deserialisation. + [JsonProperty("mods")] + public APIMod[] Mods { get; } + + [JsonProperty("user")] + public User User { get; } + + [JsonProperty("statistics")] + public Dictionary Statistics { get; } + + public SubmittableScore(ScoreInfo score) + { + Rank = score.Rank; + TotalScore = score.TotalScore; + Accuracy = score.Accuracy; + PP = score.PP; + MaxCombo = score.MaxCombo; + RulesetID = score.RulesetID; + Passed = score.Passed; + Mods = score.APIMods; + User = score.User; + Statistics = score.Statistics; + } + } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 3b09669926..2747cd48c0 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Database; @@ -23,44 +22,32 @@ namespace osu.Game.Scoring { public int ID { get; set; } - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] public ScoreRank Rank { get; set; } - [JsonProperty("total_score")] public long TotalScore { get; set; } - [JsonProperty("accuracy")] [Column(TypeName = "DECIMAL(1,4)")] // TODO: This data type is wrong (should contain more precision). But at the same time, we probably don't need to be storing this in the database. public double Accuracy { get; set; } - [JsonIgnore] public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); - [JsonProperty(@"pp")] public double? PP { get; set; } - [JsonProperty("max_combo")] public int MaxCombo { get; set; } - [JsonIgnore] public int Combo { get; set; } // Todo: Shouldn't exist in here - [JsonProperty("ruleset_id")] public int RulesetID { get; set; } - [JsonProperty("passed")] [NotMapped] public bool Passed { get; set; } = true; - [JsonIgnore] public RulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; private Mod[] mods; - [JsonIgnore] [NotMapped] public Mod[] Mods { @@ -75,7 +62,7 @@ namespace osu.Game.Scoring if (mods != null) scoreMods = mods; else if (localAPIMods != null) - scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); + scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); return scoreMods; } @@ -87,9 +74,8 @@ namespace osu.Game.Scoring } // Used for API serialisation/deserialisation. - [JsonProperty("mods")] [NotMapped] - private APIMod[] apiMods + public APIMod[] APIMods { get { @@ -111,19 +97,16 @@ namespace osu.Game.Scoring } // Used for database serialisation/deserialisation. - [JsonIgnore] [Column("Mods")] public string ModsJson { - get => JsonConvert.SerializeObject(apiMods); - set => apiMods = JsonConvert.DeserializeObject(value); + get => JsonConvert.SerializeObject(APIMods); + set => APIMods = JsonConvert.DeserializeObject(value); } [NotMapped] - [JsonProperty("user")] public User User { get; set; } - [JsonIgnore] [Column("User")] public string UserString { @@ -135,7 +118,6 @@ namespace osu.Game.Scoring } } - [JsonIgnore] [Column("UserID")] public int? UserID { @@ -147,23 +129,18 @@ namespace osu.Game.Scoring } } - [JsonIgnore] public int BeatmapInfoID { get; set; } - [JsonIgnore] [Column("Beatmap")] - public virtual BeatmapInfo BeatmapInfo { get; set; } + public BeatmapInfo BeatmapInfo { get; set; } - [JsonIgnore] public long? OnlineScoreID { get; set; } - [JsonIgnore] public DateTimeOffset Date { get; set; } - [JsonProperty("statistics")] + [NotMapped] public Dictionary Statistics { get; set; } = new Dictionary(); - [JsonIgnore] [Column("Statistics")] public string StatisticsJson { @@ -181,29 +158,23 @@ namespace osu.Game.Scoring } [NotMapped] - [JsonIgnore] public List HitEvents { get; set; } - [JsonIgnore] public List Files { get; set; } - [JsonIgnore] public string Hash { get; set; } - [JsonIgnore] public bool DeletePending { get; set; } /// /// The position of this score, starting at 1. /// [NotMapped] - [JsonProperty("position")] - public int? Position { get; set; } + public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone. /// /// Whether this represents a legacy (osu!stable) score. /// - [JsonIgnore] [NotMapped] public bool IsLegacyScore => Mods.OfType().Any(); From 3f030cebf4b2e36ff4d375a2a182d87e9c12eeb7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 14:14:25 +0900 Subject: [PATCH 05/15] Remove local score copying in `GetScoresRequest` to allow `APIScoreInfo.Beatmap` to be `APIBeatmap` --- .../Online/API/Requests/GetScoresRequest.cs | 22 ---------------- .../API/Requests/Responses/APIScoreInfo.cs | 25 ++++++------------- .../Responses/APIScoreWithPosition.cs | 5 ++-- .../BeatmapSet/Scores/ScoresContainer.cs | 4 +-- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 4 +-- 6 files changed, 16 insertions(+), 46 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 9037869c77..790a247ccb 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -9,7 +9,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; -using System.Diagnostics; namespace osu.Game.Online.API.Requests { @@ -32,27 +31,6 @@ namespace osu.Game.Online.API.Requests this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); this.mods = mods ?? Array.Empty(); - - Success += onSuccess; - } - - private void onSuccess(APIScoresCollection r) - { - Debug.Assert(ruleset.OnlineID >= 0); - - foreach (APIScoreInfo score in r.Scores) - { - score.Beatmap = beatmapInfo; - score.OnlineRulesetID = ruleset.OnlineID; - } - - var userScore = r.UserScore; - - if (userScore != null) - { - userScore.Score.Beatmap = beatmapInfo; - userScore.Score.OnlineRulesetID = ruleset.OnlineID; - } } protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}"; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 568c6bea7f..957d5991c8 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -39,7 +39,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset Date { get; set; } [JsonProperty(@"beatmap")] - public IBeatmapInfo Beatmap { get; set; } + public APIBeatmap Beatmap { get; set; } [JsonProperty("accuracy")] public double Accuracy { get; set; } @@ -71,10 +71,8 @@ namespace osu.Game.Online.API.Requests.Responses [JsonConverter(typeof(StringEnumConverter))] public ScoreRank Rank { get; set; } - IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - - // TODO: nuke - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + // TODO: This function will eventually be going away. + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { var ruleset = rulesets.GetRuleset(OnlineRulesetID); @@ -94,7 +92,6 @@ namespace osu.Game.Online.API.Requests.Responses OnlineScoreID = OnlineID, Date = Date, PP = PP, - BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, @@ -102,6 +99,9 @@ namespace osu.Game.Online.API.Requests.Responses Mods = mods, }; + if (beatmap != null) + scoreInfo.BeatmapInfo = beatmap; + if (Statistics != null) { foreach (var kvp in Statistics) @@ -138,19 +138,10 @@ namespace osu.Game.Online.API.Requests.Responses return scoreInfo; } - [JsonProperty(@"beatmapset")] - public APIBeatmapSet Metadata - { - set - { - // in the deserialisation case we need to ferry this data across. - if (Beatmap is APIBeatmap apiBeatmap) - apiBeatmap.BeatmapSet = value; - } - } - public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID }; + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + Dictionary IScoreInfo.Statistics => new Dictionary(); // TODO: implement... maybe. hitresults have weird mappings per ruleset it would seem. } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 89bb416393..a0fc549d98 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; +using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -15,10 +16,10 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"score")] public APIScoreInfo Score; - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var score = Score.CreateScoreInfo(rulesets); + var score = Score.CreateScoreInfo(rulesets, beatmap); score.Position = Position; return score; } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 3a3566aad9..c6eb516c7c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (value?.Scores.Any() != true) return; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Show(); var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value); topScoresContainer.Add(new DrawableTopScore(topScore)); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 684c7574cb..4f4dfa4909 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 07300635aa..de1ba5cf2e 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -192,14 +192,14 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, BeatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) return; scoresCallback?.Invoke(ordered.Result); - TopScore = r.UserScore?.CreateScoreInfo(rulesets); + TopScore = r.UserScore?.CreateScoreInfo(rulesets, BeatmapInfo); }), TaskContinuationOptions.OnlyOnRanToCompletion); }; From cd7bd935f6e4c4512a42f8a256d774f50bb1fff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 14:18:10 +0900 Subject: [PATCH 06/15] Remove `Statistics` from interface until we figure how to properly deserialise --- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 3 --- osu.Game/Scoring/IScoreInfo.cs | 6 ++---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 957d5991c8..b996d21ad3 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -9,7 +9,6 @@ using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Users; @@ -141,7 +140,5 @@ namespace osu.Game.Online.API.Requests.Responses public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID }; IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - - Dictionary IScoreInfo.Statistics => new Dictionary(); // TODO: implement... maybe. hitresults have weird mappings per ruleset it would seem. } } diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index b230babf3c..171964206d 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; using osu.Game.Users; namespace osu.Game.Scoring @@ -29,13 +27,13 @@ namespace osu.Game.Scoring IBeatmapInfo Beatmap { get; } - Dictionary Statistics { get; } - IRulesetInfo Ruleset { get; } public ScoreRank Rank { get; } // Mods is currently missing from this interface as the `IMod` class has properties which can't be fulfilled by `APIMod`, // but also doesn't expose `Settings`. We can consider how to implement this in the future if required. + + // Statistics is also missing. This can be reconsidered once changes in serialisation have been completed. } } From f68d6dbc8f3c0665468dce17ccf1f8f7ee6d4224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 14:32:31 +0900 Subject: [PATCH 07/15] Update score submission serialisation tests to use `SubmittableScore` --- .../Online/TestAPIModJsonSerialization.cs | 23 +++--- .../Online/Solo/SubmitSoloScoreRequest.cs | 58 -------------- osu.Game/Online/Solo/SubmittableScore.cs | 75 +++++++++++++++++++ 3 files changed, 84 insertions(+), 72 deletions(-) create mode 100644 osu.Game/Online/Solo/SubmittableScore.cs diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index ad2007f202..c8848ab7d8 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; +using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -88,33 +89,27 @@ namespace osu.Game.Tests.Online } [Test] - public void TestDeserialiseScoreInfoWithEmptyMods() + public void TestDeserialiseSubmittableScoreWithEmptyMods() { - var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); - - if (deserialised != null) - deserialised.Ruleset = new OsuRuleset().RulesetInfo; + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That(deserialised?.Mods.Length, Is.Zero); } [Test] - public void TestDeserialiseScoreInfoWithCustomModSetting() + public void TestDeserialiseSubmittableScoreWithCustomModSetting() { - var score = new ScoreInfo + var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } - }; + }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); - if (deserialised != null) - deserialised.Ruleset = new OsuRuleset().RulesetInfo; - - Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2)); + Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } private class TestRuleset : Ruleset diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 184dbc911d..25c2e5a61f 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -1,17 +1,12 @@ // 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.Net.Http; using Newtonsoft.Json; -using Newtonsoft.Json.Converters; using osu.Framework.IO.Network; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -using osu.Game.Users; namespace osu.Game.Online.Solo { @@ -47,57 +42,4 @@ namespace osu.Game.Online.Solo protected override string Target => $@"beatmaps/{beatmapId}/solo/scores/{scoreId}"; } - - /// - /// A class specifically for sending scores to the API during score submission. - /// This is used instead of due to marginally different serialisation naming requirements. - /// - public class SubmittableScore - { - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; } - - [JsonProperty("total_score")] - public long TotalScore { get; } - - [JsonProperty("accuracy")] - public double Accuracy { get; } - - [JsonProperty(@"pp")] - public double? PP { get; } - - [JsonProperty("max_combo")] - public int MaxCombo { get; } - - [JsonProperty("ruleset_id")] - public int RulesetID { get; } - - [JsonProperty("passed")] - public bool Passed { get; } - - // Used for API serialisation/deserialisation. - [JsonProperty("mods")] - public APIMod[] Mods { get; } - - [JsonProperty("user")] - public User User { get; } - - [JsonProperty("statistics")] - public Dictionary Statistics { get; } - - public SubmittableScore(ScoreInfo score) - { - Rank = score.Rank; - TotalScore = score.TotalScore; - Accuracy = score.Accuracy; - PP = score.PP; - MaxCombo = score.MaxCombo; - RulesetID = score.RulesetID; - Passed = score.Passed; - Mods = score.APIMods; - User = score.User; - Statistics = score.Statistics; - } - } } diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs new file mode 100644 index 0000000000..bafb308a13 --- /dev/null +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.Solo +{ + /// + /// A class specifically for sending scores to the API during score submission. + /// This is used instead of due to marginally different serialisation naming requirements. + /// + [Serializable] + public class SubmittableScore + { + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + [JsonProperty("total_score")] + public long TotalScore { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty("ruleset_id")] + public int RulesetID { get; set; } + + [JsonProperty("passed")] + public bool Passed { get; set; } + + // Used for API serialisation/deserialisation. + [JsonProperty("mods")] + public APIMod[] Mods { get; set; } + + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } + + [UsedImplicitly] + public SubmittableScore() + { + } + + public SubmittableScore(ScoreInfo score) + { + Rank = score.Rank; + TotalScore = score.TotalScore; + Accuracy = score.Accuracy; + PP = score.PP; + MaxCombo = score.MaxCombo; + RulesetID = score.RulesetID; + Passed = score.Passed; + Mods = score.APIMods; + User = score.User; + Statistics = score.Statistics; + } + } +} From 06b6bcfd29a355b8a7ca89d2f2febafc74ca98d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Oct 2021 14:51:06 +0900 Subject: [PATCH 08/15] Add xmldoc for `CreateScoreInfo` function for now I don't actually know how temporary this one is going to be. The usages are quite deep - ie. converting to a `ScoreInfo` to get a calculated total score for ordering purposes. --- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index b996d21ad3..4abb227414 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -70,7 +70,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonConverter(typeof(StringEnumConverter))] public ScoreRank Rank { get; set; } - // TODO: This function will eventually be going away. + /// + /// Create a from an API score instance. + /// + /// A ruleset store, used to populate a ruleset instance in the returned score. + /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). + /// public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { var ruleset = rulesets.GetRuleset(OnlineRulesetID); From e9ba1ea1989b3dd03a4703a069df2ff9939b0714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 30 Oct 2021 15:08:45 +0200 Subject: [PATCH 09/15] Mark `IScoreInfo` implementation with region --- osu.Game/Scoring/ScoreInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 0efce4686c..36608e2a74 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -250,8 +250,12 @@ namespace osu.Game.Scoring #endregion + #region Implementation of IScoreInfo + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IRulesetInfo IScoreInfo.Ruleset => Ruleset; bool IScoreInfo.HasReplay => Files.Any(); + + #endregion } } From b63a90966ba260c5122203c4b06299d2af40aa52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Oct 2021 23:49:26 +0900 Subject: [PATCH 10/15] Remove misplaced access modifier in interface specification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Scoring/IScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 171964206d..77579f23d9 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -29,7 +29,7 @@ namespace osu.Game.Scoring IRulesetInfo Ruleset { get; } - public ScoreRank Rank { get; } + ScoreRank Rank { get; } // Mods is currently missing from this interface as the `IMod` class has properties which can't be fulfilled by `APIMod`, // but also doesn't expose `Settings`. We can consider how to implement this in the future if required. From 269a8df0ecff144c4052c3640767aa98632e2e07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Nov 2021 13:20:37 +0900 Subject: [PATCH 11/15] Fix `HasReplay` not being corrrectly implemented by `APIScoreInfo` --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index b84ac6c2f1..288da71ba3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay { OnlineID = 2553163309, OnlineRulesetID = 0, - Replay = replayAvailable, + HasReplay = replayAvailable, User = new User { Id = 39828, diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 4abb227414..1057819c6d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -26,13 +26,11 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"user")] public User User { get; set; } - public bool HasReplay { get; set; } - [JsonProperty(@"id")] public long OnlineID { get; set; } [JsonProperty(@"replay")] - public bool Replay { get; set; } + public bool HasReplay { get; set; } [JsonProperty(@"created_at")] public DateTimeOffset Date { get; set; } @@ -97,7 +95,7 @@ namespace osu.Game.Online.API.Requests.Responses Date = Date, PP = PP, RulesetID = OnlineRulesetID, - Hash = Replay ? "online" : string.Empty, // todo: temporary? + Hash = HasReplay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, From 708b57348dfb2c1ef369c8a260875a10c7e11cb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Nov 2021 13:22:16 +0900 Subject: [PATCH 12/15] Change loose api ordering requirement to throw instead --- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 1057819c6d..3d82f91fb5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -50,8 +50,11 @@ namespace osu.Game.Online.API.Requests.Responses set { // in the deserialisation case we need to ferry this data across. - if (Beatmap is APIBeatmap apiBeatmap) - apiBeatmap.BeatmapSet = value; + // the order of properties returned by the API guarantees that the beatmap is populated by this point. + if (!(Beatmap is APIBeatmap apiBeatmap)) + throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); + + apiBeatmap.BeatmapSet = value; } } From 94dce3f92acf5ff156afd7cefe0b3ad609e8f2ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Nov 2021 15:43:19 +0900 Subject: [PATCH 13/15] Remove whitespace --- osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index a0fc549d98..48b7134901 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -17,7 +17,6 @@ namespace osu.Game.Online.API.Requests.Responses public APIScoreInfo Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) - { var score = Score.CreateScoreInfo(rulesets, beatmap); score.Position = Position; From 722e0d50bb9c2602c82c81b182c9dca4e51373ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Nov 2021 15:54:39 +0900 Subject: [PATCH 14/15] Fix `BeatmapInfo` not being correctly populated in `CreateScoreInfo` call --- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 3d82f91fb5..1b66a1dcc3 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -92,6 +92,7 @@ namespace osu.Game.Online.API.Requests.Responses { TotalScore = TotalScore, MaxCombo = MaxCombo, + BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), User = User, Accuracy = Accuracy, OnlineScoreID = OnlineID, From 6a44cf3ff1181311d6acffd769793838505a3f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 Nov 2021 09:14:02 +0100 Subject: [PATCH 15/15] Add missing beatmap spec in test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 288da71ba3..311c3ddc03 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -13,6 +13,7 @@ using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking; using osuTK.Input; @@ -136,6 +137,7 @@ namespace osu.Game.Tests.Visual.Gameplay { OnlineID = 2553163309, OnlineRulesetID = 0, + Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), HasReplay = replayAvailable, User = new User {