diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchEditorSaving.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchEditorSaving.cs new file mode 100644 index 000000000000..53ef24e02cc3 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneCatchEditorSaving.cs @@ -0,0 +1,66 @@ +// 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 NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Catch.Tests.Editor +{ + public partial class TestSceneCatchEditorSaving : EditorSavingTestScene + { + protected override Ruleset CreateRuleset() => new CatchRuleset(); + + [Test] + public void TestCatchJuiceStreamTickCorrect() + { + AddStep("enter timing mode", () => InputManager.Key(Key.F3)); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + AddStep("enter compose mode", () => InputManager.Key(Key.F1)); + + Vector2 startPoint = Vector2.Zero; + float increment = 0; + + AddUntilStep("wait for playfield", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + AddStep("move to centre", () => + { + var playfield = this.ChildrenOfType().Single(); + startPoint = playfield.ScreenSpaceDrawQuad.Centre + new Vector2(0, playfield.ScreenSpaceDrawQuad.Height / 3); + increment = playfield.ScreenSpaceDrawQuad.Height / 10; + InputManager.MoveMouseTo(startPoint); + }); + AddStep("choose juice stream placing tool", () => InputManager.Key(Key.Number3)); + AddStep("start placement", () => InputManager.Click(MouseButton.Left)); + + AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(2 * increment, -increment))); + AddStep("add node", () => InputManager.Click(MouseButton.Left)); + + AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(-2 * increment, -2 * increment))); + AddStep("add node", () => InputManager.Click(MouseButton.Left)); + + AddStep("move to next", () => InputManager.MoveMouseTo(startPoint + new Vector2(0, -3 * increment))); + AddStep("end placement", () => InputManager.Click(MouseButton.Right)); + + AddUntilStep("juice stream placed", () => EditorBeatmap.HitObjects, () => Has.Count.EqualTo(1)); + + int largeDropletCount = 0, tinyDropletCount = 0; + AddStep("store droplet count", () => + { + largeDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet)); + tinyDropletCount = EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet)); + }); + + SaveEditor(); + ReloadEditorToSameBeatmap(); + + AddAssert("large droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(Droplet)), () => Is.EqualTo(largeDropletCount)); + AddAssert("tiny droplet count is the same", () => EditorBeatmap.HitObjects[0].NestedHitObjects.Count(t => t.GetType() == typeof(TinyDroplet)), () => Is.EqualTo(tinyDropletCount)); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 8c460586b0cf..f5c5ffb529df 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -45,7 +45,7 @@ protected override IEnumerable ConvertHitObject(HitObject obj, I LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y, // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // this results in more (or less) ticks being generated in velocityFactor * SliderVelocityMultiplier; - - [JsonIgnore] - public double TickDistance => tickDistanceFactor * TickDistanceMultiplier; + public double TickDistance { get; private set; } /// /// The length of one span of this . @@ -68,8 +63,13 @@ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, I TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); - velocityFactor = base_scoring_distance * difficulty.SliderMultiplier / timingPoint.BeatLength; - tickDistanceFactor = base_scoring_distance * difficulty.SliderMultiplier / difficulty.SliderTickRate; + Velocity = base_scoring_distance * difficulty.SliderMultiplier / LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(this, timingPoint, CatchRuleset.SHORT_NAME); + + // WARNING: this is intentionally not computed as `BASE_SCORING_DISTANCE * difficulty.SliderMultiplier` + // for backwards compatibility reasons (intentionally introducing floating point errors to match stable). + double scoringDistance = Velocity * timingPoint.BeatLength; + + TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; } protected override void CreateNestedHitObjects(CancellationToken cancellationToken)