mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 10:42:54 +08:00
Merge pull request #6632 from peppy/bindable-control-points
Bindable control points
This commit is contained in:
commit
073066170c
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
var controlPointInfo = new ControlPointInfo();
|
var controlPointInfo = new ControlPointInfo();
|
||||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
controlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||||
{
|
{
|
||||||
|
@ -64,9 +64,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||||
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
editorBeatmap.ControlPointInfo.Clear();
|
||||||
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
|
@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
||||||
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
||||||
|
|
||||||
|
@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}, 25),
|
}, 25),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ControlPointInfo =
|
|
||||||
{
|
|
||||||
DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
|
|
||||||
},
|
|
||||||
BeatmapInfo =
|
BeatmapInfo =
|
||||||
{
|
{
|
||||||
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
||||||
@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
p.OnLoadComplete += _ =>
|
p.OnLoadComplete += _ =>
|
||||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
AddStep("Reset height", () => changePlayfieldSize(6));
|
AddStep("Reset height", () => changePlayfieldSize(6));
|
||||||
|
|
||||||
var controlPointInfo = new ControlPointInfo();
|
var controlPointInfo = new ControlPointInfo();
|
||||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
controlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||||
{
|
{
|
||||||
@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
|
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
|
||||||
|
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
|
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||||
|
|
||||||
Hit hit = new Hit();
|
Hit hit = new Hit();
|
||||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
|
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
|
||||||
|
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
|
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||||
|
|
||||||
Hit hit = new Hit();
|
Hit hit = new Hit();
|
||||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||||
|
@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
|
|||||||
{
|
{
|
||||||
this.controlPoints = controlPoints;
|
this.controlPoints = controlPoints;
|
||||||
|
|
||||||
IEnumerable<SampleControlPoint> samplePoints;
|
IEnumerable<SampleControlPoint> samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints;
|
||||||
if (controlPoints.SamplePoints.Count == 0)
|
|
||||||
// Get the default sample point
|
|
||||||
samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) };
|
|
||||||
else
|
|
||||||
samplePoints = controlPoints.SamplePoints;
|
|
||||||
|
|
||||||
foreach (var s in samplePoints)
|
foreach (var s in samplePoints)
|
||||||
{
|
{
|
||||||
|
@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var controlPoints = beatmap.ControlPointInfo;
|
var controlPoints = beatmap.ControlPointInfo;
|
||||||
|
|
||||||
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||||
Assert.AreEqual(42, controlPoints.DifficultyPoints.Count);
|
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||||
Assert.AreEqual(42, controlPoints.SamplePoints.Count);
|
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
|
||||||
Assert.AreEqual(42, controlPoints.EffectPoints.Count);
|
Assert.AreEqual(8, controlPoints.EffectPoints.Count);
|
||||||
|
|
||||||
var timingPoint = controlPoints.TimingPointAt(0);
|
var timingPoint = controlPoints.TimingPointAt(0);
|
||||||
Assert.AreEqual(956, timingPoint.Time);
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
||||||
|
|
||||||
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
||||||
Assert.AreEqual(48428, difficultyPoint.Time);
|
Assert.AreEqual(0, difficultyPoint.Time);
|
||||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
||||||
|
|
||||||
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
||||||
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||||
|
|
||||||
effectPoint = controlPoints.EffectPointAt(119637);
|
effectPoint = controlPoints.EffectPointAt(119637);
|
||||||
Assert.AreEqual(119637, effectPoint.Time);
|
Assert.AreEqual(95901, effectPoint.Time);
|
||||||
Assert.IsFalse(effectPoint.KiaiMode);
|
Assert.IsFalse(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||||
}
|
}
|
||||||
@ -262,6 +262,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTimingPointResetsSpeedMultiplier()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var controlPoints = decoder.Decode(stream).ControlPointInfo;
|
||||||
|
|
||||||
|
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1));
|
||||||
|
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestDecodeBeatmapColours()
|
public void TestDecodeBeatmapColours()
|
||||||
{
|
{
|
||||||
|
@ -25,11 +25,10 @@ namespace osu.Game.Tests.Editor
|
|||||||
BeatDivisor.Value = 1;
|
BeatDivisor.Value = 1;
|
||||||
|
|
||||||
composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
|
||||||
|
|
||||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = 1 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 1000 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||||
});
|
});
|
||||||
|
|
||||||
[TestCase(1)]
|
[TestCase(1)]
|
||||||
@ -47,8 +46,8 @@ namespace osu.Game.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep($"set multiplier = {multiplier}", () =>
|
AddStep($"set multiplier = {multiplier}", () =>
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
||||||
});
|
});
|
||||||
|
|
||||||
assertSnapDistance(100 * multiplier);
|
assertSnapDistance(100 * multiplier);
|
||||||
@ -76,8 +75,8 @@ namespace osu.Game.Tests.Editor
|
|||||||
|
|
||||||
AddStep("set beat length = 500", () =>
|
AddStep("set beat length = 500", () =>
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
});
|
});
|
||||||
|
|
||||||
assertDurationToDistance(500, 200);
|
assertDurationToDistance(500, 200);
|
||||||
@ -97,8 +96,8 @@ namespace osu.Game.Tests.Editor
|
|||||||
|
|
||||||
AddStep("set beat length = 500", () =>
|
AddStep("set beat length = 500", () =>
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
});
|
});
|
||||||
|
|
||||||
assertDistanceToDuration(200, 500);
|
assertDistanceToDuration(200, 500);
|
||||||
@ -124,8 +123,8 @@ namespace osu.Game.Tests.Editor
|
|||||||
|
|
||||||
AddStep("set beat length = 500", () =>
|
AddStep("set beat length = 500", () =>
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
});
|
});
|
||||||
|
|
||||||
assertSnappedDuration(50, 0);
|
assertSnappedDuration(50, 0);
|
||||||
@ -155,8 +154,8 @@ namespace osu.Game.Tests.Editor
|
|||||||
|
|
||||||
AddStep("set beat length = 500", () =>
|
AddStep("set beat length = 500", () =>
|
||||||
{
|
{
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
});
|
});
|
||||||
|
|
||||||
assertSnappedDistance(50, 0);
|
assertSnappedDistance(50, 0);
|
||||||
|
227
osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
Normal file
227
osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.NonVisual
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ControlPointInfoTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestAdd()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new TimingControlPoint());
|
||||||
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddRedundantTiming()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point.
|
||||||
|
cpi.Add(1000, new TimingControlPoint()); // is redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddRedundantDifficulty()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new DifficultyControlPoint()); // is redundant
|
||||||
|
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
|
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddRedundantSample()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new SampleControlPoint()); // is redundant
|
||||||
|
cpi.Add(1000, new SampleControlPoint()); // is redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
|
cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddRedundantEffect()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new EffectControlPoint()); // is redundant
|
||||||
|
cpi.Add(1000, new EffectControlPoint()); // is redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
|
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddGroup()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
var group2 = cpi.GroupAt(1000, true);
|
||||||
|
|
||||||
|
Assert.That(group, Is.EqualTo(group2));
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestGroupAtLookupOnly()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(5000, true);
|
||||||
|
Assert.That(group, Is.Not.Null);
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.GroupAt(1000), Is.Null);
|
||||||
|
Assert.That(cpi.GroupAt(5000), Is.Not.Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddRemoveGroup()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
cpi.RemoveGroup(group);
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddControlPointToGroup()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
// usually redundant, but adding to group forces it to be added
|
||||||
|
group.Add(new DifficultyControlPoint());
|
||||||
|
|
||||||
|
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddDuplicateControlPointToGroup()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
group.Add(new DifficultyControlPoint());
|
||||||
|
group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 });
|
||||||
|
|
||||||
|
Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
|
||||||
|
Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveControlPointFromGroup()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
var group = cpi.GroupAt(1000, true);
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
|
|
||||||
|
var difficultyPoint = new DifficultyControlPoint();
|
||||||
|
|
||||||
|
group.Add(difficultyPoint);
|
||||||
|
group.Remove(difficultyPoint);
|
||||||
|
|
||||||
|
Assert.That(group.ControlPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOrdering()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new TimingControlPoint());
|
||||||
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||||
|
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
||||||
|
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
||||||
|
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||||
|
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8));
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time)));
|
||||||
|
|
||||||
|
Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time)));
|
||||||
|
Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestClear()
|
||||||
|
{
|
||||||
|
var cpi = new ControlPointInfo();
|
||||||
|
|
||||||
|
cpi.Add(0, new TimingControlPoint());
|
||||||
|
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
|
||||||
|
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 });
|
||||||
|
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 });
|
||||||
|
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
|
||||||
|
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
cpi.Clear();
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,-200,4,1,0,100,0,0
|
||||||
|
2000,100,1,1,0,100,1,0
|
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
public TestSceneDistanceSnapGrid()
|
public TestSceneDistanceSnapGrid()
|
||||||
{
|
{
|
||||||
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
||||||
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
{
|
{
|
||||||
var testBeatmap = new Beatmap
|
var testBeatmap = new Beatmap
|
||||||
{
|
{
|
||||||
ControlPointInfo = new ControlPointInfo
|
ControlPointInfo = new ControlPointInfo(),
|
||||||
{
|
|
||||||
TimingPoints =
|
|
||||||
{
|
|
||||||
new TimingControlPoint { Time = 0, BeatLength = 200 },
|
|
||||||
new TimingControlPoint { Time = 100, BeatLength = 400 },
|
|
||||||
new TimingControlPoint { Time = 175, BeatLength = 800 },
|
|
||||||
new TimingControlPoint { Time = 350, BeatLength = 200 },
|
|
||||||
new TimingControlPoint { Time = 450, BeatLength = 100 },
|
|
||||||
new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
HitObjects =
|
HitObjects =
|
||||||
{
|
{
|
||||||
new HitCircle { StartTime = 0 },
|
new HitCircle { StartTime = 0 },
|
||||||
@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 });
|
||||||
|
testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 });
|
||||||
|
testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 });
|
||||||
|
testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 });
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(testBeatmap);
|
Beatmap.Value = CreateWorkingBeatmap(testBeatmap);
|
||||||
|
|
||||||
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
|
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
|
||||||
|
@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRelativeBeatLengthScaleSingleTimingPoint()
|
public void TestRelativeBeatLengthScaleSingleTimingPoint()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 });
|
var beatmap = createBeatmap();
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||||
|
|
||||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||||
|
|
||||||
@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
|
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(
|
var beatmap = createBeatmap();
|
||||||
new TimingControlPoint { BeatLength = time_range / 2 },
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||||
new TimingControlPoint { Time = 12000, BeatLength = time_range },
|
beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range });
|
||||||
new TimingControlPoint { Time = 100000, BeatLength = time_range });
|
beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range });
|
||||||
|
|
||||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||||
|
|
||||||
@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
|
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(
|
var beatmap = createBeatmap();
|
||||||
new TimingControlPoint { BeatLength = time_range },
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||||
|
|
||||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||||
|
|
||||||
@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestNonRelativeScale()
|
public void TestNonRelativeScale()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(
|
var beatmap = createBeatmap();
|
||||||
new TimingControlPoint { BeatLength = time_range },
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||||
|
|
||||||
createTest(beatmap);
|
createTest(beatmap);
|
||||||
|
|
||||||
@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSliderMultiplierDoesNotAffectRelativeBeatLength()
|
public void TestSliderMultiplierDoesNotAffectRelativeBeatLength()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range });
|
var beatmap = createBeatmap();
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||||
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
||||||
|
|
||||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||||
@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestSliderMultiplierAffectsNonRelativeBeatLength()
|
public void TestSliderMultiplierAffectsNonRelativeBeatLength()
|
||||||
{
|
{
|
||||||
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range });
|
var beatmap = createBeatmap();
|
||||||
|
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||||
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
|
||||||
|
|
||||||
createTest(beatmap);
|
createTest(beatmap);
|
||||||
@ -154,14 +157,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
/// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points.
|
/// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points.
|
||||||
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
|
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="timingControlPoints">The timing points to add to the beatmap.</param>
|
|
||||||
/// <returns>The <see cref="IBeatmap"/>.</returns>
|
/// <returns>The <see cref="IBeatmap"/>.</returns>
|
||||||
private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints)
|
private IBeatmap createBeatmap()
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
|
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
|
||||||
|
|
||||||
beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
|
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
|
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Lists;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private SortedList<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints;
|
private List<TimingControlPoint> timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ToList();
|
||||||
|
|
||||||
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
|
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -10,12 +10,11 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time at which the control point takes effect.
|
/// The time at which the control point takes effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double Time;
|
public double Time => controlPointGroup?.Time ?? 0;
|
||||||
|
|
||||||
/// <summary>
|
private ControlPointGroup controlPointGroup;
|
||||||
/// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap.
|
|
||||||
/// </summary>
|
public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup;
|
||||||
internal bool AutoGenerated;
|
|
||||||
|
|
||||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||||
|
|
||||||
|
50
osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
Normal file
50
osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
|
{
|
||||||
|
public class ControlPointGroup : IComparable<ControlPointGroup>
|
||||||
|
{
|
||||||
|
public event Action<ControlPoint> ItemAdded;
|
||||||
|
public event Action<ControlPoint> ItemRemoved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time at which the control point takes effect.
|
||||||
|
/// </summary>
|
||||||
|
public double Time { get; }
|
||||||
|
|
||||||
|
public IBindableList<ControlPoint> ControlPoints => controlPoints;
|
||||||
|
|
||||||
|
private readonly BindableList<ControlPoint> controlPoints = new BindableList<ControlPoint>();
|
||||||
|
|
||||||
|
public ControlPointGroup(double time)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time);
|
||||||
|
|
||||||
|
public void Add(ControlPoint point)
|
||||||
|
{
|
||||||
|
var existing = controlPoints.FirstOrDefault(p => p.GetType() == point.GetType());
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
Remove(existing);
|
||||||
|
|
||||||
|
point.AttachGroup(this);
|
||||||
|
|
||||||
|
controlPoints.Add(point);
|
||||||
|
ItemAdded?.Invoke(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(ControlPoint point)
|
||||||
|
{
|
||||||
|
controlPoints.Remove(point);
|
||||||
|
ItemRemoved?.Invoke(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Lists;
|
using osu.Framework.Lists;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.ControlPoints
|
namespace osu.Game.Beatmaps.ControlPoints
|
||||||
@ -12,57 +13,78 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class ControlPointInfo
|
public class ControlPointInfo
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All control points grouped by time.
|
||||||
|
/// </summary>
|
||||||
|
[JsonProperty]
|
||||||
|
public IBindableList<ControlPointGroup> Groups => groups;
|
||||||
|
|
||||||
|
private readonly BindableList<ControlPointGroup> groups = new BindableList<ControlPointGroup>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All timing points.
|
/// All timing points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public SortedList<TimingControlPoint> TimingPoints { get; private set; } = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
|
public IReadOnlyList<TimingControlPoint> TimingPoints => timingPoints;
|
||||||
|
|
||||||
|
private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All difficulty points.
|
/// All difficulty points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public SortedList<DifficultyControlPoint> DifficultyPoints { get; private set; } = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
|
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
|
||||||
|
|
||||||
|
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All sound points.
|
/// All sound points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public SortedList<SampleControlPoint> SamplePoints { get; private set; } = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default);
|
public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints;
|
||||||
|
|
||||||
|
private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All effect points.
|
/// All effect points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public SortedList<EffectControlPoint> EffectPoints { get; private set; } = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
|
public IReadOnlyList<EffectControlPoint> EffectPoints => effectPoints;
|
||||||
|
|
||||||
|
private readonly SortedList<EffectControlPoint> effectPoints = new SortedList<EffectControlPoint>(Comparer<EffectControlPoint>.Default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All control points, of all types.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the difficulty control point at.</param>
|
/// <param name="time">The time to find the difficulty control point at.</param>
|
||||||
/// <returns>The difficulty control point.</returns>
|
/// <returns>The difficulty control point.</returns>
|
||||||
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
|
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the effect control point at.</param>
|
/// <param name="time">The time to find the effect control point at.</param>
|
||||||
/// <returns>The effect control point.</returns>
|
/// <returns>The effect control point.</returns>
|
||||||
public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
|
public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the sound control point at.</param>
|
/// <param name="time">The time to find the sound control point at.</param>
|
||||||
/// <returns>The sound control point.</returns>
|
/// <returns>The sound control point.</returns>
|
||||||
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the timing control point that is active at <paramref name="time"/>.
|
/// Finds the timing control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the timing control point at.</param>
|
/// <param name="time">The time to find the timing control point at.</param>
|
||||||
/// <returns>The timing control point.</returns>
|
/// <returns>The timing control point.</returns>
|
||||||
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the maximum BPM represented by any timing control point.
|
/// Finds the maximum BPM represented by any timing control point.
|
||||||
@ -85,24 +107,93 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public double BPMMode =>
|
public double BPMMode =>
|
||||||
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remove all <see cref="ControlPointGroup"/>s and return to a pristine state.
|
||||||
|
/// </summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
groups.Clear();
|
||||||
|
timingPoints.Clear();
|
||||||
|
difficultyPoints.Clear();
|
||||||
|
samplePoints.Clear();
|
||||||
|
effectPoints.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new <see cref="ControlPoint"/>. Note that the provided control point may not be added if the correct state is already present at the provided time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time at which the control point should be added.</param>
|
||||||
|
/// <param name="controlPoint">The control point to add.</param>
|
||||||
|
/// <returns>Whether the control point was added.</returns>
|
||||||
|
public bool Add(double time, ControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
if (checkAlreadyExisting(time, controlPoint))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GroupAt(time, true).Add(controlPoint);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlPointGroup GroupAt(double time, bool addIfNotExisting = false)
|
||||||
|
{
|
||||||
|
var newGroup = new ControlPointGroup(time);
|
||||||
|
|
||||||
|
int i = groups.BinarySearch(newGroup);
|
||||||
|
|
||||||
|
if (i >= 0)
|
||||||
|
return groups[i];
|
||||||
|
|
||||||
|
if (addIfNotExisting)
|
||||||
|
{
|
||||||
|
newGroup.ItemAdded += groupItemAdded;
|
||||||
|
newGroup.ItemRemoved += groupItemRemoved;
|
||||||
|
|
||||||
|
groups.Insert(~i, newGroup);
|
||||||
|
return newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveGroup(ControlPointGroup group)
|
||||||
|
{
|
||||||
|
group.ItemAdded -= groupItemAdded;
|
||||||
|
group.ItemRemoved -= groupItemRemoved;
|
||||||
|
|
||||||
|
groups.Remove(group);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
|
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
|
||||||
|
/// Includes logic for returning a specific point when no matching point is found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="list">The list to search.</param>
|
/// <param name="list">The list to search.</param>
|
||||||
/// <param name="time">The time to find the control point at.</param>
|
/// <param name="time">The time to find the control point at.</param>
|
||||||
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
|
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
|
||||||
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
||||||
private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
|
private T binarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T prePoint = null)
|
||||||
where T : ControlPoint, new()
|
where T : ControlPoint, new()
|
||||||
|
{
|
||||||
|
return binarySearch(list, time) ?? prePoint ?? new T();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Binary searches one of the control point lists to find the active control point at <paramref name="time"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="list">The list to search.</param>
|
||||||
|
/// <param name="time">The time to find the control point at.</param>
|
||||||
|
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
||||||
|
private T binarySearch<T>(IReadOnlyList<T> list, double time)
|
||||||
|
where T : ControlPoint
|
||||||
{
|
{
|
||||||
if (list == null)
|
if (list == null)
|
||||||
throw new ArgumentNullException(nameof(list));
|
throw new ArgumentNullException(nameof(list));
|
||||||
|
|
||||||
if (list.Count == 0)
|
if (list.Count == 0)
|
||||||
return new T();
|
return null;
|
||||||
|
|
||||||
if (time < list[0].Time)
|
if (time < list[0].Time)
|
||||||
return prePoint ?? new T();
|
return null;
|
||||||
|
|
||||||
if (time >= list[list.Count - 1].Time)
|
if (time >= list[list.Count - 1].Time)
|
||||||
return list[list.Count - 1];
|
return list[list.Count - 1];
|
||||||
@ -125,5 +216,82 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
// l will be the first control point with Time > time, but we want the one before it
|
// l will be the first control point with Time > time, but we want the one before it
|
||||||
return list[l - 1];
|
return list[l - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check whether <see cref="newPoint"/> should be added.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The time to find the timing control point at.</param>
|
||||||
|
/// <param name="newPoint">A point to be added.</param>
|
||||||
|
/// <returns>Whether the new point should be added.</returns>
|
||||||
|
private bool checkAlreadyExisting(double time, ControlPoint newPoint)
|
||||||
|
{
|
||||||
|
ControlPoint existing = null;
|
||||||
|
|
||||||
|
switch (newPoint)
|
||||||
|
{
|
||||||
|
case TimingControlPoint _:
|
||||||
|
// Timing points are a special case and need to be added regardless of fallback availability.
|
||||||
|
existing = binarySearch(TimingPoints, time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EffectControlPoint _:
|
||||||
|
existing = EffectPointAt(time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleControlPoint _:
|
||||||
|
existing = SamplePointAt(time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DifficultyControlPoint _:
|
||||||
|
existing = DifficultyPointAt(time);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return existing?.EquivalentTo(newPoint) == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void groupItemAdded(ControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case TimingControlPoint typed:
|
||||||
|
timingPoints.Add(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EffectControlPoint typed:
|
||||||
|
effectPoints.Add(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleControlPoint typed:
|
||||||
|
samplePoints.Add(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DifficultyControlPoint typed:
|
||||||
|
difficultyPoints.Add(typed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void groupItemRemoved(ControlPoint controlPoint)
|
||||||
|
{
|
||||||
|
switch (controlPoint)
|
||||||
|
{
|
||||||
|
case TimingControlPoint typed:
|
||||||
|
timingPoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EffectControlPoint typed:
|
||||||
|
effectPoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleControlPoint typed:
|
||||||
|
samplePoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DifficultyControlPoint typed:
|
||||||
|
difficultyPoints.Remove(typed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.IO.File;
|
using osu.Framework.IO.File;
|
||||||
@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
base.ParseStreamInto(stream, beatmap);
|
base.ParseStreamInto(stream, beatmap);
|
||||||
|
|
||||||
|
flushPendingPoints();
|
||||||
|
|
||||||
// Objects may be out of order *only* if a user has manually edited an .osu file.
|
// Objects may be out of order *only* if a user has manually edited an .osu file.
|
||||||
// Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
|
// Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
|
||||||
// OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted)
|
// OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted)
|
||||||
@ -369,104 +372,64 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (timingChange)
|
if (timingChange)
|
||||||
{
|
{
|
||||||
var controlPoint = CreateTimingControlPoint();
|
var controlPoint = CreateTimingControlPoint();
|
||||||
controlPoint.Time = time;
|
|
||||||
controlPoint.BeatLength = beatLength;
|
controlPoint.BeatLength = beatLength;
|
||||||
controlPoint.TimeSignature = timeSignature;
|
controlPoint.TimeSignature = timeSignature;
|
||||||
|
|
||||||
handleTimingControlPoint(controlPoint);
|
addControlPoint(time, controlPoint, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDifficultyControlPoint(new LegacyDifficultyControlPoint
|
addControlPoint(time, new LegacyDifficultyControlPoint
|
||||||
{
|
{
|
||||||
Time = time,
|
|
||||||
SpeedMultiplier = speedMultiplier,
|
SpeedMultiplier = speedMultiplier,
|
||||||
AutoGenerated = timingChange
|
}, timingChange);
|
||||||
});
|
|
||||||
|
|
||||||
handleEffectControlPoint(new EffectControlPoint
|
addControlPoint(time, new EffectControlPoint
|
||||||
{
|
{
|
||||||
Time = time,
|
|
||||||
KiaiMode = kiaiMode,
|
KiaiMode = kiaiMode,
|
||||||
OmitFirstBarLine = omitFirstBarSignature,
|
OmitFirstBarLine = omitFirstBarSignature,
|
||||||
AutoGenerated = timingChange
|
}, timingChange);
|
||||||
});
|
|
||||||
|
|
||||||
handleSampleControlPoint(new LegacySampleControlPoint
|
addControlPoint(time, new LegacySampleControlPoint
|
||||||
{
|
{
|
||||||
Time = time,
|
|
||||||
SampleBank = stringSampleSet,
|
SampleBank = stringSampleSet,
|
||||||
SampleVolume = sampleVolume,
|
SampleVolume = sampleVolume,
|
||||||
CustomSampleBank = customSampleBank,
|
CustomSampleBank = customSampleBank,
|
||||||
AutoGenerated = timingChange
|
}, timingChange);
|
||||||
});
|
|
||||||
|
// To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but
|
||||||
|
// appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line
|
||||||
|
// with the same time value (allowing them to overwrite as necessary).
|
||||||
|
//
|
||||||
|
// The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal.
|
||||||
|
if (timingChange)
|
||||||
|
flushPendingPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTimingControlPoint(TimingControlPoint newPoint)
|
private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
|
||||||
{
|
private double pendingControlPointsTime;
|
||||||
var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time);
|
|
||||||
|
|
||||||
if (existing.Time == newPoint.Time)
|
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
||||||
{
|
{
|
||||||
// autogenerated points should not replace non-autogenerated.
|
if (time != pendingControlPointsTime)
|
||||||
// this allows for incorrectly ordered timing points to still be correctly handled.
|
flushPendingPoints();
|
||||||
if (newPoint.AutoGenerated && !existing.AutoGenerated)
|
|
||||||
|
if (timingChange)
|
||||||
|
{
|
||||||
|
beatmap.ControlPointInfo.Add(time, point);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
beatmap.ControlPointInfo.TimingPoints.Remove(existing);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmap.ControlPointInfo.TimingPoints.Add(newPoint);
|
pendingControlPoints.Add(point);
|
||||||
|
pendingControlPointsTime = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDifficultyControlPoint(DifficultyControlPoint newPoint)
|
private void flushPendingPoints()
|
||||||
{
|
{
|
||||||
var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time);
|
foreach (var p in pendingControlPoints)
|
||||||
|
beatmap.ControlPointInfo.Add(pendingControlPointsTime, p);
|
||||||
|
|
||||||
if (existing.Time == newPoint.Time)
|
pendingControlPoints.Clear();
|
||||||
{
|
|
||||||
// autogenerated points should not replace non-autogenerated.
|
|
||||||
// this allows for incorrectly ordered timing points to still be correctly handled.
|
|
||||||
if (newPoint.AutoGenerated && !existing.AutoGenerated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.DifficultyPoints.Remove(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleEffectControlPoint(EffectControlPoint newPoint)
|
|
||||||
{
|
|
||||||
var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time);
|
|
||||||
|
|
||||||
if (existing.Time == newPoint.Time)
|
|
||||||
{
|
|
||||||
// autogenerated points should not replace non-autogenerated.
|
|
||||||
// this allows for incorrectly ordered timing points to still be correctly handled.
|
|
||||||
if (newPoint.AutoGenerated && !existing.AutoGenerated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.EffectPoints.Remove(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.EffectPoints.Add(newPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleSampleControlPoint(SampleControlPoint newPoint)
|
|
||||||
{
|
|
||||||
var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time);
|
|
||||||
|
|
||||||
if (existing.Time == newPoint.Time)
|
|
||||||
{
|
|
||||||
// autogenerated points should not replace non-autogenerated.
|
|
||||||
// this allows for incorrectly ordered timing points to still be correctly handled.
|
|
||||||
if (newPoint.AutoGenerated && !existing.AutoGenerated)
|
|
||||||
return;
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.SamplePoints.Remove(existing);
|
|
||||||
}
|
|
||||||
|
|
||||||
beatmap.ControlPointInfo.SamplePoints.Add(newPoint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleHitObject(string line)
|
private void handleHitObject(string line)
|
||||||
|
@ -104,14 +104,10 @@ namespace osu.Game.Graphics.Containers
|
|||||||
defaultTiming = new TimingControlPoint
|
defaultTiming = new TimingControlPoint
|
||||||
{
|
{
|
||||||
BeatLength = default_beat_length,
|
BeatLength = default_beat_length,
|
||||||
AutoGenerated = true,
|
|
||||||
Time = 0
|
|
||||||
};
|
};
|
||||||
|
|
||||||
defaultEffect = new EffectControlPoint
|
defaultEffect = new EffectControlPoint
|
||||||
{
|
{
|
||||||
Time = 0,
|
|
||||||
AutoGenerated = true,
|
|
||||||
KiaiMode = false,
|
KiaiMode = false,
|
||||||
OmitFirstBarLine = false
|
OmitFirstBarLine = false
|
||||||
};
|
};
|
||||||
|
@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
// Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to
|
// Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to
|
||||||
// the next timing point's start time
|
// the next timing point's start time
|
||||||
var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time);
|
var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
|
||||||
if (position > nextTimingPoint?.Time)
|
if (position > nextTimingPoint?.Time)
|
||||||
position = nextTimingPoint.Time;
|
position = nextTimingPoint.Time;
|
||||||
|
|
||||||
@ -87,7 +87,17 @@ namespace osu.Game.Screens.Edit
|
|||||||
if (direction < 0 && timingPoint.Time == CurrentTime)
|
if (direction < 0 && timingPoint.Time == CurrentTime)
|
||||||
{
|
{
|
||||||
// When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into
|
// When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into
|
||||||
int activeIndex = ControlPointInfo.TimingPoints.IndexOf(timingPoint);
|
int activeIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < ControlPointInfo.TimingPoints.Count; i++)
|
||||||
|
{
|
||||||
|
if (ControlPointInfo.TimingPoints[i] == timingPoint)
|
||||||
|
{
|
||||||
|
activeIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (activeIndex > 0 && CurrentTime == timingPoint.Time)
|
while (activeIndex > 0 && CurrentTime == timingPoint.Time)
|
||||||
timingPoint = ControlPointInfo.TimingPoints[--activeIndex];
|
timingPoint = ControlPointInfo.TimingPoints[--activeIndex];
|
||||||
}
|
}
|
||||||
@ -124,7 +134,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
||||||
seekTime = timingPoint.Time;
|
seekTime = timingPoint.Time;
|
||||||
|
|
||||||
var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time);
|
var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
|
||||||
if (seekTime > nextTimingPoint?.Time)
|
if (seekTime > nextTimingPoint?.Time)
|
||||||
seekTime = nextTimingPoint.Time;
|
seekTime = nextTimingPoint.Time;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user