diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index aaef69f119..ccfe1501bd 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -174,9 +174,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps switch (original) { - case IHasDistance: + case IHasPath: { - var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); + var generator = new PathObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap); conversion = generator; var positionData = original as IHasPosition; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs similarity index 96% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs index cce0944564..4922915c7d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs @@ -22,13 +22,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// A pattern generator for IHasDistance hit objects. /// - internal class DistanceObjectPatternGenerator : PatternGenerator + internal class PathObjectPatternGenerator : PatternGenerator { - /// - /// Base osu! slider scoring distance. - /// - private const float osu_base_scoring_distance = 100; - public readonly int StartTime; public readonly int EndTime; public readonly int SegmentDuration; @@ -36,17 +31,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private PatternType convertType; - public DistanceObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) + public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { convertType = PatternType.None; if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) convertType = PatternType.LowProbability; - var distanceData = hitObject as IHasDistance; + var pathData = hitObject as IHasPath; var repeatsData = hitObject as IHasRepeats; - Debug.Assert(distanceData != null); + Debug.Assert(pathData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); @@ -60,8 +55,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); + double distance = pathData.Path.ExpectedDistance.Value ?? 0; + // This matches stable's calculation. - EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); + EndTime = (int)Math.Floor(StartTime + distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index e46e2ec09c..2551321ff2 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps switch (obj) { - case IHasDistance distanceData: + case IHasPath pathData: { - if (shouldConvertSliderToHits(obj, beatmap, distanceData, out int taikoDuration, out double tickSpacing)) + if (shouldConvertSliderToHits(obj, beatmap, pathData, out int taikoDuration, out double tickSpacing)) { IList> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } } - private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out int taikoDuration, out double tickSpacing) + private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasPath pathData, out int taikoDuration, out double tickSpacing) { // DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS. // Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable. @@ -182,7 +182,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps // The true distance, accounting for any repeats. This ends up being the drum roll distance later int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + double distance = pathData.Path.ExpectedDistance.Value ?? 0; + + // Do not combine the following two lines! + distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + distance *= spans; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index dcfe8ecb41..02432a1935 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -15,12 +15,14 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Taiko; using osu.Game.Skinning; using osu.Game.Tests.Resources; using osuTK; @@ -1156,5 +1158,35 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(((IHasComboInformation)playable.HitObjects[17]).ComboIndexWithOffsets, Is.EqualTo(9)); } } + + [Test] + public void TestSliderConversionWithCustomDistance([Values("taiko", "mania")] string rulesetName) + { + using (var resStream = TestResources.OpenResource("custom-slider-length.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + Ruleset ruleset; + + switch (rulesetName) + { + case "taiko": + ruleset = new TaikoRuleset(); + break; + + case "mania": + ruleset = new ManiaRuleset(); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(rulesetName), rulesetName, null); + } + + var decoder = Decoder.GetDecoder(stream); + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + IBeatmap beatmap = working.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); + + Assert.That(beatmap.HitObjects[0].GetEndTime(), Is.EqualTo(3153)); + } + } } } diff --git a/osu.Game.Tests/Resources/custom-slider-length.osu b/osu.Game.Tests/Resources/custom-slider-length.osu new file mode 100644 index 0000000000..f7529918a9 --- /dev/null +++ b/osu.Game.Tests/Resources/custom-slider-length.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:6 +CircleSize:7 +OverallDifficulty:7 +ApproachRate:10 +SliderMultiplier:1.7 +SliderTickRate:1 + +[TimingPoints] +29,333.333333333333,4,1,0,100,1,0 +29,-10000,4,1,0,100,0,0 + +[HitObjects] +256,192,29,6,0,P|384:192|384:192,1,159.375 \ No newline at end of file