diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index c50250159e..7317771bac 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
- Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
+ Assert.AreEqual(6, controlPoints.DifficultyPoints.Count);
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
- Assert.AreEqual(8, controlPoints.EffectPoints.Count);
+ Assert.AreEqual(9, controlPoints.EffectPoints.Count);
var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time);
@@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(48428);
- Assert.AreEqual(0, difficultyPoint.Time);
+ Assert.AreEqual(956, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(116999);
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
index c4b990675e..d57baf25be 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
@@ -30,11 +30,13 @@ namespace osu.Game.Beatmaps.ControlPoints
public void Add(ControlPoint point)
{
- point.AttachGroup(this);
+ var existing = controlPoints.FirstOrDefault(p => p.GetType() == point.GetType());
- foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray())
+ if (existing != null)
Remove(existing);
+ point.AttachGroup(this);
+
controlPoints.Add(point);
ItemAdded?.Invoke(point);
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index b7bb993fc0..f8c6d3aa3b 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -62,28 +62,28 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The time to find the difficulty control point at.
/// The difficulty control point.
- public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
+ public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time);
///
/// Finds the effect control point that is active at .
///
/// The time to find the effect control point at.
/// The effect control point.
- public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
+ public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time);
///
/// Finds the sound control point that is active at .
///
/// The time to find the sound control point at.
/// The sound control point.
- public SampleControlPoint SamplePointAt(double time) => binarySearch(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
+ public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
///
/// Finds the timing control point that is active at .
///
/// The time to find the timing control point at.
/// The timing control point.
- public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
+ public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
///
/// Finds the closest of the same type as that is active at .
@@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps.ControlPoints
{
switch (referencePoint)
{
- case TimingControlPoint _: return TimingPointAt(time);
+ case TimingControlPoint _: return binarySearch(TimingPoints, time);
- case EffectControlPoint _: return EffectPointAt(time);
+ case EffectControlPoint _: return binarySearch(EffectPoints, time);
- case SampleControlPoint _: return SamplePointAt(time);
+ case SampleControlPoint _: return binarySearch(SamplePoints, time);
- case DifficultyControlPoint _: return DifficultyPointAt(time);
+ case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time);
}
return null;
@@ -130,22 +130,35 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// Binary searches one of the control point lists to find the active control point at .
+ /// Includes logic for returning a specific point when no matching point is found.
///
/// The list to search.
/// The time to find the control point at.
/// The control point to use when is before any control points. If null, a new control point will be constructed.
- /// The active control point at .
- private T binarySearch(IReadOnlyList list, double time, T prePoint = null)
+ /// The active control point at , or a fallback if none found.
+ private T binarySearchWithFallback(IReadOnlyList list, double time, T prePoint = null)
where T : ControlPoint, new()
+ {
+ return binarySearch(list, time) ?? prePoint ?? new T();
+ }
+
+ ///
+ /// Binary searches one of the control point lists to find the active control point at .
+ ///
+ /// The list to search.
+ /// The time to find the control point at.
+ /// The active control point at .
+ private T binarySearch(IReadOnlyList list, double time)
+ where T : ControlPoint
{
if (list == null)
throw new ArgumentNullException(nameof(list));
if (list.Count == 0)
- return new T();
+ return null;
if (time < list[0].Time)
- return prePoint ?? new T();
+ return null;
if (time >= list[list.Count - 1].Time)
return list[list.Count - 1];
@@ -240,6 +253,13 @@ namespace osu.Game.Beatmaps.ControlPoints
}
}
- public void Clear() => groups.Clear();
+ public void Clear()
+ {
+ groups.Clear();
+ timingPoints.Clear();
+ difficultyPoints.Clear();
+ samplePoints.Clear();
+ effectPoints.Clear();
+ }
}
}