1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 18:32:55 +08:00

Merge branch 'beatmap-refactor/get-and-present' into beatmap-refactor/beatmap-overlays

This commit is contained in:
Dean Herbert 2021-10-29 18:01:17 +09:00
commit 8ad33d43d0
22 changed files with 302 additions and 212 deletions

View File

@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Solo;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -88,33 +89,27 @@ namespace osu.Game.Tests.Online
} }
[Test] [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<ScoreInfo>(JsonConvert.SerializeObject(score)); var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
if (deserialised != null)
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
Assert.That(deserialised?.Mods.Length, Is.Zero); Assert.That(deserialised?.Mods.Length, Is.Zero);
} }
[Test] [Test]
public void TestDeserialiseScoreInfoWithCustomModSetting() public void TestDeserialiseSubmittableScoreWithCustomModSetting()
{ {
var score = new ScoreInfo var score = new SubmittableScore(new ScoreInfo
{ {
Ruleset = new OsuRuleset().RulesetInfo, Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
}; });
var deserialised = JsonConvert.DeserializeObject<ScoreInfo>(JsonConvert.SerializeObject(score)); var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
if (deserialised != null) Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2));
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
Assert.That(((OsuModDoubleTime)deserialised?.Mods[0])?.SpeedChange.Value, Is.EqualTo(2));
} }
private class TestRuleset : Ruleset private class TestRuleset : Ruleset

View File

@ -132,9 +132,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private ScoreInfo getScoreInfo(bool replayAvailable) private ScoreInfo getScoreInfo(bool replayAvailable)
{ {
return new APILegacyScoreInfo return new APIScoreInfo
{ {
OnlineScoreID = 2553163309, OnlineID = 2553163309,
OnlineRulesetID = 0, OnlineRulesetID = 0,
Replay = replayAvailable, Replay = replayAvailable,
User = new User User = new User

View File

@ -43,11 +43,11 @@ namespace osu.Game.Tests.Visual.Online
} }
}; };
var allScores = new APILegacyScores var allScores = new APIScoresCollection
{ {
Scores = new List<APILegacyScoreInfo> Scores = new List<APIScoreInfo>
{ {
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234567890, TotalScore = 1234567890,
Accuracy = 1, Accuracy = 1,
}, },
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234789, TotalScore = 1234789,
Accuracy = 0.9997, Accuracy = 0.9997,
}, },
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 12345678, TotalScore = 12345678,
Accuracy = 0.9854, Accuracy = 0.9854,
}, },
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Visual.Online
TotalScore = 1234567, TotalScore = 1234567,
Accuracy = 0.8765, Accuracy = 0.8765,
}, },
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User 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 User = new User
{ {
@ -185,9 +185,9 @@ namespace osu.Game.Tests.Visual.Online
Position = 1337, Position = 1337,
}; };
var myBestScoreWithNullPosition = new APILegacyUserTopScoreInfo var myBestScoreWithNullPosition = new APIScoreWithPosition
{ {
Score = new APILegacyScoreInfo Score = new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -208,11 +208,11 @@ namespace osu.Game.Tests.Visual.Online
Position = null, Position = null,
}; };
var oneScore = new APILegacyScores var oneScore = new APIScoresCollection
{ {
Scores = new List<APILegacyScoreInfo> Scores = new List<APIScoreInfo>
{ {
new APILegacyScoreInfo new APIScoreInfo
{ {
User = new User User = new User
{ {
@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Online
private class TestScoresContainer : ScoresContainer private class TestScoresContainer : ScoresContainer
{ {
public new APILegacyScores Scores public new APIScoresCollection Scores
{ {
set => base.Scores = value; set => base.Scores = value;
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A single beatmap difficulty. /// A single beatmap difficulty.
/// </summary> /// </summary>
public interface IBeatmapInfo : IHasOnlineID public interface IBeatmapInfo : IHasOnlineID<int>
{ {
/// <summary> /// <summary>
/// The user-specified name given to this beatmap. /// The user-specified name given to this beatmap.

View File

@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive. /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
/// </summary> /// </summary>
public interface IBeatmapSetInfo : IHasOnlineID public interface IBeatmapSetInfo : IHasOnlineID<int>
{ {
/// <summary> /// <summary>
/// The date when this beatmap was imported. /// The date when this beatmap was imported.

View File

@ -5,7 +5,7 @@
namespace osu.Game.Database namespace osu.Game.Database
{ {
public interface IHasOnlineID public interface IHasOnlineID<out T>
{ {
/// <summary> /// <summary>
/// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID. /// The server-side ID representing this instance, if one exists. Any value 0 or less denotes a missing ID.
@ -14,6 +14,6 @@ namespace osu.Game.Database
/// Generally we use -1 when specifying "missing" in code, but values of 0 are also considered missing as the online source /// Generally we use -1 when specifying "missing" in code, but values of 0 are also considered missing as the online source
/// is generally a MySQL autoincrement value, which can never be 0. /// is generally a MySQL autoincrement value, which can never be 0.
/// </remarks> /// </remarks>
int OnlineID { get; } T OnlineID { get; }
} }
} }

View File

@ -9,21 +9,20 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System.Text; using System.Text;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetScoresRequest : APIRequest<APILegacyScores> public class GetScoresRequest : APIRequest<APIScoresCollection>
{ {
private readonly BeatmapInfo beatmapInfo; private readonly IBeatmapInfo beatmapInfo;
private readonly BeatmapLeaderboardScope scope; private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset; private readonly IRulesetInfo ruleset;
private readonly IEnumerable<IMod> mods; private readonly IEnumerable<IMod> mods;
public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null) public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
{ {
if (!beatmapInfo.OnlineBeatmapID.HasValue) if (beatmapInfo.OnlineID <= 0)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}.");
if (scope == BeatmapLeaderboardScope.Local) if (scope == BeatmapLeaderboardScope.Local)
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
@ -32,30 +31,9 @@ namespace osu.Game.Online.API.Requests
this.scope = scope; this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
this.mods = mods ?? Array.Empty<IMod>(); this.mods = mods ?? Array.Empty<IMod>();
Success += onSuccess;
} }
private void onSuccess(APILegacyScores r) protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}";
{
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()}";
private string createQueryParameters() private string createQueryParameters()
{ {

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserScoresRequest : PaginatedAPIRequest<List<APILegacyScoreInfo>> public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>>
{ {
private readonly long userId; private readonly long userId;
private readonly ScoreType type; private readonly ScoreType type;

View File

@ -1,35 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<APILegacyScoreInfo> 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;
}
}
}

View File

@ -15,9 +15,68 @@ using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses 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; }
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 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.
if (Beatmap is APIBeatmap apiBeatmap)
apiBeatmap.BeatmapSet = value;
}
}
[JsonProperty("statistics")]
public Dictionary<string, int> 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; }
/// <summary>
/// Create a <see cref="ScoreInfo"/> from an API score instance.
/// </summary>
/// <param name="rulesets">A ruleset store, used to populate a ruleset instance in the returned score.</param>
/// <param name="beatmap">An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap).</param>
/// <returns></returns>
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
{ {
var ruleset = rulesets.GetRuleset(OnlineRulesetID); var ruleset = rulesets.GetRuleset(OnlineRulesetID);
@ -34,10 +93,9 @@ namespace osu.Game.Online.API.Requests.Responses
MaxCombo = MaxCombo, MaxCombo = MaxCombo,
User = User, User = User,
Accuracy = Accuracy, Accuracy = Accuracy,
OnlineScoreID = OnlineScoreID, OnlineScoreID = OnlineID,
Date = Date, Date = Date,
PP = PP, PP = PP,
BeatmapInfo = BeatmapInfo,
RulesetID = OnlineRulesetID, RulesetID = OnlineRulesetID,
Hash = Replay ? "online" : string.Empty, // todo: temporary? Hash = Replay ? "online" : string.Empty, // todo: temporary?
Rank = Rank, Rank = Rank,
@ -45,6 +103,9 @@ namespace osu.Game.Online.API.Requests.Responses
Mods = mods, Mods = mods,
}; };
if (beatmap != null)
scoreInfo.BeatmapInfo = beatmap;
if (Statistics != null) if (Statistics != null)
{ {
foreach (var kvp in Statistics) foreach (var kvp in Statistics)
@ -81,57 +142,8 @@ namespace osu.Game.Online.API.Requests.Responses
return scoreInfo; return scoreInfo;
} }
[JsonProperty(@"score")] public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID };
public int TotalScore { get; set; }
[JsonProperty(@"max_combo")] IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
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<string, int> 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; }
} }
} }

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<APIScoreInfo> Scores;
[JsonProperty(@"userScore")]
public APIScoreWithPosition UserScore;
}
}

View File

@ -16,13 +16,13 @@ namespace osu.Game.Online.Solo
private readonly int beatmapId; private readonly int beatmapId;
private readonly ScoreInfo scoreInfo; private readonly SubmittableScore score;
public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo) public SubmitSoloScoreRequest(int beatmapId, long scoreId, ScoreInfo scoreInfo)
{ {
this.beatmapId = beatmapId; this.beatmapId = beatmapId;
this.scoreId = scoreId; this.scoreId = scoreId;
this.scoreInfo = scoreInfo; score = new SubmittableScore(scoreInfo);
} }
protected override WebRequest CreateWebRequest() protected override WebRequest CreateWebRequest()
@ -32,7 +32,7 @@ namespace osu.Game.Online.Solo
req.ContentType = "application/json"; req.ContentType = "application/json";
req.Method = HttpMethod.Put; req.Method = HttpMethod.Put;
req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
{ {
ReferenceLoopHandling = ReferenceLoopHandling.Ignore ReferenceLoopHandling = ReferenceLoopHandling.Ignore
})); }));

View File

@ -0,0 +1,75 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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
{
/// <summary>
/// A class specifically for sending scores to the API during score submission.
/// This is used instead of <see cref="APIScoreInfo"/> due to marginally different serialisation naming requirements.
/// </summary>
[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<HitResult, int> 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;
}
}
}

View File

@ -436,11 +436,15 @@ namespace osu.Game
/// <item>first beatmap from any ruleset.</item> /// <item>first beatmap from any ruleset.</item>
/// </list> /// </list>
/// </remarks> /// </remarks>
public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null) public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null)
{ {
var databasedSet = beatmap.OnlineBeatmapSetID != null BeatmapSetInfo databasedSet = null;
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); if (beatmap.OnlineID > 0)
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID);
if (beatmap is BeatmapSetInfo localBeatmap)
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash);
if (databasedSet == null) if (databasedSet == null)
{ {

View File

@ -52,7 +52,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private CancellationTokenSource loadCancellationSource; private CancellationTokenSource loadCancellationSource;
protected APILegacyScores Scores protected APIScoresCollection Scores
{ {
set => Schedule(() => set => Schedule(() =>
{ {
@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
if (value?.Scores.Any() != true) if (value?.Scores.Any() != true)
return; 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(() => .ContinueWith(ordered => Schedule(() =>
{ {
if (loadCancellationSource.IsCancellationRequested) if (loadCancellationSource.IsCancellationRequested)
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
scoreTable.Show(); scoreTable.Show();
var userScore = value.UserScore; var userScore = value.UserScore;
var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets); var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value);
topScoresContainer.Add(new DrawableTopScore(topScore)); topScoresContainer.Add(new DrawableTopScore(topScore));

View File

@ -15,7 +15,7 @@ using osu.Framework.Localisation;
namespace osu.Game.Overlays.Profile.Sections.Ranks namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
public class PaginatedScoreContainer : PaginatedProfileSubsection<APILegacyScoreInfo> public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo>
{ {
private readonly ScoreType type; private readonly ScoreType type;
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
} }
} }
protected override void OnItemsReceived(List<APILegacyScoreInfo> items) protected override void OnItemsReceived(List<APIScoreInfo> items)
{ {
if (VisiblePages == 0) if (VisiblePages == 0)
drawableItemIndex = 0; drawableItemIndex = 0;
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
base.OnItemsReceived(items); base.OnItemsReceived(items);
} }
protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() => protected override APIRequest<List<APIScoreInfo>> CreateRequest() =>
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
private int drawableItemIndex; private int drawableItemIndex;
protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) protected override Drawable CreateDrawableItem(APIScoreInfo model)
{ {
switch (type) switch (type)
{ {

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets
/// <summary> /// <summary>
/// A representation of a ruleset's metadata. /// A representation of a ruleset's metadata.
/// </summary> /// </summary>
public interface IRulesetInfo : IHasOnlineID public interface IRulesetInfo : IHasOnlineID<int>
{ {
/// <summary> /// <summary>
/// The user-exposed name of this ruleset. /// The user-exposed name of this ruleset.

View File

@ -0,0 +1,39 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<long>
{
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; }
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.
}
}

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
@ -19,47 +18,36 @@ using osu.Game.Utils;
namespace osu.Game.Scoring namespace osu.Game.Scoring
{ {
public class ScoreInfo : IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo> public class ScoreInfo : IScoreInfo, IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>
{ {
public int ID { get; set; } public int ID { get; set; }
[JsonProperty("rank")]
[JsonConverter(typeof(StringEnumConverter))]
public ScoreRank Rank { get; set; } public ScoreRank Rank { get; set; }
[JsonProperty("total_score")]
public long TotalScore { get; set; } 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. [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; } public double Accuracy { get; set; }
[JsonIgnore]
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy(); public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
[JsonProperty(@"pp")]
public double? PP { get; set; } public double? PP { get; set; }
[JsonProperty("max_combo")]
public int MaxCombo { get; set; } public int MaxCombo { get; set; }
[JsonIgnore]
public int Combo { get; set; } // Todo: Shouldn't exist in here public int Combo { get; set; } // Todo: Shouldn't exist in here
[JsonProperty("ruleset_id")]
public int RulesetID { get; set; } public int RulesetID { get; set; }
[JsonProperty("passed")]
[NotMapped] [NotMapped]
public bool Passed { get; set; } = true; public bool Passed { get; set; } = true;
[JsonIgnore] public RulesetInfo Ruleset { get; set; }
public virtual RulesetInfo Ruleset { get; set; }
private APIMod[] localAPIMods; private APIMod[] localAPIMods;
private Mod[] mods; private Mod[] mods;
[JsonIgnore]
[NotMapped] [NotMapped]
public Mod[] Mods public Mod[] Mods
{ {
@ -74,7 +62,7 @@ namespace osu.Game.Scoring
if (mods != null) if (mods != null)
scoreMods = mods; scoreMods = mods;
else if (localAPIMods != null) else if (localAPIMods != null)
scoreMods = apiMods.Select(m => m.ToMod(rulesetInstance)).ToArray(); scoreMods = APIMods.Select(m => m.ToMod(rulesetInstance)).ToArray();
return scoreMods; return scoreMods;
} }
@ -86,9 +74,8 @@ namespace osu.Game.Scoring
} }
// Used for API serialisation/deserialisation. // Used for API serialisation/deserialisation.
[JsonProperty("mods")]
[NotMapped] [NotMapped]
private APIMod[] apiMods public APIMod[] APIMods
{ {
get get
{ {
@ -110,19 +97,16 @@ namespace osu.Game.Scoring
} }
// Used for database serialisation/deserialisation. // Used for database serialisation/deserialisation.
[JsonIgnore]
[Column("Mods")] [Column("Mods")]
public string ModsJson public string ModsJson
{ {
get => JsonConvert.SerializeObject(apiMods); get => JsonConvert.SerializeObject(APIMods);
set => apiMods = JsonConvert.DeserializeObject<APIMod[]>(value); set => APIMods = JsonConvert.DeserializeObject<APIMod[]>(value);
} }
[NotMapped] [NotMapped]
[JsonProperty("user")]
public User User { get; set; } public User User { get; set; }
[JsonIgnore]
[Column("User")] [Column("User")]
public string UserString public string UserString
{ {
@ -134,7 +118,6 @@ namespace osu.Game.Scoring
} }
} }
[JsonIgnore]
[Column("UserID")] [Column("UserID")]
public int? UserID public int? UserID
{ {
@ -146,23 +129,18 @@ namespace osu.Game.Scoring
} }
} }
[JsonIgnore]
public int BeatmapInfoID { get; set; } public int BeatmapInfoID { get; set; }
[JsonIgnore]
[Column("Beatmap")] [Column("Beatmap")]
public virtual BeatmapInfo BeatmapInfo { get; set; } public BeatmapInfo BeatmapInfo { get; set; }
[JsonIgnore]
public long? OnlineScoreID { get; set; } public long? OnlineScoreID { get; set; }
[JsonIgnore]
public DateTimeOffset Date { get; set; } public DateTimeOffset Date { get; set; }
[JsonProperty("statistics")] [NotMapped]
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>(); public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
[JsonIgnore]
[Column("Statistics")] [Column("Statistics")]
public string StatisticsJson public string StatisticsJson
{ {
@ -180,29 +158,23 @@ namespace osu.Game.Scoring
} }
[NotMapped] [NotMapped]
[JsonIgnore]
public List<HitEvent> HitEvents { get; set; } public List<HitEvent> HitEvents { get; set; }
[JsonIgnore]
public List<ScoreFileInfo> Files { get; set; } public List<ScoreFileInfo> Files { get; set; }
[JsonIgnore]
public string Hash { get; set; } public string Hash { get; set; }
[JsonIgnore]
public bool DeletePending { get; set; } public bool DeletePending { get; set; }
/// <summary> /// <summary>
/// The position of this score, starting at 1. /// The position of this score, starting at 1.
/// </summary> /// </summary>
[NotMapped] [NotMapped]
[JsonProperty("position")] public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone.
public int? Position { get; set; }
/// <summary> /// <summary>
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score. /// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary> /// </summary>
[JsonIgnore]
[NotMapped] [NotMapped]
public bool IsLegacyScore => Mods.OfType<ModClassic>().Any(); public bool IsLegacyScore => Mods.OfType<ModClassic>().Any();
@ -271,5 +243,11 @@ namespace osu.Game.Scoring
return ReferenceEquals(this, other); return ReferenceEquals(this, other);
} }
public long OnlineID => OnlineScoreID ?? -1;
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
IRulesetInfo IScoreInfo.Ruleset => Ruleset;
bool IScoreInfo.HasReplay => Files.Any();
} }
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking
return null; return null;
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); 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; return getScoreRequest;
} }

View File

@ -192,14 +192,14 @@ namespace osu.Game.Screens.Select.Leaderboards
req.Success += r => 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(() => .ContinueWith(ordered => Schedule(() =>
{ {
if (loadCancellationSource.IsCancellationRequested) if (loadCancellationSource.IsCancellationRequested)
return; return;
scoresCallback?.Invoke(ordered.Result); scoresCallback?.Invoke(ordered.Result);
TopScore = r.UserScore?.CreateScoreInfo(rulesets); TopScore = r.UserScore?.CreateScoreInfo(rulesets, BeatmapInfo);
}), TaskContinuationOptions.OnlyOnRanToCompletion); }), TaskContinuationOptions.OnlyOnRanToCompletion);
}; };