Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Force new combo on objects succeeding a break #31448

Merged
merged 4 commits into from
Jan 8, 2025

Conversation

bdach
Copy link
Collaborator

@bdach bdach commented Jan 7, 2025

No issue thread for this again. Reported internally on discord.

Placing this logic in the beatmap processor, as a post-processing step, means that the new combo force won't be visible until a placement has been committed. That can be seen as subpar, but I tried putting this logic in the placement and it sucked anyway:

  • While the combo number was correct, the colour looked off, because it would use the same combo colour as the already-placed objects after said break, which would only cycle to the next, correct one on placement
  • Not all scenarios can be handled in the placement. Refer to one of the test cases added in 973f606, wherein two objects are placed far apart from each other, and an automated break is inserted between them - the placement has no practical way of knowing whether it's going to have a break inserted automatically before it or not.

If you wanna check out the original version I tried and see why I didn't end up PRing it, see diff below:

diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
index 0ffd1072cd..2d340a08a4 100644
--- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs
@@ -46,6 +46,9 @@ public partial class ComposeBlueprintContainer : EditorBlueprintContainer
         [Resolved(canBeNull: true)]
         private EditorScreenWithTimeline editorScreen { get; set; }
 
+        [Resolved]
+        private EditorBeatmap editorBeatmap { get; set; }
+
         /// <remarks>
         /// Positional input must be received outside the container's bounds,
         /// in order to handle composer blueprints which are partially offscreen.
@@ -173,8 +176,14 @@ private void nudgeSelection(Vector2 delta)
 
         private void updatePlacementNewCombo()
         {
-            if (CurrentHitObjectPlacement?.HitObject is IHasComboInformation c)
-                c.NewCombo = NewCombo.Value == TernaryState.True;
+            var hitObject = CurrentHitObjectPlacement?.HitObject;
+            if (hitObject is not IHasComboInformation c)
+                return;
+
+            var closestBreak = editorBeatmap.Breaks.LastOrDefault(b => b.StartTime <= hitObject.StartTime);
+            bool isFirstAfterBreak = closestBreak != null && !editorBeatmap.HitObjects.Any(ho => ho.StartTime >= closestBreak.EndTime && ho.StartTime <= hitObject.StartTime);
+
+            c.NewCombo = NewCombo.Value == TernaryState.True || isFirstAfterBreak;
         }
 
         private void updatePlacementSamples()
@@ -364,6 +373,7 @@ protected override void Update()
 
                 // updates the placement with the latest editor clock time.
                 updatePlacementTimeAndPosition();
+                updatePlacementNewCombo();
             }
         }
 

bdach added 2 commits January 7, 2025 13:59
No issue thread for this again. Reported internally on discord:
https://discord.com/channels/90072389919997952/1259818301517725707/1320420768814727229

Placing this logic in the beatmap processor, as a post-processing step,
means that the new combo force won't be visible until a placement has
been committed. That can be seen as subpar, but I tried putting this
logic in the placement and it sucked anyway:

- While the combo number was correct, the colour looked off, because it
  would use the same combo colour as the already-placed objects after
  said break, which would only cycle to the next, correct one on
  placement

- Not all scenarios can be handled in the placement. Refer to one of the
  test cases added in the preceding commit, wherein two objects are
  placed far apart from each other, and an automated break is inserted
  between them - the placement has no practical way of knowing whether
  it's going to have a break inserted automatically before it or not.
@@ -100,5 +101,31 @@ private void autoGenerateBreaks()
Beatmap.Breaks.Add(breakPeriod);
}
}

private void ensureNewComboAfterBreaks()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think applying as a post-process is fine for now. Would suggest a slight refactor to avoid calling UpdateComboInformation a second time during processing when not required (also happens in PreProcess), and also switching to foreach to match the style of the implementation in that method closer:

diff --git a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
index 8108f51ad1..33d277c84c 100644
--- a/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmapProcessor.cs
@@ -111,20 +111,30 @@ private void ensureNewComboAfterBreaks()
 
             int currentBreak = 0;
 
-            for (int i = 0; i < Beatmap.HitObjects.Count; ++i)
-            {
-                var hitObject = Beatmap.HitObjects[i];
+            IHasComboInformation? lastObj = null;
+            bool requiredChanges = false;
 
+            foreach (var hitObject in Beatmap.HitObjects)
+            {
                 if (hitObject is not IHasComboInformation hasCombo)
                     continue;
 
-                if (currentBreak < breakEnds.Count && hitObject.StartTime >= breakEnds[currentBreak])
+                if (hitObject.StartTime >= breakEnds[currentBreak])
                 {
-                    hasCombo.NewCombo = true;
-                    currentBreak += 1;
+                    if (!hasCombo.NewCombo)
+                    {
+                        hasCombo.NewCombo = true;
+                        requiredChanges = true;
+                    }
+
+                    if (++currentBreak == breakEnds.Count)
+                        break;
                 }
 
-                hasCombo.UpdateComboInformation(i > 0 ? Beatmap.HitObjects[i - 1] as IHasComboInformation : null);
+                if (requiredChanges)
+                    hasCombo.UpdateComboInformation(lastObj);
+
+                lastObj = hasCombo;
             }
         }
     }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made the changes in 7c70dc4, but that's not your patch above applied verbatim. The reason why it's not is that the patch is slightly buggy; the break on ++currentBreak == breakEnds.Count is incorrect because it terminates the entire foreach, which notably includes the "update combo information if necessary" part. Therefore the entire logic would stop working if you inserted an object after the end of the last break in the beatmap.

I've added test coverage in fbfda2e to exercise this because I also stepped on this rake before.

@peppy peppy enabled auto-merge January 8, 2025 09:31
@bdach bdach disabled auto-merge January 8, 2025 10:22
@bdach bdach merged commit e7070bd into ppy:master Jan 8, 2025
10 checks passed
@bdach bdach deleted the automatic-new-combo-after-break branch January 8, 2025 10:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants