1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 07:32:58 +08:00

Merge pull request #24988 from smoogipoo/fix-legacy-score-multipliers-2

Fix legacy score calculators using incorrect mod multipliers
This commit is contained in:
Bartłomiej Dach 2023-10-09 12:56:39 +02:00 committed by GitHub
commit b3d3ae87be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 328 additions and 39 deletions

View File

@ -2,10 +2,13 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -137,5 +140,53 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (increaseCombo) if (increaseCombo)
combo++; combo++;
} }
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case CatchModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case CatchModEasy:
multiplier *= 0.5;
break;
case CatchModHalfTime:
case CatchModDaycore:
multiplier *= 0.3;
break;
case CatchModHidden:
multiplier *= scoreV2 ? 1.0 : 1.06;
break;
case CatchModHardRock:
multiplier *= 1.12;
break;
case CatchModDoubleTime:
case CatchModNightcore:
multiplier *= 1.06;
break;
case CatchModFlashlight:
multiplier *= 1.12;
break;
case CatchModRelax:
return 0;
}
}
return multiplier;
}
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Utils; using osu.Game.Utils;
using osuTK; using osuTK;
@ -43,39 +44,41 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
: base(beatmap, ruleset) : base(beatmap, ruleset)
{ {
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
TargetColumns = GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap));
double roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize); if (IsForCurrentRuleset && TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
double roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty);
if (IsForCurrentRuleset)
{ {
TargetColumns = GetColumnCountForNonConvert(beatmap.BeatmapInfo); TargetColumns /= 2;
Dual = true;
if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS)
{
TargetColumns /= 2;
Dual = true;
}
}
else
{
float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4;
else
TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
} }
originalTargetColumns = TargetColumns; originalTargetColumns = TargetColumns;
} }
public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo) public static int GetColumnCount(LegacyBeatmapConversionDifficultyInfo difficulty)
{ {
double roundedCircleSize = Math.Round(beatmapInfo.Difficulty.CircleSize); if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
return GetColumnCountForNonConvert(difficulty);
double roundedCircleSize = Math.Round(difficulty.CircleSize);
double roundedOverallDifficulty = Math.Round(difficulty.OverallDifficulty);
int countSliderOrSpinner = difficulty.TotalObjectCount - difficulty.CircleCount;
float percentSpecialObjects = (float)countSliderOrSpinner / difficulty.TotalObjectCount;
if (percentSpecialObjects < 0.2)
return 7;
if (percentSpecialObjects < 0.3 || roundedCircleSize >= 5)
return roundedOverallDifficulty > 5 ? 7 : 6;
if (percentSpecialObjects > 0.6)
return roundedOverallDifficulty > 4 ? 5 : 4;
return Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
}
public static int GetColumnCountForNonConvert(IBeatmapDifficultyInfo difficulty)
{
double roundedCircleSize = Math.Round(difficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize); return (int)Math.Max(1, roundedCircleSize);
} }

View File

@ -1,7 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Scoring.Legacy;
namespace osu.Game.Rulesets.Mania.Difficulty namespace osu.Game.Rulesets.Mania.Difficulty
@ -12,5 +17,50 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{ {
return new LegacyScoreAttributes { ComboScore = 1000000 }; return new LegacyScoreAttributes { ComboScore = 1000000 };
} }
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case ManiaModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case ManiaModEasy:
multiplier *= 0.5;
break;
case ManiaModHalfTime:
case ManiaModDaycore:
multiplier *= 0.5;
break;
}
}
if (new ManiaRuleset().RulesetInfo.Equals(difficulty.SourceRuleset))
return multiplier;
// Apply key mod multipliers.
int originalColumns = ManiaBeatmapConverter.GetColumnCount(difficulty);
int actualColumns = originalColumns;
actualColumns = mods.OfType<ManiaKeyMod>().SingleOrDefault()?.KeyCount ?? actualColumns;
if (mods.Any(m => m is ManiaModDualStages))
actualColumns *= 2;
if (actualColumns > originalColumns)
multiplier *= 0.9;
else if (actualColumns < originalColumns)
multiplier *= 0.9 - 0.04 * (originalColumns - actualColumns);
return multiplier;
}
} }
} }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania
public bool Matches(BeatmapInfo beatmapInfo) public bool Matches(BeatmapInfo beatmapInfo)
{ {
return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); return !keys.HasFilter || (beatmapInfo.Ruleset.OnlineID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo.Difficulty)));
} }
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)

View File

@ -2,11 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Scoring.Legacy;
@ -174,5 +177,58 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (increaseCombo) if (increaseCombo)
combo++; combo++;
} }
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case OsuModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case OsuModEasy:
multiplier *= 0.5;
break;
case OsuModHalfTime:
case OsuModDaycore:
multiplier *= 0.3;
break;
case OsuModHidden:
multiplier *= 1.06;
break;
case OsuModHardRock:
multiplier *= scoreV2 ? 1.10 : 1.06;
break;
case OsuModDoubleTime:
case OsuModNightcore:
multiplier *= scoreV2 ? 1.20 : 1.12;
break;
case OsuModFlashlight:
multiplier *= 1.12;
break;
case OsuModSpunOut:
multiplier *= 0.9;
break;
case OsuModRelax:
case OsuModAutopilot:
return 0;
}
}
return multiplier;
}
} }
} }

View File

@ -2,13 +2,16 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Rulesets.Scoring.Legacy;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty namespace osu.Game.Rulesets.Taiko.Difficulty
@ -194,5 +197,47 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
if (increaseCombo) if (increaseCombo)
combo++; combo++;
} }
public double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty)
{
bool scoreV2 = mods.Any(m => m is ModScoreV2);
double multiplier = 1.0;
foreach (var mod in mods)
{
switch (mod)
{
case TaikoModNoFail:
multiplier *= scoreV2 ? 1.0 : 0.5;
break;
case TaikoModEasy:
multiplier *= 0.5;
break;
case TaikoModHalfTime:
case TaikoModDaycore:
multiplier *= 0.3;
break;
case TaikoModHidden:
case TaikoModHardRock:
multiplier *= 1.06;
break;
case TaikoModDoubleTime:
case TaikoModNightcore:
case TaikoModFlashlight:
multiplier *= 1.12;
break;
case TaikoModRelax:
return 0;
}
}
return multiplier;
}
} }
} }

View File

@ -127,8 +127,9 @@ namespace osu.Game.Tests.Database
}); });
} }
[Test] [TestCase(30000002)]
public void TestScoreUpgradeSuccess() [TestCase(30000003)]
public void TestScoreUpgradeSuccess(int scoreVersion)
{ {
ScoreInfo scoreInfo = null!; ScoreInfo scoreInfo = null!;
@ -138,7 +139,7 @@ namespace osu.Game.Tests.Database
{ {
r.Add(scoreInfo = new ScoreInfo(ruleset: r.All<RulesetInfo>().First(), beatmap: r.All<BeatmapInfo>().First()) r.Add(scoreInfo = new ScoreInfo(ruleset: r.All<RulesetInfo>().First(), beatmap: r.All<BeatmapInfo>().First())
{ {
TotalScoreVersion = 30000002, TotalScoreVersion = scoreVersion,
LegacyTotalScore = 123456, LegacyTotalScore = 123456,
IsLegacyScore = true, IsLegacyScore = true,
}); });

View File

@ -235,7 +235,9 @@ namespace osu.Game
Logger.Log("Querying for scores that need total score conversion..."); Logger.Log("Querying for scores that need total score conversion...");
HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(r.All<ScoreInfo>() HashSet<Guid> scoreIds = realmAccess.Run(r => new HashSet<Guid>(r.All<ScoreInfo>()
.Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null
&& (s.TotalScoreVersion == 30000002
|| s.TotalScoreVersion == 30000003))
.AsEnumerable().Select(s => s.ID))); .AsEnumerable().Select(s => s.ID)));
Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion.");

View File

@ -10,7 +10,6 @@ using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO.Legacy; using osu.Game.IO.Legacy;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -225,28 +224,30 @@ namespace osu.Game.Database
ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator(); ILegacyScoreSimulator sv1Simulator = legacyRuleset.CreateLegacyScoreSimulator();
LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap); LegacyScoreAttributes attributes = sv1Simulator.Simulate(beatmap, playableBeatmap);
return ConvertFromLegacyTotalScore(score, attributes); return ConvertFromLegacyTotalScore(score, LegacyBeatmapConversionDifficultyInfo.FromBeatmap(beatmap.Beatmap), attributes);
} }
/// <summary> /// <summary>
/// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>. /// Converts from <see cref="ScoreInfo.LegacyTotalScore"/> to the new standardised scoring of <see cref="ScoreProcessor"/>.
/// </summary> /// </summary>
/// <param name="score">The score to convert the total score of.</param> /// <param name="score">The score to convert the total score of.</param>
/// <param name="attributes">Difficulty attributes providing the legacy scoring values /// <param name="difficulty">The beatmap difficulty.</param>
/// (<see cref="DifficultyAttributes.LegacyAccuracyScore"/>, <see cref="DifficultyAttributes.LegacyComboScore"/>, and <see cref="DifficultyAttributes.LegacyBonusScoreRatio"/>) /// <param name="attributes">The legacy scoring attributes for the beatmap which the score was set on.</param>
/// for the beatmap which the score was set on.</param>
/// <returns>The standardised total score.</returns> /// <returns>The standardised total score.</returns>
public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyScoreAttributes attributes) public static long ConvertFromLegacyTotalScore(ScoreInfo score, LegacyBeatmapConversionDifficultyInfo difficulty, LegacyScoreAttributes attributes)
{ {
if (!score.IsLegacyScore) if (!score.IsLegacyScore)
return score.TotalScore; return score.TotalScore;
Debug.Assert(score.LegacyTotalScore != null); Debug.Assert(score.LegacyTotalScore != null);
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); Ruleset ruleset = score.Ruleset.CreateInstance();
if (ruleset is not ILegacyRuleset legacyRuleset)
return score.TotalScore;
double legacyModMultiplier = legacyRuleset.CreateLegacyScoreSimulator().GetLegacyScoreMultiplier(score.Mods, difficulty);
int maximumLegacyAccuracyScore = attributes.AccuracyScore; int maximumLegacyAccuracyScore = attributes.AccuracyScore;
long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * modMultiplier); long maximumLegacyComboScore = (long)Math.Round(attributes.ComboScore * legacyModMultiplier);
double maximumLegacyBonusRatio = attributes.BonusScoreRatio; double maximumLegacyBonusRatio = attributes.BonusScoreRatio;
// The part of total score that doesn't include bonus. // The part of total score that doesn't include bonus.
@ -258,6 +259,8 @@ namespace osu.Game.Database
// The bonus proportion makes up the rest of the score that exceeds maximumLegacyBaseScore. // The bonus proportion makes up the rest of the score that exceeds maximumLegacyBaseScore.
double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio); double bonusProportion = Math.Max(0, ((long)score.LegacyTotalScore - maximumLegacyBaseScore) * maximumLegacyBonusRatio);
double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n);
switch (score.Ruleset.OnlineID) switch (score.Ruleset.OnlineID)
{ {
case 0: case 0:

View File

@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Scoring.Legacy namespace osu.Game.Rulesets.Scoring.Legacy
{ {
@ -16,5 +18,13 @@ namespace osu.Game.Rulesets.Scoring.Legacy
/// <param name="workingBeatmap">The working beatmap.</param> /// <param name="workingBeatmap">The working beatmap.</param>
/// <param name="playableBeatmap">A playable version of the beatmap for the ruleset.</param> /// <param name="playableBeatmap">A playable version of the beatmap for the ruleset.</param>
LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap); LegacyScoreAttributes Simulate(IWorkingBeatmap workingBeatmap, IBeatmap playableBeatmap);
/// <summary>
/// Returns the legacy score multiplier for the mods. This is only used during legacy score conversion.
/// </summary>
/// <param name="mods">The mods.</param>
/// <param name="difficulty">Extra difficulty parameters.</param>
/// <returns>The legacy multiplier.</returns>
double GetLegacyScoreMultiplier(IReadOnlyList<Mod> mods, LegacyBeatmapConversionDifficultyInfo difficulty);
} }
} }

View File

@ -0,0 +1,67 @@
// 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.Linq;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Scoring.Legacy
{
/// <summary>
/// A set of properties that are required to facilitate beatmap conversion between legacy rulesets.
/// </summary>
public class LegacyBeatmapConversionDifficultyInfo : IBeatmapDifficultyInfo
{
/// <summary>
/// The beatmap's ruleset.
/// </summary>
public IRulesetInfo SourceRuleset { get; set; } = new RulesetInfo();
/// <summary>
/// The beatmap circle size.
/// </summary>
public float CircleSize { get; set; }
/// <summary>
/// The beatmap overall difficulty.
/// </summary>
public float OverallDifficulty { get; set; }
/// <summary>
/// The count of hitcircles in the beatmap.
/// </summary>
/// <remarks>
/// When converting from osu! ruleset beatmaps, this is equivalent to the sum of sliders and spinners in the beatmap.
/// </remarks>
public int CircleCount { get; set; }
/// <summary>
/// The total count of hitobjects in the beatmap.
/// </summary>
public int TotalObjectCount { get; set; }
float IBeatmapDifficultyInfo.DrainRate => 0;
float IBeatmapDifficultyInfo.ApproachRate => 0;
double IBeatmapDifficultyInfo.SliderMultiplier => 0;
double IBeatmapDifficultyInfo.SliderTickRate => 0;
public static LegacyBeatmapConversionDifficultyInfo FromAPIBeatmap(APIBeatmap apiBeatmap) => new LegacyBeatmapConversionDifficultyInfo
{
SourceRuleset = apiBeatmap.Ruleset,
CircleSize = apiBeatmap.CircleSize,
OverallDifficulty = apiBeatmap.OverallDifficulty,
CircleCount = apiBeatmap.CircleCount,
TotalObjectCount = apiBeatmap.SliderCount + apiBeatmap.SpinnerCount + apiBeatmap.CircleCount
};
public static LegacyBeatmapConversionDifficultyInfo FromBeatmap(IBeatmap beatmap) => new LegacyBeatmapConversionDifficultyInfo
{
SourceRuleset = beatmap.BeatmapInfo.Ruleset,
CircleSize = beatmap.Difficulty.CircleSize,
OverallDifficulty = beatmap.Difficulty.OverallDifficulty,
CircleCount = beatmap.HitObjects.Count(h => h is not IHasDuration),
TotalObjectCount = beatmap.HitObjects.Count
};
}
}

View File

@ -30,9 +30,10 @@ namespace osu.Game.Scoring.Legacy
/// <item><description>30000001: Appends <see cref="LegacyReplaySoloScoreInfo"/> to the end of scores.</description></item> /// <item><description>30000001: Appends <see cref="LegacyReplaySoloScoreInfo"/> to the end of scores.</description></item>
/// <item><description>30000002: Score stored to replay calculated using the Score V2 algorithm. Legacy scores on this version are candidate to Score V1 -> V2 conversion.</description></item> /// <item><description>30000002: Score stored to replay calculated using the Score V2 algorithm. Legacy scores on this version are candidate to Score V1 -> V2 conversion.</description></item>
/// <item><description>30000003: First version after converting legacy total score to standardised.</description></item> /// <item><description>30000003: First version after converting legacy total score to standardised.</description></item>
/// <item><description>30000004: Fixed mod multipliers during legacy score conversion. Reconvert all scores.</description></item>
/// </list> /// </list>
/// </remarks> /// </remarks>
public const int LATEST_VERSION = 30000003; public const int LATEST_VERSION = 30000004;
/// <summary> /// <summary>
/// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays.