diff --git a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs index cf498c7856..e1c385097f 100644 --- a/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/LegacyBeatmapExporterTest.cs @@ -59,7 +59,15 @@ namespace osu.Game.Tests.Beatmaps.IO // Ensure importer encoding is correct AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"fractional-coordinates.olz")); - AddAssert("hit object has fractional position", () => ((IHasYPosition)beatmap.Beatmap.HitObjects[1]).Y, () => Is.EqualTo(383.99997).Within(0.00001)); + AddAssert("second slider has fractional position", + () => ((IHasXPosition)beatmap.Beatmap.HitObjects[1]).X, + () => Is.EqualTo(-3.0517578E-05).Within(0.00001)); + AddAssert("second slider path has fractional coordinates", + () => ((IHasPath)beatmap.Beatmap.HitObjects[1]).Path.ControlPoints[1].Position.X, + () => Is.EqualTo(191.999939).Within(0.00001)); + AddAssert("second hit circle has fractional position", + () => ((IHasYPosition)beatmap.Beatmap.HitObjects[3]).Y, + () => Is.EqualTo(383.99997).Within(0.00001)); // Ensure exporter legacy conversion is correct AddStep("export", () => @@ -71,7 +79,15 @@ namespace osu.Game.Tests.Beatmaps.IO }); AddStep("import beatmap again", () => beatmap = importBeatmapFromStream(outStream)); - AddAssert("hit object is snapped", () => ((IHasYPosition)beatmap.Beatmap.HitObjects[1]).Y, () => Is.EqualTo(384).Within(0.00001)); + AddAssert("second slider is snapped", + () => ((IHasXPosition)beatmap.Beatmap.HitObjects[1]).X, + () => Is.EqualTo(0).Within(0.00001)); + AddAssert("second slider path is snapped", + () => ((IHasPath)beatmap.Beatmap.HitObjects[1]).Path.ControlPoints[1].Position.X, + () => Is.EqualTo(192).Within(0.00001)); + AddAssert("second hit circle is snapped", + () => ((IHasYPosition)beatmap.Beatmap.HitObjects[3]).Y, + () => Is.EqualTo(384).Within(0.00001)); } [Test] diff --git a/osu.Game.Tests/Resources/Archives/fractional-coordinates.olz b/osu.Game.Tests/Resources/Archives/fractional-coordinates.olz index 5c5af368c8..40f33dea75 100644 Binary files a/osu.Game.Tests/Resources/Archives/fractional-coordinates.olz and b/osu.Game.Tests/Resources/Archives/fractional-coordinates.olz differ diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index e7e5ddb4d2..8d90c9adb4 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -132,7 +132,20 @@ namespace osu.Game.Database hasPath.Path.ControlPoints[^1].Type = null; if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1 - && hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue; + && hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) + { + // Round every control point to integer positions before skipping to the next hit object + for (int i = 0; i < hasPath.Path.ControlPoints.Count; i++) + { + var position = new Vector2( + MathF.Round(hasPath.Path.ControlPoints[i].Position.X), + MathF.Round(hasPath.Path.ControlPoints[i].Position.Y)); + + hasPath.Path.ControlPoints[i].Position = position; + } + + continue; + } var convertedToBezier = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints); @@ -142,10 +155,10 @@ namespace osu.Game.Database { var convertedPoint = convertedToBezier[i]; - // Truncate control points to integer positions + // Round control points to integer positions var position = new Vector2( - (float)Math.Floor(convertedPoint.Position.X), - (float)Math.Floor(convertedPoint.Position.Y)); + MathF.Round(convertedPoint.Position.X), + MathF.Round(convertedPoint.Position.Y)); // stable only supports a single curve type specification per slider. // we exploit the fact that the converted-to-Bézier path only has Bézier segments,