From c937c45360228a8f8d11808d7d3903fa7cf2227f Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 8 Jul 2021 18:49:32 +0900 Subject: [PATCH 1/5] Don't move selected objects outside the playfield in catch editor --- .../Edit/CatchSelectionHandler.cs | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d35d74d93d..9fcfa22cac 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; @@ -26,13 +29,19 @@ namespace osu.Game.Rulesets.Catch.Edit Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); float deltaX = targetPosition.X - originalPosition.X; + foreach (float x in EditorBeatmap.SelectedHitObjects.SelectMany(getOriginalPositions)) + deltaX = Math.Clamp(deltaX, -x, CatchPlayfield.WIDTH - x); + + if (deltaX == 0) + { + // Returns true: even there is no positional change, there may be a time change. + return true; + } + EditorBeatmap.PerformOnSelection(h => { if (!(h is CatchHitObject hitObject)) return; - if (hitObject is BananaShower) return; - - // TODO: confine in bounds hitObject.OriginalX += deltaX; // Move the nested hit objects to give an instant result before nested objects are recreated. @@ -42,5 +51,37 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } + + /// + /// Enumerate X positions that should be contained in-bounds after move offset is applied. + /// + private IEnumerable getOriginalPositions(HitObject hitObject) + { + switch (hitObject) + { + case Fruit fruit: + yield return fruit.OriginalX; + + break; + + case JuiceStream juiceStream: + foreach (var nested in juiceStream.NestedHitObjects.OfType()) + { + // Exclude tiny droplets: even if `OriginalX` is outside the playfield, it can be moved inside the playfield after the random offset application. + if (!(nested is TinyDroplet)) + yield return nested.OriginalX; + } + + break; + + case BananaShower _: + // A banana shower occupies the whole screen width. + // If the selection contains a banana shower, the selection cannot be moved horizontally. + yield return 0; + yield return CatchPlayfield.WIDTH; + + break; + } + } } } From 7e146796066588763ab367a6915e32c7a1f5d902 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 9 Jul 2021 12:58:08 +0900 Subject: [PATCH 2/5] Expand the selection movement limiting code with detailed comments --- .../Edit/CatchSelectionHandler.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 9fcfa22cac..d3e79e1778 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -27,10 +27,9 @@ namespace osu.Game.Rulesets.Catch.Edit var blueprint = moveEvent.Blueprint; Vector2 originalPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint); Vector2 targetPosition = HitObjectContainer.ToLocalSpace(blueprint.ScreenSpaceSelectionPoint + moveEvent.ScreenSpaceDelta); - float deltaX = targetPosition.X - originalPosition.X; - foreach (float x in EditorBeatmap.SelectedHitObjects.SelectMany(getOriginalPositions)) - deltaX = Math.Clamp(deltaX, -x, CatchPlayfield.WIDTH - x); + float deltaX = targetPosition.X - originalPosition.X; + deltaX = limitMovement(deltaX, EditorBeatmap.SelectedHitObjects); if (deltaX == 0) { @@ -52,6 +51,36 @@ namespace osu.Game.Rulesets.Catch.Edit return true; } + /// + /// Limit positional movement of the objects by the constraint that moved objects should stay in bounds. + /// + /// The positional movement. + /// The objects to be moved. + /// The positional movement with the restriction applied. + private float limitMovement(float deltaX, IEnumerable movingObjects) + { + float minX = float.PositiveInfinity; + float maxX = float.NegativeInfinity; + + foreach (float x in movingObjects.SelectMany(getOriginalPositions)) + { + minX = Math.Min(minX, x); + maxX = Math.Max(maxX, x); + } + + // To make an object with position `x` stay in bounds after `deltaX` movement, `0 <= x + deltaX <= WIDTH` should be satisfied. + // Subtracting `x`, we get `-x <= deltaX <= WIDTH - x`. + // We only need to apply the inequality to extreme values of `x`. + float lowerBound = -minX; + float upperBound = CatchPlayfield.WIDTH - maxX; + // The inequality may be unsatisfiable if the objects were already out of bounds. + // In that case, don't move objects at all. + if (!(lowerBound <= upperBound)) + return 0; + + return Math.Clamp(deltaX, lowerBound, upperBound); + } + /// /// Enumerate X positions that should be contained in-bounds after move offset is applied. /// From 995ef953c689746c546457b9ec477368a89af366 Mon Sep 17 00:00:00 2001 From: ekrctb Date: Fri, 9 Jul 2021 15:13:54 +0900 Subject: [PATCH 3/5] Modify comment --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index d3e79e1778..02dc6f61c8 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (deltaX == 0) { - // Returns true: even there is no positional change, there may be a time change. + // Even there is no positional change, there may be a time change. return true; } @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Edit case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects.OfType()) { - // Exclude tiny droplets: even if `OriginalX` is outside the playfield, it can be moved inside the playfield after the random offset application. + // Even if `OriginalX` is outside the playfield, tiny droplets can be moved inside the playfield after the random offset application. if (!(nested is TinyDroplet)) yield return nested.OriginalX; } From 494089e402dcd358dc6d1e20eadf8736fa858407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:22:54 +0200 Subject: [PATCH 4/5] Fix up English in comment --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 02dc6f61c8..51ccdb7410 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Edit if (deltaX == 0) { - // Even there is no positional change, there may be a time change. + // Even if there is no positional change, there may be a time change. return true; } From c5011865fca0f93672703d183b3268d5071cbd86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 10 Jul 2021 11:23:38 +0200 Subject: [PATCH 5/5] Invert strangely negated condition --- osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs index 51ccdb7410..7eebf04ca2 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Edit float upperBound = CatchPlayfield.WIDTH - maxX; // The inequality may be unsatisfiable if the objects were already out of bounds. // In that case, don't move objects at all. - if (!(lowerBound <= upperBound)) + if (lowerBound > upperBound) return 0; return Math.Clamp(deltaX, lowerBound, upperBound);