Skip to content

Commit

Permalink
Merge pull request #28792 from bdach/stacking-updates
Browse files Browse the repository at this point in the history
Run stacking while performing movement in osu! composer
  • Loading branch information
smoogipoo authored Aug 5, 2024
2 parents 136cdcf + 117d6bf commit fb8f80f
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 7 deletions.
17 changes: 11 additions & 6 deletions osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,27 @@ public override void PostProcess()
{
base.PostProcess();

var hitObjects = Beatmap.HitObjects as List<OsuHitObject> ?? Beatmap.HitObjects.OfType<OsuHitObject>().ToList();
ApplyStacking(Beatmap);
}

internal static void ApplyStacking(IBeatmap beatmap)
{
var hitObjects = beatmap.HitObjects as List<OsuHitObject> ?? beatmap.HitObjects.OfType<OsuHitObject>().ToList();

if (hitObjects.Count > 0)
{
// Reset stacking
foreach (var h in hitObjects)
h.StackHeight = 0;

if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
else
applyStackingOld(Beatmap.BeatmapInfo, hitObjects);
applyStackingOld(beatmap.BeatmapInfo, hitObjects);
}
}

private void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
Expand Down Expand Up @@ -209,7 +214,7 @@ private void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObject
}
}

private void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
{
for (int i = 0; i < hitObjects.Count; i++)
{
Expand Down
6 changes: 6 additions & 0 deletions osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ private bool snapToVisibleBlueprints(Vector2 screenSpacePosition, out SnapResult

if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
{
// if the snap target is a stacked object, snap to its unstacked position rather than its stacked position.
// this is intended to make working with stacks easier (because thanks to this, you can drag an object to any
// of the items on the stack to add an object to it, rather than having to drag to the position of the *first* object on it at all times).
if (b.Item is OsuHitObject osuObject && osuObject.StackOffset != Vector2.Zero)
closestSnapPosition = b.ToScreenSpace(b.ToLocalSpace(closestSnapPosition) - osuObject.StackOffset);

// only return distance portion, since time is not really valid
snapResult = new SnapResult(closestSnapPosition, null, playfield);
return true;
Expand Down
24 changes: 23 additions & 1 deletion osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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 osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components;
Expand Down Expand Up @@ -50,12 +51,33 @@ public override bool HandleMovement(MoveSelectionEvent<HitObject> moveEvent)
{
var hitObjects = selectedMovableObjects;

var localDelta = this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);

// this conditional is a rather ugly special case for stacks.
// as it turns out, adding the `EditorBeatmap.Update()` call at the end of this would cause stacked objects to jitter when moved around
// (they would stack and then unstack every frame).
// the reason for that is that the selection handling abstractions are not aware of the distinction between "displayed" and "actual" position
// which is unique to osu! due to stacking being applied as a post-processing step.
// therefore, the following loop would occur:
// - on frame 1 the blueprint is snapped to the stack's baseline position. `EditorBeatmap.Update()` applies stacking successfully,
// the blueprint moves up the stack from its original drag position.
// - on frame 2 the blueprint's position is now the *stacked* position, which is interpreted higher up as *manually performing an unstack*
// to the blueprint's unstacked position (as the machinery higher up only cares about differences in screen space position).
if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset)))
return true;

// this will potentially move the selection out of bounds...
foreach (var h in hitObjects)
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
h.Position += localDelta;

// but this will be corrected.
moveSelectionInBounds();

// manually update stacking.
// this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons,
// as the entire flow is too expensive to run on every movement.
Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap);

return true;
}

Expand Down

0 comments on commit fb8f80f

Please sign in to comment.