mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 08:52:55 +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()
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
||||
controlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
|
@ -64,9 +64,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
||||
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||
});
|
||||
|
||||
[TestCase(1)]
|
||||
|
@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||
{
|
||||
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 });
|
||||
|
||||
|
@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}, 25),
|
||||
}
|
||||
},
|
||||
ControlPointInfo =
|
||||
{
|
||||
DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
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 } });
|
||||
|
||||
p.OnLoadComplete += _ =>
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
AddStep("Reset height", () => changePlayfieldSize(6));
|
||||
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
||||
controlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
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;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
|
||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||
|
||||
Hit hit = new Hit();
|
||||
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;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai });
|
||||
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
|
||||
|
||||
Hit hit = new Hit();
|
||||
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
|
||||
|
@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
|
||||
{
|
||||
this.controlPoints = controlPoints;
|
||||
|
||||
IEnumerable<SampleControlPoint> samplePoints;
|
||||
if (controlPoints.SamplePoints.Count == 0)
|
||||
// Get the default sample point
|
||||
samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) };
|
||||
else
|
||||
samplePoints = controlPoints.SamplePoints;
|
||||
IEnumerable<SampleControlPoint> samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints;
|
||||
|
||||
foreach (var s in samplePoints)
|
||||
{
|
||||
|
@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var controlPoints = beatmap.ControlPointInfo;
|
||||
|
||||
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||
Assert.AreEqual(42, controlPoints.DifficultyPoints.Count);
|
||||
Assert.AreEqual(42, controlPoints.SamplePoints.Count);
|
||||
Assert.AreEqual(42, controlPoints.EffectPoints.Count);
|
||||
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
|
||||
Assert.AreEqual(8, 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(48428, difficultyPoint.Time);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
|
||||
|
||||
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
||||
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(119637);
|
||||
Assert.AreEqual(119637, effectPoint.Time);
|
||||
Assert.AreEqual(95901, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
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]
|
||||
public void TestDecodeBeatmapColours()
|
||||
{
|
||||
|
@ -25,11 +25,10 @@ namespace osu.Game.Tests.Editor
|
||||
BeatDivisor.Value = 1;
|
||||
|
||||
composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
|
||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = 1 });
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 1000 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
});
|
||||
|
||||
[TestCase(1)]
|
||||
@ -47,8 +46,8 @@ namespace osu.Game.Tests.Editor
|
||||
{
|
||||
AddStep($"set multiplier = {multiplier}", () =>
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier });
|
||||
});
|
||||
|
||||
assertSnapDistance(100 * multiplier);
|
||||
@ -76,8 +75,8 @@ namespace osu.Game.Tests.Editor
|
||||
|
||||
AddStep("set beat length = 500", () =>
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
});
|
||||
|
||||
assertDurationToDistance(500, 200);
|
||||
@ -97,8 +96,8 @@ namespace osu.Game.Tests.Editor
|
||||
|
||||
AddStep("set beat length = 500", () =>
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
});
|
||||
|
||||
assertDistanceToDuration(200, 500);
|
||||
@ -124,8 +123,8 @@ namespace osu.Game.Tests.Editor
|
||||
|
||||
AddStep("set beat length = 500", () =>
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
});
|
||||
|
||||
assertSnappedDuration(50, 0);
|
||||
@ -155,8 +154,8 @@ namespace osu.Game.Tests.Editor
|
||||
|
||||
AddStep("set beat length = 500", () =>
|
||||
{
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = 500 });
|
||||
composer.EditorBeatmap.ControlPointInfo.Clear();
|
||||
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
});
|
||||
|
||||
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()
|
||||
{
|
||||
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[]
|
||||
{
|
||||
|
@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor
|
||||
{
|
||||
var testBeatmap = new Beatmap
|
||||
{
|
||||
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 }
|
||||
}
|
||||
},
|
||||
ControlPointInfo = new ControlPointInfo(),
|
||||
HitObjects =
|
||||
{
|
||||
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);
|
||||
|
||||
Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock };
|
||||
|
@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
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);
|
||||
|
||||
@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range / 2 },
|
||||
new TimingControlPoint { Time = 12000, BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 100000, BeatLength = time_range });
|
||||
var beatmap = createBeatmap();
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||
beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range });
|
||||
beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range });
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
|
||||
@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
||||
var beatmap = createBeatmap();
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
|
||||
@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestNonRelativeScale()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
||||
var beatmap = createBeatmap();
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
|
||||
beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 });
|
||||
|
||||
createTest(beatmap);
|
||||
|
||||
@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
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;
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
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;
|
||||
|
||||
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.
|
||||
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
|
||||
/// </summary>
|
||||
/// <param name="timingControlPoints">The timing points to add to the beatmap.</param>
|
||||
/// <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 } };
|
||||
|
||||
beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
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)
|
||||
{
|
||||
|
@ -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.
|
||||
|
||||
using System;
|
||||
@ -10,12 +10,11 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
/// </summary>
|
||||
public double Time;
|
||||
public double Time => controlPointGroup?.Time ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap.
|
||||
/// </summary>
|
||||
internal bool AutoGenerated;
|
||||
private ControlPointGroup controlPointGroup;
|
||||
|
||||
public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup;
|
||||
|
||||
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.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Lists;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
@ -12,57 +13,78 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
[Serializable]
|
||||
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>
|
||||
/// All timing points.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// All difficulty points.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// All sound points.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// All effect points.
|
||||
/// </summary>
|
||||
[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>
|
||||
/// Finds the difficulty control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the difficulty control point at.</param>
|
||||
/// <returns>The difficulty control point.</returns>
|
||||
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearch(DifficultyPoints, time);
|
||||
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the effect control point at.</param>
|
||||
/// <returns>The effect control point.</returns>
|
||||
public EffectControlPoint EffectPointAt(double time) => binarySearch(EffectPoints, time);
|
||||
public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time);
|
||||
|
||||
/// <summary>
|
||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the sound control point at.</param>
|
||||
/// <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>
|
||||
/// Finds the timing control point that is active at <paramref name="time"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to find the timing control point at.</param>
|
||||
/// <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>
|
||||
/// Finds the maximum BPM represented by any timing control point.
|
||||
@ -85,24 +107,93 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public double BPMMode =>
|
||||
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>
|
||||
/// 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>
|
||||
/// <param name="list">The list to search.</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>
|
||||
/// <returns>The active control point at <paramref name="time"/>.</returns>
|
||||
private T binarySearch<T>(SortedList<T> list, double time, T prePoint = null)
|
||||
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
||||
private T binarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T prePoint = null)
|
||||
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)
|
||||
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];
|
||||
@ -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
|
||||
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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.IO.File;
|
||||
@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
base.ParseStreamInto(stream, beatmap);
|
||||
|
||||
flushPendingPoints();
|
||||
|
||||
// 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).
|
||||
// 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)
|
||||
{
|
||||
var controlPoint = CreateTimingControlPoint();
|
||||
controlPoint.Time = time;
|
||||
|
||||
controlPoint.BeatLength = beatLength;
|
||||
controlPoint.TimeSignature = timeSignature;
|
||||
|
||||
handleTimingControlPoint(controlPoint);
|
||||
addControlPoint(time, controlPoint, true);
|
||||
}
|
||||
|
||||
handleDifficultyControlPoint(new LegacyDifficultyControlPoint
|
||||
addControlPoint(time, new LegacyDifficultyControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SpeedMultiplier = speedMultiplier,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
}, timingChange);
|
||||
|
||||
handleEffectControlPoint(new EffectControlPoint
|
||||
addControlPoint(time, new EffectControlPoint
|
||||
{
|
||||
Time = time,
|
||||
KiaiMode = kiaiMode,
|
||||
OmitFirstBarLine = omitFirstBarSignature,
|
||||
AutoGenerated = timingChange
|
||||
});
|
||||
}, timingChange);
|
||||
|
||||
handleSampleControlPoint(new LegacySampleControlPoint
|
||||
addControlPoint(time, new LegacySampleControlPoint
|
||||
{
|
||||
Time = time,
|
||||
SampleBank = stringSampleSet,
|
||||
SampleVolume = sampleVolume,
|
||||
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)
|
||||
{
|
||||
var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time);
|
||||
private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
|
||||
private double pendingControlPointsTime;
|
||||
|
||||
if (existing.Time == newPoint.Time)
|
||||
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
||||
{
|
||||
// autogenerated points should not replace non-autogenerated.
|
||||
// this allows for incorrectly ordered timing points to still be correctly handled.
|
||||
if (newPoint.AutoGenerated && !existing.AutoGenerated)
|
||||
if (time != pendingControlPointsTime)
|
||||
flushPendingPoints();
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
beatmap.ControlPointInfo.Add(time, point);
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
pendingControlPoints.Clear();
|
||||
}
|
||||
|
||||
private void handleHitObject(string line)
|
||||
|
@ -104,14 +104,10 @@ namespace osu.Game.Graphics.Containers
|
||||
defaultTiming = new TimingControlPoint
|
||||
{
|
||||
BeatLength = default_beat_length,
|
||||
AutoGenerated = true,
|
||||
Time = 0
|
||||
};
|
||||
|
||||
defaultEffect = new EffectControlPoint
|
||||
{
|
||||
Time = 0,
|
||||
AutoGenerated = true,
|
||||
KiaiMode = 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
|
||||
// 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)
|
||||
position = nextTimingPoint.Time;
|
||||
|
||||
@ -87,7 +87,17 @@ namespace osu.Game.Screens.Edit
|
||||
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
|
||||
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)
|
||||
timingPoint = ControlPointInfo.TimingPoints[--activeIndex];
|
||||
}
|
||||
@ -124,7 +134,7 @@ namespace osu.Game.Screens.Edit
|
||||
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
||||
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)
|
||||
seekTime = nextTimingPoint.Time;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user