diff --git a/osu.Android.props b/osu.Android.props
index 6cbb4b2e68..d701aaf199 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 4d73e711bb..11571ea761 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -46,7 +46,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private void addConnection(FollowPointConnection connection)
{
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
- int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
+ int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) =>
+ {
+ int comp = g1.StartTime.Value.CompareTo(g2.StartTime.Value);
+
+ if (comp != 0)
+ return comp;
+
+ // we always want to insert the new item after equal ones.
+ // this is important for beatmaps with multiple hitobjects at the same point in time.
+ // if we use standard comparison insert order, there will be a churn of connections getting re-updated to
+ // the next object at the point-in-time, adding a construction/disposal overhead (see FollowPointConnection.End implementation's ClearInternal).
+ // this is easily visible on https://osu.ppy.sh/beatmapsets/150945#osu/372245
+ return -1;
+ }));
if (index < connections.Count - 1)
{
diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
index eebf6980fe..40565048c2 100644
--- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
yield return new TernaryStateMenuItem("Rim", action: state =>
{
+ ChangeHandler.BeginChange();
+
foreach (var h in hits)
{
switch (state)
@@ -35,6 +37,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
break;
}
}
+
+ ChangeHandler.EndChange();
})
{
State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) }
@@ -47,6 +51,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
yield return new TernaryStateMenuItem("Strong", action: state =>
{
+ ChangeHandler.BeginChange();
+
foreach (var h in hits)
{
switch (state)
@@ -62,6 +68,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
EditorBeatmap?.UpdateHitObject(h);
}
+
+ ChangeHandler.EndChange();
})
{
State = { Value = getTernaryState(hits, h => h.IsStrong) }
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 2d5e4b911e..58cc324233 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Gameplay
private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
{
- public bool NewCombo { get; } = false;
+ public bool NewCombo { get; set; } = false;
public int ComboOffset { get; } = 0;
public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
index 08130e60db..c2a18330c9 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online
public void TestMultipleLoads()
{
var comments = exampleComments;
- int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel);
+ int topLevelCommentCount = exampleComments.Comments.Count;
AddStep("hide container", () => commentsContainer.Hide());
setUpCommentsResponse(comments);
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index e42a359d2e..28a77a8bdf 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Edit
}
catch (Exception e)
{
- Logger.Error(e, "Could not load beatmap sucessfully!");
+ Logger.Error(e, "Could not load beatmap successfully!");
return;
}
diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
index 4e3de04278..211c077d4f 100644
--- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
+++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Objects.Types
///
int ComboIndex { get; set; }
+ ///
+ /// Whether the HitObject starts a new combo.
+ ///
+ new bool NewCombo { get; set; }
+
Bindable LastInComboBindable { get; }
///
diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs
index f4f66f1272..9a0217a1eb 100644
--- a/osu.Game/Rulesets/UI/HitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs
@@ -43,10 +43,20 @@ namespace osu.Game.Rulesets.UI
return true;
}
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ unbindStartTimeMap();
+ }
+
public virtual void Clear(bool disposeChildren = true)
{
ClearInternal(disposeChildren);
+ unbindStartTimeMap();
+ }
+ private void unbindStartTimeMap()
+ {
foreach (var kvp in startTimeMap)
kvp.Value.bindable.UnbindAll();
startTimeMap.Clear();
diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
index f95bf350b6..6e2c8bd01c 100644
--- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs
@@ -20,6 +20,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
@@ -44,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected EditorBeatmap EditorBeatmap { get; private set; }
[Resolved(CanBeNull = true)]
- private IEditorChangeHandler changeHandler { get; set; }
+ protected IEditorChangeHandler ChangeHandler { get; private set; }
public SelectionHandler()
{
@@ -193,12 +194,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void deleteSelected()
{
- changeHandler?.BeginChange();
+ ChangeHandler?.BeginChange();
foreach (var h in selectedBlueprints.ToList())
EditorBeatmap?.Remove(h.HitObject);
- changeHandler?.EndChange();
+ ChangeHandler?.EndChange();
}
#endregion
@@ -254,7 +255,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// The name of the hit sample.
public void AddHitSample(string sampleName)
{
- changeHandler?.BeginChange();
+ ChangeHandler?.BeginChange();
foreach (var h in SelectedHitObjects)
{
@@ -265,7 +266,30 @@ namespace osu.Game.Screens.Edit.Compose.Components
h.Samples.Add(new HitSampleInfo { Name = sampleName });
}
- changeHandler?.EndChange();
+ ChangeHandler?.EndChange();
+ }
+
+ ///
+ /// Set the new combo state of all selected s.
+ ///
+ /// Whether to set or unset.
+ /// Throws if any selected object doesn't implement
+ public void SetNewCombo(bool state)
+ {
+ ChangeHandler?.BeginChange();
+
+ foreach (var h in SelectedHitObjects)
+ {
+ var comboInfo = h as IHasComboInformation;
+
+ if (comboInfo == null)
+ throw new InvalidOperationException($"Tried to change combo state of a {h.GetType()}, which doesn't implement {nameof(IHasComboInformation)}");
+
+ comboInfo.NewCombo = state;
+ EditorBeatmap?.UpdateHitObject(h);
+ }
+
+ ChangeHandler?.EndChange();
}
///
@@ -274,12 +298,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// The name of the hit sample.
public void RemoveHitSample(string sampleName)
{
- changeHandler?.BeginChange();
+ ChangeHandler?.BeginChange();
foreach (var h in SelectedHitObjects)
h.SamplesBindable.RemoveAll(s => s.Name == sampleName);
- changeHandler?.EndChange();
+ ChangeHandler?.EndChange();
}
#endregion
@@ -297,6 +321,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints));
+ if (selectedBlueprints.All(b => b.HitObject is IHasComboInformation))
+ items.Add(createNewComboMenuItem());
+
if (selectedBlueprints.Count == 1)
items.AddRange(selectedBlueprints[0].ContextMenuItems);
@@ -326,6 +353,41 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected virtual IEnumerable