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.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5e2374cbcb..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; @@ -132,11 +133,12 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreInfo getScoreInfo(bool replayAvailable) { - return new APILegacyScoreInfo + return new APIScoreInfo { - OnlineScoreID = 2553163309, + OnlineID = 2553163309, OnlineRulesetID = 0, - Replay = replayAvailable, + Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), + HasReplay = replayAvailable, User = new User { Id = 39828, 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..790a247ccb 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -9,11 +9,10 @@ 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 { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmapInfo; private readonly BeatmapLeaderboardScope scope; @@ -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(APILegacyScores r) - { - Debug.Assert(ruleset.ID != null, "ruleset.ID != null"); - - foreach (APILegacyScoreInfo score in r.Scores) - { - score.BeatmapInfo = beatmapInfo; - score.OnlineRulesetID = ruleset.ID.Value; - } - - var userScore = r.UserScore; - - if (userScore != null) - { - userScore.Score.BeatmapInfo = beatmapInfo; - userScore.Score.OnlineRulesetID = ruleset.ID.Value; - } } protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}"; 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/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs deleted file mode 100644 index 009639c1dc..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.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 - { - [JsonProperty(@"position")] - public int? Position; - - [JsonProperty(@"score")] - public APILegacyScoreInfo Score; - - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) - { - var score = Score.CreateScoreInfo(rulesets); - score.Position = Position; - return score; - } - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs similarity index 67% rename from osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index aaf2dccc82..1b66a1dcc3 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -15,9 +15,69 @@ using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APILegacyScoreInfo + public class APIScoreInfo : IScoreInfo { - public ScoreInfo CreateScoreInfo(RulesetStore rulesets) + [JsonProperty(@"score")] + public long TotalScore { get; set; } + + [JsonProperty(@"max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty(@"user")] + public User User { get; set; } + + [JsonProperty(@"id")] + public long OnlineID { get; set; } + + [JsonProperty(@"replay")] + public bool HasReplay { get; set; } + + [JsonProperty(@"created_at")] + public DateTimeOffset Date { get; set; } + + [JsonProperty(@"beatmap")] + public APIBeatmap 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. + // 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; + } + } + + [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; } + + /// + /// 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); @@ -32,19 +92,22 @@ namespace osu.Game.Online.API.Requests.Responses { TotalScore = TotalScore, MaxCombo = MaxCombo, + BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), User = User, Accuracy = Accuracy, - OnlineScoreID = OnlineScoreID, + OnlineScoreID = OnlineID, Date = Date, PP = PP, - BeatmapInfo = BeatmapInfo, RulesetID = OnlineRulesetID, - Hash = Replay ? "online" : string.Empty, // todo: temporary? + Hash = HasReplay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, Mods = mods, }; + if (beatmap != null) + scoreInfo.BeatmapInfo = beatmap; + if (Statistics != null) { foreach (var kvp in Statistics) @@ -81,57 +144,8 @@ namespace osu.Game.Online.API.Requests.Responses return scoreInfo; } - [JsonProperty(@"score")] - public int TotalScore { get; set; } + public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID }; - [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 - { - set - { - // extract the set ID to its correct place. - BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID }; - value.ID = 0; - - BeatmapInfo.Metadata = 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; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs new file mode 100644 index 0000000000..48b7134901 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -0,0 +1,26 @@ +// 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; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScoreWithPosition + { + [JsonProperty(@"position")] + public int? Position; + + [JsonProperty(@"score")] + public APIScoreInfo Score; + + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + { + var score = Score.CreateScoreInfo(rulesets, beatmap); + score.Position = Position; + return score; + } + } +} 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/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 85fa3eeb34..25c2e5a61f 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -16,13 +16,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 +32,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 })); 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; + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 82657afc86..c6eb516c7c 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(() => { @@ -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/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) { diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs new file mode 100644 index 0000000000..77579f23d9 --- /dev/null +++ b/osu.Game/Scoring/IScoreInfo.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; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Users; + +namespace osu.Game.Scoring +{ + public interface IScoreInfo : IHasOnlineID + { + User User { get; } + + long TotalScore { get; } + + int MaxCombo { get; } + + double Accuracy { get; } + + bool HasReplay { get; } + + DateTimeOffset Date { get; } + + double? PP { get; } + + IBeatmapInfo Beatmap { get; } + + IRulesetInfo Ruleset { 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. + + // Statistics is also missing. This can be reconsidered once changes in serialisation have been completed. + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ed3b27dd22..36608e2a74 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; @@ -19,47 +18,36 @@ 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; } - [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 virtual RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; set; } private APIMod[] localAPIMods; + private Mod[] mods; - [JsonIgnore] [NotMapped] public Mod[] Mods { @@ -74,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; } @@ -86,9 +74,8 @@ namespace osu.Game.Scoring } // Used for API serialisation/deserialisation. - [JsonProperty("mods")] [NotMapped] - private APIMod[] apiMods + public APIMod[] APIMods { get { @@ -110,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 { @@ -134,7 +118,6 @@ namespace osu.Game.Scoring } } - [JsonIgnore] [Column("UserID")] public int? UserID { @@ -146,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")] - public Dictionary Statistics = new Dictionary(); + [NotMapped] + public Dictionary Statistics { get; set; } = new Dictionary(); - [JsonIgnore] [Column("Statistics")] public string StatisticsJson { @@ -180,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(); @@ -277,5 +249,13 @@ namespace osu.Game.Scoring public long OnlineID => OnlineScoreID ?? -1; #endregion + + #region Implementation of IScoreInfo + + IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; + IRulesetInfo IScoreInfo.Ruleset => Ruleset; + bool IScoreInfo.HasReplay => Files.Any(); + + #endregion } } diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 5e582a8dcb..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.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, 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); };