1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-15 17:47:18 +08:00

Merge pull request #15351 from peppy/score-refactor/isolated-serialisation

Clean up score serialisation
This commit is contained in:
Dan Balasescu 2021-11-01 18:32:09 +09:00 committed by GitHub
commit b4b7d6945b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 290 additions and 199 deletions

View File

@ -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<ScoreInfo>(JsonConvert.SerializeObject(score));
if (deserialised != null)
deserialised.Ruleset = new OsuRuleset().RulesetInfo;
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(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<ScoreInfo>(JsonConvert.SerializeObject(score));
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(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

View File

@ -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,

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
{
@ -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<APILegacyScoreInfo>
Scores = new List<APIScoreInfo>
{
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;
}

View File

@ -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<APILegacyScores>
public class GetScoresRequest : APIRequest<APIScoresCollection>
{
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<IMod>();
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()}";

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class GetUserScoresRequest : PaginatedAPIRequest<List<APILegacyScoreInfo>>
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>>
{
private readonly long userId;
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,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<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);
@ -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<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; }
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
}
}

View File

@ -0,0 +1,26 @@
// 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 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
}));

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

@ -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));

View File

@ -15,7 +15,7 @@ using osu.Framework.Localisation;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public class PaginatedScoreContainer : PaginatedProfileSubsection<APILegacyScoreInfo>
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo>
{
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)
drawableItemIndex = 0;
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
base.OnItemsReceived(items);
}
protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() =>
protected override APIRequest<List<APIScoreInfo>> 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)
{

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; }
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.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<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>, IHasOnlineID<long>
public class ScoreInfo : IScoreInfo, IHasFiles<ScoreFileInfo>, IHasPrimaryKey, ISoftDelete, IEquatable<ScoreInfo>, IDeepCloneable<ScoreInfo>
{
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<APIMod[]>(value);
get => JsonConvert.SerializeObject(APIMods);
set => APIMods = JsonConvert.DeserializeObject<APIMod[]>(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<HitResult, int> Statistics = new Dictionary<HitResult, int>();
[NotMapped]
public Dictionary<HitResult, int> Statistics { get; set; } = new Dictionary<HitResult, int>();
[JsonIgnore]
[Column("Statistics")]
public string StatisticsJson
{
@ -180,29 +158,23 @@ namespace osu.Game.Scoring
}
[NotMapped]
[JsonIgnore]
public List<HitEvent> HitEvents { get; set; }
[JsonIgnore]
public List<ScoreFileInfo> Files { get; set; }
[JsonIgnore]
public string Hash { get; set; }
[JsonIgnore]
public bool DeletePending { get; set; }
/// <summary>
/// The position of this score, starting at 1.
/// </summary>
[NotMapped]
[JsonProperty("position")]
public int? Position { get; set; }
public int? Position { get; set; } // TODO: remove after all calls to `CreateScoreInfo` are gone.
/// <summary>
/// Whether this <see cref="ScoreInfo"/> represents a legacy (osu!stable) score.
/// </summary>
[JsonIgnore]
[NotMapped]
public bool IsLegacyScore => Mods.OfType<ModClassic>().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
}
}

View File

@ -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;
}

View File

@ -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);
};