diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index ab88be1511c1..4e281cf28ecf 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -219,6 +219,8 @@ public void TestSoloScoreData() { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } }; + scoreInfo.OnlineID = 123123; + scoreInfo.ClientVersion = "2023.1221.0"; var beatmap = new TestBeatmap(ruleset); var score = new Score @@ -237,9 +239,11 @@ public void TestSoloScoreData() Assert.Multiple(() => { + Assert.That(decodedAfterEncode.ScoreInfo.OnlineID, Is.EqualTo(123123)); Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics)); Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics)); Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods)); + Assert.That(decodedAfterEncode.ScoreInfo.ClientVersion, Is.EqualTo("2023.1221.0")); }); } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index dd724d268ece..ebbc329b9da0 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -196,6 +196,7 @@ public void TestImportMods() User = new APIUser { Username = "Test user" }, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = new OsuRuleset().RulesetInfo, + ClientVersion = "12345", Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, }; @@ -203,6 +204,7 @@ public void TestImportMods() Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); + Assert.That(imported.ClientVersion, Is.EqualTo(toImport.ClientVersion)); } finally { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 0dd544bb3015..fafd1330ccdd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -41,6 +41,9 @@ public partial class TestScenePlayerLocalScoreImport : PlayerTestScene private BeatmapSetInfo? importedSet; + [Resolved] + private OsuGameBase osu { get; set; } = null!; + [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { @@ -153,6 +156,7 @@ public void TestScoreStoredLocally() AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + AddUntilStep("score has correct version", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID)!.ClientVersion), () => Is.EqualTo(osu.Version)); } [Test] diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index ad61292c2e66..4bd7f36cddc3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -90,8 +90,9 @@ public class RealmAccess : IDisposable /// 36 2023-10-26 Add LegacyOnlineID to ScoreInfo. Move osu_scores_*_high IDs stored in OnlineID to LegacyOnlineID. Reset anomalous OnlineIDs. /// 38 2023-12-10 Add EndTimeObjectCount and TotalObjectCount to BeatmapInfo. /// 39 2023-12-19 Migrate any EndTimeObjectCount and TotalObjectCount values of 0 to -1 to better identify non-calculated values. + /// 40 2023-12-21 Add ScoreInfo.Version to keep track of which build scores were set on. /// - private const int schema_version = 39; + private const int schema_version = 40; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs index d34edf7bdfeb..2c5b91f10f71 100644 --- a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -35,12 +35,16 @@ public class LegacyReplaySoloScoreInfo [JsonProperty("maximum_statistics")] public Dictionary MaximumStatistics { get; set; } = new Dictionary(); + [JsonProperty("client_version")] + public string ClientVersion = string.Empty; + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo { OnlineID = score.OnlineID, Mods = score.APIMods, Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + ClientVersion = score.ClientVersion, }; } } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index c5e6e3bcce8c..ed11691674b4 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -125,6 +125,7 @@ public Score Parse(Stream stream) score.ScoreInfo.Statistics = readScore.Statistics; score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); + score.ScoreInfo.ClientVersion = readScore.ClientVersion; }); } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5545ba552e61..7071bd380e7b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -46,6 +46,12 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD /// public BeatmapInfo? BeatmapInfo { get; set; } + /// + /// The version of the client this score was set using. + /// Sourced from at the point of score submission. + /// + public string ClientVersion { get; set; } = string.Empty; + /// /// The at the point in time when the score was set. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cc08079d88a7..c9251b0a7850 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -109,6 +109,9 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb [Resolved] private MusicController musicController { get; set; } + [Resolved] + private OsuGameBase game { get; set; } + public GameplayState GameplayState { get; private set; } private Ruleset ruleset; @@ -1155,7 +1158,11 @@ public override bool OnExiting(ScreenExitEvent e) /// The . protected virtual Score CreateScore(IBeatmap beatmap) => new Score { - ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, + ScoreInfo = new ScoreInfo + { + User = api.LocalUser.Value, + ClientVersion = game.Version, + }, }; ///