From ea6ac6c23d2ec66f5be1e959ca804484f582dc13 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 30 Jan 2024 14:12:55 +0900 Subject: [PATCH 1/4] Add support for computing performance of non-legacy scores --- PerformanceCalculator/ApiCommand.cs | 11 ++- .../Difficulty/DifficultyCommand.cs | 2 +- .../Leaderboard/LeaderboardCommand.cs | 2 +- PerformanceCalculator/LegacyHelper.cs | 94 ++++++++++++------- .../LegacyScorePerformanceCommand.cs | 17 ++++ .../Performance/ReplayPerformanceCommand.cs | 2 +- .../Performance/ScorePerformanceCommand.cs | 76 +++++++++++++-- .../Profile/ProfileCommand.cs | 2 +- .../Simulate/SimulateCommand.cs | 2 +- osu.Tools.sln.DotSettings | 1 + 10 files changed, 158 insertions(+), 51 deletions(-) create mode 100644 PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs diff --git a/PerformanceCalculator/ApiCommand.cs b/PerformanceCalculator/ApiCommand.cs index 4f33c202f..ae6ce5b62 100644 --- a/PerformanceCalculator/ApiCommand.cs +++ b/PerformanceCalculator/ApiCommand.cs @@ -1,6 +1,7 @@ // 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.ComponentModel.DataAnnotations; using System.Globalization; using System.Net.Http; @@ -33,11 +34,19 @@ public override void OnExecute(CommandLineApplication app, IConsole console) base.OnExecute(app, console); } - protected T GetJsonFromApi(string request) + protected T GetJsonFromApi(string request, HttpMethod method = null, Dictionary parameters = null) { using var req = new JsonWebRequest($"{Program.ENDPOINT_CONFIGURATION.APIEndpointUrl}/api/v2/{request}"); + req.Method = method ?? HttpMethod.Get; req.AddHeader("x-api-version", api_version.ToString(CultureInfo.InvariantCulture)); req.AddHeader(System.Net.HttpRequestHeader.Authorization.ToString(), $"Bearer {apiAccessToken}"); + + if (parameters != null) + { + foreach ((string key, string value) in parameters) + req.AddParameter(key, value); + } + req.Perform(); return req.ResponseObject; diff --git a/PerformanceCalculator/Difficulty/DifficultyCommand.cs b/PerformanceCalculator/Difficulty/DifficultyCommand.cs index 22e15c19c..aa27c6507 100644 --- a/PerformanceCalculator/Difficulty/DifficultyCommand.cs +++ b/PerformanceCalculator/Difficulty/DifficultyCommand.cs @@ -126,7 +126,7 @@ private Result processBeatmap(WorkingBeatmap beatmap) { // Get the ruleset var ruleset = LegacyHelper.GetRulesetFromLegacyID(Ruleset ?? beatmap.BeatmapInfo.Ruleset.OnlineID); - var mods = NoClassicMod ? getMods(ruleset) : LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(beatmap.BeatmapInfo, ruleset, getMods(ruleset)); + var mods = NoClassicMod ? getMods(ruleset) : LegacyHelper.FilterDifficultyAdjustmentMods(beatmap.BeatmapInfo, ruleset, getMods(ruleset)); var attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(mods); return new Result diff --git a/PerformanceCalculator/Leaderboard/LeaderboardCommand.cs b/PerformanceCalculator/Leaderboard/LeaderboardCommand.cs index b574ddfff..635efc3ce 100644 --- a/PerformanceCalculator/Leaderboard/LeaderboardCommand.cs +++ b/PerformanceCalculator/Leaderboard/LeaderboardCommand.cs @@ -61,7 +61,7 @@ public override void Execute() var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); - var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(working.BeatmapInfo, ruleset, scoreInfo.Mods).ToArray()); + var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.FilterDifficultyAdjustmentMods(working.BeatmapInfo, ruleset, scoreInfo.Mods).ToArray()); var performanceCalculator = ruleset.CreatePerformanceCalculator(); plays.Add((performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes).Total ?? 0, play.PP ?? 0.0)); diff --git a/PerformanceCalculator/LegacyHelper.cs b/PerformanceCalculator/LegacyHelper.cs index 6bb4fec89..731cea5a7 100644 --- a/PerformanceCalculator/LegacyHelper.cs +++ b/PerformanceCalculator/LegacyHelper.cs @@ -2,20 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.IO; using System.Linq; -using osu.Framework.Audio.Track; -using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Taiko; -using osu.Game.Skinning; -using osu.Game.Utils; +using osu.Game.Rulesets.Taiko.Difficulty; namespace PerformanceCalculator { @@ -63,51 +63,73 @@ public static string GetRulesetShortNameFromId(int id) } } - /// - /// Transforms a given combination into one which is applicable to legacy scores. - /// This is used to match osu!stable/osu!web calculations for the time being, until such a point that these mods do get considered. - /// - public static Mod[] ConvertToLegacyDifficultyAdjustmentMods(BeatmapInfo beatmapInfo, Ruleset ruleset, Mod[] mods) + public const LegacyMods KEY_MODS = LegacyMods.Key1 | LegacyMods.Key2 | LegacyMods.Key3 | LegacyMods.Key4 | LegacyMods.Key5 | LegacyMods.Key6 | LegacyMods.Key7 | LegacyMods.Key8 + | LegacyMods.Key9 | LegacyMods.KeyCoop; + + // See: https://github.com/ppy/osu-performance/blob/83c02f50315a4ef7feea80acb84c66ee437d7210/include/pp/Common.h#L109-L129 + public static LegacyMods MaskRelevantMods(LegacyMods mods, bool isConvertedBeatmap, int rulesetId) { - var allMods = ruleset.CreateAllMods().ToArray(); + LegacyMods relevantMods = LegacyMods.DoubleTime | LegacyMods.HalfTime | LegacyMods.HardRock | LegacyMods.Easy; - var allowedMods = ModUtils.FlattenMods( - ruleset.CreateDifficultyCalculator(new EmptyWorkingBeatmap(beatmapInfo)).CreateDifficultyAdjustmentModCombinations()) - .Select(m => m.GetType()) - .Distinct() - .ToHashSet(); + switch (rulesetId) + { + case 0: + if ((mods & LegacyMods.Flashlight) > 0) + relevantMods |= LegacyMods.Flashlight | LegacyMods.Hidden | LegacyMods.TouchDevice; + else + relevantMods |= LegacyMods.Flashlight | LegacyMods.TouchDevice; + break; - // Special case to allow either DT or NC. - if (allowedMods.Any(type => type.IsSubclassOf(typeof(ModDoubleTime))) && mods.Any(m => m is ModNightcore)) - allowedMods.Add(allMods.Single(m => m is ModNightcore).GetType()); + case 3: + if (isConvertedBeatmap) + relevantMods |= KEY_MODS; + break; + } - var result = new List(); + return mods & relevantMods; + } - var classicMod = allMods.SingleOrDefault(m => m is ModClassic); - if (classicMod != null) - result.Add(classicMod); + /// + /// Transforms a given combination into one which is applicable to legacy scores. + /// This is used to match osu!stable/osu!web calculations for the time being, until such a point that these mods do get considered. + /// + public static LegacyMods ConvertToLegacyDifficultyAdjustmentMods(BeatmapInfo beatmapInfo, Ruleset ruleset, Mod[] mods) + { + var legacyMods = ruleset.ConvertToLegacyMods(mods); - result.AddRange(mods.Where(m => allowedMods.Contains(m.GetType()))); + // mods that are not represented in `LegacyMods` (but we can approximate them well enough with others) + if (mods.Any(mod => mod is ModDaycore)) + legacyMods |= LegacyMods.HalfTime; - return result.ToArray(); + return MaskRelevantMods(legacyMods, ruleset.RulesetInfo.OnlineID != beatmapInfo.Ruleset.OnlineID, ruleset.RulesetInfo.OnlineID); } - private class EmptyWorkingBeatmap : WorkingBeatmap + /// + /// Transforms a given combination into one which is applicable to legacy scores. + /// This is used to match osu!stable/osu!web calculations for the time being, until such a point that these mods do get considered. + /// + public static Mod[] FilterDifficultyAdjustmentMods(BeatmapInfo beatmapInfo, Ruleset ruleset, Mod[] mods) + => ruleset.ConvertFromLegacyMods(ConvertToLegacyDifficultyAdjustmentMods(beatmapInfo, ruleset, mods)).ToArray(); + + public static DifficultyAttributes CreateDifficultyAttributes(int legacyId) { - public EmptyWorkingBeatmap(BeatmapInfo beatmapInfo) - : base(beatmapInfo, null) + switch (legacyId) { - } - - protected override IBeatmap GetBeatmap() => throw new NotImplementedException(); + case 0: + return new OsuDifficultyAttributes(); - public override Texture GetBackground() => throw new NotImplementedException(); + case 1: + return new TaikoDifficultyAttributes(); - protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + case 2: + return new CatchDifficultyAttributes(); - protected override ISkin GetSkin() => throw new NotImplementedException(); + case 3: + return new ManiaDifficultyAttributes(); - public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + default: + throw new ArgumentException($"Invalid ruleset ID: {legacyId}", nameof(legacyId)); + } } } } diff --git a/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs b/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs new file mode 100644 index 000000000..a125c96ff --- /dev/null +++ b/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.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 McMaster.Extensions.CommandLineUtils; +using osu.Game.Online.API.Requests.Responses; + +namespace PerformanceCalculator.Performance +{ + [Command(Name = "legacy-score", Description = "Computes the performance (pp) of an online score.")] + public class LegacyScorePerformanceCommand : ScorePerformanceCommand + { + [Argument(1, "ruleset-id", "The ID of the ruleset that the score was set on.")] + public int RulesetId { get; set; } + + protected override SoloScoreInfo QueryScore() => GetJsonFromApi($"scores/{LegacyHelper.GetRulesetShortNameFromId(RulesetId)}/{ScoreId}"); + } +} diff --git a/PerformanceCalculator/Performance/ReplayPerformanceCommand.cs b/PerformanceCalculator/Performance/ReplayPerformanceCommand.cs index 8a2b6e161..c0057dfe5 100644 --- a/PerformanceCalculator/Performance/ReplayPerformanceCommand.cs +++ b/PerformanceCalculator/Performance/ReplayPerformanceCommand.cs @@ -42,7 +42,7 @@ public override void Execute() if (score.ScoreInfo.IsLegacyScore) { - difficultyMods = LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, difficultyMods); + difficultyMods = LegacyHelper.FilterDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, difficultyMods); score.ScoreInfo.LegacyTotalScore = (int)score.ScoreInfo.TotalScore; StandardisedScoreMigrationTools.UpdateFromLegacy( score.ScoreInfo, diff --git a/PerformanceCalculator/Performance/ScorePerformanceCommand.cs b/PerformanceCalculator/Performance/ScorePerformanceCommand.cs index 3b457020f..020f552df 100644 --- a/PerformanceCalculator/Performance/ScorePerformanceCommand.cs +++ b/PerformanceCalculator/Performance/ScorePerformanceCommand.cs @@ -1,32 +1,43 @@ // 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 System.Globalization; using System.Linq; +using System.Net.Http; using McMaster.Extensions.CommandLineUtils; +using Newtonsoft.Json; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Difficulty; using osu.Game.Rulesets.Scoring.Legacy; +using osu.Game.Rulesets.Taiko.Difficulty; namespace PerformanceCalculator.Performance { [Command(Name = "score", Description = "Computes the performance (pp) of an online score.")] public class ScorePerformanceCommand : ApiCommand { - [Argument(0, "ruleset-id", "The ID of the ruleset that the score was set on.")] - public int RulesetId { get; set; } - - [Argument(1, "score-id", "The score's online ID.")] + [Argument(0, "score-id", "The score's online ID.")] public ulong ScoreId { get; set; } + [Option(CommandOptionType.NoValue, Template = "-a|--online-attributes", Description = "Whether to use the currently-live difficulty attributes for the beatmap.")] + public bool OnlineAttributes { get; set; } + public override void Execute() { base.Execute(); - SoloScoreInfo apiScore = GetJsonFromApi($"scores/{LegacyHelper.GetRulesetShortNameFromId(RulesetId)}/{ScoreId}"); + SoloScoreInfo apiScore = QueryScore(); APIBeatmap apiBeatmap = GetJsonFromApi($"beatmaps/lookup?id={apiScore.BeatmapID}"); var ruleset = LegacyHelper.GetRulesetFromLegacyID(apiScore.RulesetID); @@ -52,12 +63,59 @@ public override void Execute() ((ILegacyRuleset)ruleset).CreateLegacyScoreSimulator().Simulate(workingBeatmap, workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods))); } - var difficultyCalculator = ruleset.CreateDifficultyCalculator(workingBeatmap); - var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, score.Mods)); + DifficultyAttributes attributes; + + if (OnlineAttributes) + { + LegacyMods legacyMods = LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, score.Mods); + attributes = queryApiAttributes(apiScore.BeatmapID, apiScore.RulesetID, legacyMods); + } + else + { + var difficultyCalculator = ruleset.CreateDifficultyCalculator(workingBeatmap); + attributes = difficultyCalculator.Calculate(LegacyHelper.FilterDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, score.Mods)); + } + var performanceCalculator = ruleset.CreatePerformanceCalculator(); - var performanceAttributes = performanceCalculator?.Calculate(score, difficultyAttributes); + var performanceAttributes = performanceCalculator?.Calculate(score, attributes); + + OutputPerformance(score, performanceAttributes, attributes); + } + + protected virtual SoloScoreInfo QueryScore() => GetJsonFromApi($"scores/{ScoreId}"); + + private DifficultyAttributes queryApiAttributes(int beatmapId, int rulesetId, LegacyMods mods) + { + Dictionary parameters = new Dictionary + { + { "mods", ((int)mods).ToString(CultureInfo.InvariantCulture) } + }; + + switch (rulesetId) + { + case 0: + return GetJsonFromApi>($"beatmaps/{beatmapId}/attributes", HttpMethod.Post, parameters).Attributes; + + case 1: + return GetJsonFromApi>($"beatmaps/{beatmapId}/attributes", HttpMethod.Post, parameters).Attributes; - OutputPerformance(score, performanceAttributes, difficultyAttributes); + case 2: + return GetJsonFromApi>($"beatmaps/{beatmapId}/attributes", HttpMethod.Post, parameters).Attributes; + + case 3: + return GetJsonFromApi>($"beatmaps/{beatmapId}/attributes", HttpMethod.Post, parameters).Attributes; + + default: + throw new ArgumentOutOfRangeException(nameof(rulesetId)); + } + } + + [JsonObject(MemberSerialization.OptIn)] + private class AttributesResponse + where T : DifficultyAttributes + { + [JsonProperty("attributes")] + public T Attributes { get; set; } } } } diff --git a/PerformanceCalculator/Profile/ProfileCommand.cs b/PerformanceCalculator/Profile/ProfileCommand.cs index 94e7845c9..a28a4ea16 100644 --- a/PerformanceCalculator/Profile/ProfileCommand.cs +++ b/PerformanceCalculator/Profile/ProfileCommand.cs @@ -52,7 +52,7 @@ public override void Execute() var score = new ProcessorScoreDecoder(working).Parse(scoreInfo); var difficultyCalculator = ruleset.CreateDifficultyCalculator(working); - var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(working.BeatmapInfo, ruleset, scoreInfo.Mods).ToArray()); + var difficultyAttributes = difficultyCalculator.Calculate(LegacyHelper.FilterDifficultyAdjustmentMods(working.BeatmapInfo, ruleset, scoreInfo.Mods).ToArray()); var performanceCalculator = ruleset.CreatePerformanceCalculator(); var ppAttributes = performanceCalculator?.Calculate(score.ScoreInfo, difficultyAttributes); diff --git a/PerformanceCalculator/Simulate/SimulateCommand.cs b/PerformanceCalculator/Simulate/SimulateCommand.cs index e5b477b83..b7351ff50 100644 --- a/PerformanceCalculator/Simulate/SimulateCommand.cs +++ b/PerformanceCalculator/Simulate/SimulateCommand.cs @@ -57,7 +57,7 @@ public override void Execute() var ruleset = Ruleset; var workingBeatmap = ProcessorWorkingBeatmap.FromFileOrId(Beatmap); - var mods = NoClassicMod ? GetMods(ruleset) : LegacyHelper.ConvertToLegacyDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, GetMods(ruleset)); + var mods = NoClassicMod ? GetMods(ruleset) : LegacyHelper.FilterDifficultyAdjustmentMods(workingBeatmap.BeatmapInfo, ruleset, GetMods(ruleset)); var beatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var beatmapMaxCombo = GetMaxCombo(beatmap); diff --git a/osu.Tools.sln.DotSettings b/osu.Tools.sln.DotSettings index a55685873..df16bfa3c 100644 --- a/osu.Tools.sln.DotSettings +++ b/osu.Tools.sln.DotSettings @@ -824,6 +824,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From ea86ddfafd210c113c0e65c25a059c923b9aca0a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 3 Feb 2024 05:49:54 +0900 Subject: [PATCH 2/4] Actually add the subcommand --- PerformanceCalculator/Performance/PerformanceListingCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/PerformanceCalculator/Performance/PerformanceListingCommand.cs b/PerformanceCalculator/Performance/PerformanceListingCommand.cs index 4804186a9..1c3cf255b 100644 --- a/PerformanceCalculator/Performance/PerformanceListingCommand.cs +++ b/PerformanceCalculator/Performance/PerformanceListingCommand.cs @@ -9,6 +9,7 @@ namespace PerformanceCalculator.Performance [Command(Name = "performance", Description = "Computes the performance (pp) of scores or replays.")] [Subcommand(typeof(ReplayPerformanceCommand))] [Subcommand(typeof(ScorePerformanceCommand))] + [Subcommand(typeof(LegacyScorePerformanceCommand))] public class PerformanceListingCommand { [UsedImplicitly] From 47b22ddc82ce2bafb9a8dacb955596af18f08db5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Feb 2024 17:39:33 +0900 Subject: [PATCH 3/4] Link implementation to osu-queue-score-statistics --- PerformanceCalculator/LegacyHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PerformanceCalculator/LegacyHelper.cs b/PerformanceCalculator/LegacyHelper.cs index 731cea5a7..7d2595b61 100644 --- a/PerformanceCalculator/LegacyHelper.cs +++ b/PerformanceCalculator/LegacyHelper.cs @@ -66,7 +66,7 @@ public static string GetRulesetShortNameFromId(int id) public const LegacyMods KEY_MODS = LegacyMods.Key1 | LegacyMods.Key2 | LegacyMods.Key3 | LegacyMods.Key4 | LegacyMods.Key5 | LegacyMods.Key6 | LegacyMods.Key7 | LegacyMods.Key8 | LegacyMods.Key9 | LegacyMods.KeyCoop; - // See: https://github.com/ppy/osu-performance/blob/83c02f50315a4ef7feea80acb84c66ee437d7210/include/pp/Common.h#L109-L129 + // See: https://github.com/ppy/osu-queue-score-statistics/blob/2264bfa68e14bb16ec71a7cac2072bdcfaf565b6/osu.Server.Queues.ScoreStatisticsProcessor/Helpers/LegacyModsHelper.cs public static LegacyMods MaskRelevantMods(LegacyMods mods, bool isConvertedBeatmap, int rulesetId) { LegacyMods relevantMods = LegacyMods.DoubleTime | LegacyMods.HalfTime | LegacyMods.HardRock | LegacyMods.Easy; From 408b1271b68c96404886de2793180cc486057fc8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Feb 2024 01:32:51 +0900 Subject: [PATCH 4/4] Fix querying imported legacy scores having duplicate stats --- .../LegacyScorePerformanceCommand.cs | 25 +++++++++++ .../Performance/ScorePerformanceCommand.cs | 44 +++++++------------ 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs b/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs index a125c96ff..846a2839a 100644 --- a/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs +++ b/PerformanceCalculator/Performance/LegacyScorePerformanceCommand.cs @@ -1,8 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using McMaster.Extensions.CommandLineUtils; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring.Legacy; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; namespace PerformanceCalculator.Performance { @@ -13,5 +21,22 @@ public class LegacyScorePerformanceCommand : ScorePerformanceCommand public int RulesetId { get; set; } protected override SoloScoreInfo QueryScore() => GetJsonFromApi($"scores/{LegacyHelper.GetRulesetShortNameFromId(RulesetId)}/{ScoreId}"); + + protected override ScoreInfo CreateScore(SoloScoreInfo apiScore, Ruleset ruleset, APIBeatmap apiBeatmap, WorkingBeatmap workingBeatmap) + { + var score = base.CreateScore(apiScore, ruleset, apiBeatmap, workingBeatmap); + + score.Mods = score.Mods.Append(ruleset.CreateMod()).ToArray(); + score.IsLegacyScore = true; + score.LegacyTotalScore = (int)score.TotalScore; + LegacyScoreDecoder.PopulateMaximumStatistics(score, workingBeatmap); + StandardisedScoreMigrationTools.UpdateFromLegacy( + score, + ruleset, + LegacyBeatmapConversionDifficultyInfo.FromAPIBeatmap(apiBeatmap), + ((ILegacyRuleset)ruleset).CreateLegacyScoreSimulator().Simulate(workingBeatmap, workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods))); + + return score; + } } } diff --git a/PerformanceCalculator/Performance/ScorePerformanceCommand.cs b/PerformanceCalculator/Performance/ScorePerformanceCommand.cs index 73854977e..a657a5991 100644 --- a/PerformanceCalculator/Performance/ScorePerformanceCommand.cs +++ b/PerformanceCalculator/Performance/ScorePerformanceCommand.cs @@ -10,18 +10,15 @@ using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Legacy; -using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania.Difficulty; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty; -using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Taiko.Difficulty; -using osu.Game.Scoring.Legacy; +using osu.Game.Scoring; namespace PerformanceCalculator.Performance { @@ -42,29 +39,8 @@ public override void Execute() APIBeatmap apiBeatmap = GetJsonFromApi($"beatmaps/lookup?id={apiScore.BeatmapID}"); var ruleset = LegacyHelper.GetRulesetFromLegacyID(apiScore.RulesetID); - var score = apiScore.ToScoreInfo(apiScore.Mods.Select(m => m.ToMod(ruleset)).ToArray(), apiBeatmap); - score.Ruleset = ruleset.RulesetInfo; - score.BeatmapInfo!.Metadata = new BeatmapMetadata - { - Title = apiBeatmap.Metadata.Title, - Artist = apiBeatmap.Metadata.Artist, - Author = new RealmUser { Username = apiBeatmap.Metadata.Author.Username }, - }; - - var workingBeatmap = ProcessorWorkingBeatmap.FromFileOrId(score.BeatmapInfo!.OnlineID.ToString()); - - if (apiScore.BuildID == null) - { - score.Mods = score.Mods.Append(ruleset.CreateMod()).ToArray(); - score.IsLegacyScore = true; - score.LegacyTotalScore = (int)score.TotalScore; - LegacyScoreDecoder.PopulateMaximumStatistics(score, workingBeatmap); - StandardisedScoreMigrationTools.UpdateFromLegacy( - score, - ruleset, - LegacyBeatmapConversionDifficultyInfo.FromAPIBeatmap(apiBeatmap), - ((ILegacyRuleset)ruleset).CreateLegacyScoreSimulator().Simulate(workingBeatmap, workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods))); - } + var workingBeatmap = ProcessorWorkingBeatmap.FromFileOrId(apiScore.BeatmapID.ToString()); + var score = CreateScore(apiScore, ruleset, apiBeatmap, workingBeatmap); DifficultyAttributes attributes; @@ -87,6 +63,20 @@ public override void Execute() protected virtual SoloScoreInfo QueryScore() => GetJsonFromApi($"scores/{ScoreId}"); + protected virtual ScoreInfo CreateScore(SoloScoreInfo apiScore, Ruleset ruleset, APIBeatmap apiBeatmap, WorkingBeatmap workingBeatmap) + { + var score = apiScore.ToScoreInfo(apiScore.Mods.Select(m => m.ToMod(ruleset)).ToArray(), apiBeatmap); + score.Ruleset = ruleset.RulesetInfo; + score.BeatmapInfo!.Metadata = new BeatmapMetadata + { + Title = apiBeatmap.Metadata.Title, + Artist = apiBeatmap.Metadata.Artist, + Author = new RealmUser { Username = apiBeatmap.Metadata.Author.Username }, + }; + + return score; + } + private DifficultyAttributes queryApiAttributes(int beatmapId, int rulesetId, LegacyMods mods) { Dictionary parameters = new Dictionary