diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
index 9bab065d1e..ffa35c24cd 100644
--- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
+++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
@@ -20,7 +20,13 @@ namespace osu.Game.Rulesets.Timing
///
/// The aggregate multiplier which this provides.
///
- public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * 1000 / TimingPoint.BeatLength;
+ public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength;
+
+ ///
+ /// The base beat length to scale the provided multiplier relative to.
+ ///
+ /// For a of 1000, a with a beat length of 500 will increase the multiplier by 2.
+ public double BaseBeatLength = 1000;
///
/// The velocity multiplier.
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index 42ec0b79b9..385f824ef5 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -69,6 +69,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
protected virtual bool UserScrollSpeedAdjustment => true;
+ ///
+ /// Whether beat lengths should scale relative to the most common beat length in the .
+ ///
+ protected virtual bool RelativeScaleBeatLengths => false;
+
///
/// Provides the default s that adjust the scrolling rate of s
/// inside this .
@@ -107,16 +112,35 @@ namespace osu.Game.Rulesets.UI.Scrolling
[BackgroundDependencyLoader]
private void load()
{
- // Calculate default multiplier control points
+ double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
+ double baseBeatLength = 1000;
+
+ if (RelativeScaleBeatLengths)
+ {
+ IReadOnlyList timingPoints = Beatmap.ControlPointInfo.TimingPoints;
+ double maxDuration = 0;
+
+ for (int i = 0; i < timingPoints.Count; i++)
+ {
+ double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime;
+ double duration = endTime - timingPoints[i].Time;
+
+ if (duration > maxDuration)
+ {
+ maxDuration = duration;
+ baseBeatLength = timingPoints[i].BeatLength;
+ }
+ }
+ }
+
+ // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
var lastTimingPoint = new TimingControlPoint();
var lastDifficultyPoint = new DifficultyControlPoint();
-
- // Merge timing + difficulty points
var allPoints = new SortedList(Comparer.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints);
- // Generate the timing points, making non-timing changes use the previous timing change
+ // Generate the timing points, making non-timing changes use the previous timing change and vice-versa
var timingChanges = allPoints.Select(c =>
{
var timingPoint = c as TimingControlPoint;
@@ -131,14 +155,13 @@ namespace osu.Game.Rulesets.UI.Scrolling
return new MultiplierControlPoint(c.Time)
{
Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier,
+ BaseBeatLength = baseBeatLength,
TimingPoint = lastTimingPoint,
DifficultyPoint = lastDifficultyPoint
};
});
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- // Perform some post processing of the timing changes
+ // Trim unwanted sequences of timing changes
timingChanges = timingChanges
// Collapse sections after the last hit object
.Where(s => s.StartTime <= lastObjectTime)
@@ -147,7 +170,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
controlPoints.AddRange(timingChanges);
- // If we have no control points, add a default one
if (controlPoints.Count == 0)
controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
}