From 26855a2c04f623e87c1a357e3dab3cda241dd075 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Nov 2023 21:14:34 +0900 Subject: [PATCH 1/4] Add failing test --- .../Formats/LegacyBeatmapDecoderTest.cs | 32 +++++++++++++++++++ .../Resources/custom-slider-length.osu | 19 +++++++++++ 2 files changed, 51 insertions(+) create mode 100644 osu.Game.Tests/Resources/custom-slider-length.osu 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 From 16577829e27696d27f3ae99129a1c9f3a16639d3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 28 Nov 2023 21:14:56 +0900 Subject: [PATCH 2/4] Fix mania and taiko slider conversion distance --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 9 ++++++++- .../Beatmaps/TaikoBeatmapConverter.cs | 10 +++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index cce0944564..8aa128be33 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -60,8 +60,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); + double distance; + + if (hitObject is IHasPath pathData) + distance = pathData.Path.ExpectedDistance.Value ?? 0; + else + distance = distanceData.Distance; + // 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..4827ec76aa 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -182,7 +182,15 @@ 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; + + if (obj is IHasPath pathData) + distance = pathData.Path.ExpectedDistance.Value ?? 0; + else + distance = distanceData.Distance; + + distance *= spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); From 301d503b0b52b4ba7ba16be201efeff7a5040e64 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Nov 2023 16:41:19 +0900 Subject: [PATCH 3/4] Add another source of FP inaccuracy to match osu!stable --- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 4827ec76aa..2393a248eb 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -190,7 +190,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else distance = distanceData.Distance; - distance *= spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + // Do not combine the following two lines! + distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + distance *= spans; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); From 1c3bcbd54812f30c1170f2f5a0ab150220505d67 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 29 Nov 2023 17:30:21 +0900 Subject: [PATCH 4/4] Use IHasPath instead of IHasDistance for mania/taiko --- .../Beatmaps/ManiaBeatmapConverter.cs | 4 ++-- ...rator.cs => PathObjectPatternGenerator.cs} | 20 +++++-------------- .../Beatmaps/TaikoBeatmapConverter.cs | 14 ++++--------- 3 files changed, 11 insertions(+), 27 deletions(-) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{DistanceObjectPatternGenerator.cs => PathObjectPatternGenerator.cs} (96%) 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 8aa128be33..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,12 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); - double distance; - - if (hitObject is IHasPath pathData) - distance = pathData.Path.ExpectedDistance.Value ?? 0; - else - distance = distanceData.Distance; + double distance = pathData.Path.ExpectedDistance.Value ?? 0; // This matches stable's calculation. EndTime = (int)Math.Floor(StartTime + distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 2393a248eb..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,13 +182,7 @@ 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; - - if (obj is IHasPath pathData) - distance = pathData.Path.ExpectedDistance.Value ?? 0; - else - distance = distanceData.Distance; + double distance = pathData.Path.ExpectedDistance.Value ?? 0; // Do not combine the following two lines! distance *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;