From 441c941039c4be699ec38764db396731d97a1316 Mon Sep 17 00:00:00 2001 From: Chris Ehmann Date: Fri, 23 May 2025 12:36:16 -0700 Subject: [PATCH 1/4] Change OsuDistanceSnapProvider to use StackedPosition when determining distance between notes --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 45ce3206d2..e792882d3b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -16,10 +16,14 @@ namespace osu.Game.Rulesets.Osu.Edit { public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { + // Check to see if before and after HitObjects exist within the same stack. + if ((((OsuHitObject)before).EndPosition - ((OsuHitObject)after).Position).Length < 0.01) + return 0; + var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType().LastOrDefault(); float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime, lastObjectWithVelocity); - float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position); + float actualDistance = (((OsuHitObject)after).StackedPosition - ((OsuHitObject)before).EndPosition).Length; return actualDistance / expectedDistance; } From a4e78a34acf46775803e7ad1a3d80e2159837007 Mon Sep 17 00:00:00 2001 From: Chris Ehmann Date: Fri, 23 May 2025 22:33:04 -0700 Subject: [PATCH 2/4] Change back to using Vector2.Distance instead of length, keep order of before-after consistent --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index e792882d3b..044088cf20 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -17,13 +17,13 @@ namespace osu.Game.Rulesets.Osu.Edit public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { // Check to see if before and after HitObjects exist within the same stack. - if ((((OsuHitObject)before).EndPosition - ((OsuHitObject)after).Position).Length < 0.01) + if (Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position) < 0.01) return 0; var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType().LastOrDefault(); float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime, lastObjectWithVelocity); - float actualDistance = (((OsuHitObject)after).StackedPosition - ((OsuHitObject)before).EndPosition).Length; + float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).StackedPosition); return actualDistance / expectedDistance; } From e92205b3eb6387ad424f8c8ef2acc4666d20dbbc Mon Sep 17 00:00:00 2001 From: Chris <79126075+chris-ehmann@users.noreply.github.com> Date: Mon, 26 May 2025 11:30:04 -0700 Subject: [PATCH 3/4] Change actualDistance to use StackedEndPosition to match StackedPosition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 044088cf20..288c65f728 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType().LastOrDefault(); float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime, lastObjectWithVelocity); - float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).StackedPosition); + float actualDistance = Vector2.Distance(((OsuHitObject)before).StackedEndPosition, ((OsuHitObject)after).StackedPosition); return actualDistance / expectedDistance; } From f28674502f829a6de5556584a354099fea56ba0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 May 2025 09:29:29 +0200 Subject: [PATCH 4/4] Establish public constant for stacking lenience --- .../Beatmaps/OsuBeatmapProcessor.cs | 22 +++++++++++-------- .../Edit/OsuDistanceSnapProvider.cs | 6 +++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index bb3fe7db06..e49d72ef33 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { public class OsuBeatmapProcessor : BeatmapProcessor { - private const int stack_distance = 3; + /// + /// The maximum distance between the end of one object and the start of another + /// which allows the objects to be stacked on top of another. + /// + public const int STACK_DISTANCE = 3; public OsuBeatmapProcessor(IBeatmap beatmap) : base(beatmap) @@ -93,8 +97,8 @@ namespace osu.Game.Rulesets.Osu.Beatmaps // We are no longer within stacking range of the next object. break; - if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance - || (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)) + if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < STACK_DISTANCE + || (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < STACK_DISTANCE)) { stackBaseIndex = n; @@ -163,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps * o <- hitCircle has stack of -1 * o <- hitCircle has stack of -2 */ - if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < STACK_DISTANCE) { int offset = objectI.StackHeight - objectN.StackHeight + 1; @@ -171,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { // For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). OsuHitObject objectJ = hitObjects[j]; - if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.EndPosition, objectJ.Position) < STACK_DISTANCE) objectJ.StackHeight -= offset; } @@ -180,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps break; } - if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.Position, objectI.Position) < STACK_DISTANCE) { // Keep processing as if there are no sliders. If we come across a slider, this gets cancelled out. //NOTE: Sliders with start positions stacking are a special case that is also handled here. @@ -204,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps // We are no longer within stacking range of the previous object. break; - if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) + if (Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < STACK_DISTANCE) { objectN.StackHeight = objectI.StackHeight + 1; objectI = objectN; @@ -245,12 +249,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps // Effects of this can be seen on https://osu.ppy.sh/beatmapsets/243#osu/1146 at sliders around 86647 ms, where // if we use `EndTime` here it would result in unexpected stacking. - if (Vector2Extensions.Distance(hitObjects[j].Position, currHitObject.Position) < stack_distance) + if (Vector2Extensions.Distance(hitObjects[j].Position, currHitObject.Position) < STACK_DISTANCE) { currHitObject.StackHeight++; startTime = hitObjects[j].StartTime; } - else if (Vector2Extensions.Distance(hitObjects[j].Position, position2) < stack_distance) + else if (Vector2Extensions.Distance(hitObjects[j].Position, position2) < STACK_DISTANCE) { // Case for sliders - bump notes down and right, rather than up and left. sliderStack++; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 288c65f728..6be60e4554 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -7,6 +7,7 @@ using osu.Game.Input.Bindings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -16,8 +17,9 @@ namespace osu.Game.Rulesets.Osu.Edit { public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { - // Check to see if before and after HitObjects exist within the same stack. - if (Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position) < 0.01) + // If the pair of hit objects in question here could feasibly be on the same stack, do not provide a distance snap value - + // they're likely too close to one another for the distance snap value to be useful anyway even if they somehow are not. + if (Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position) < OsuBeatmapProcessor.STACK_DISTANCE) return 0; var lastObjectWithVelocity = EditorBeatmap.HitObjects.TakeWhile(ho => ho != after).OfType().LastOrDefault();