1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 20:05:29 +08:00

Merge branch 'master' into fix-editor-difficulty-name-update

This commit is contained in:
Dean Herbert 2021-10-15 11:14:45 +09:00 committed by GitHub
commit 874d722820
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 603 additions and 363 deletions

View File

@ -30,3 +30,5 @@ jobs:
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
path: "*.trx" path: "*.trx"
reporter: dotnet-trx reporter: dotnet-trx
list-suites: 'failed'
list-tests: 'failed'

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1013.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1014.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -42,9 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;

View File

@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -101,27 +102,27 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float GetBeatSnapDistanceAt(double referenceTime) public override float GetBeatSnapDistanceAt(HitObject referenceObject)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float DurationToDistance(double referenceTime, double duration) public override float DurationToDistance(HitObject referenceObject, double duration)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override double DistanceToDuration(double referenceTime, float distance) public override double DistanceToDuration(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override double GetSnappedDurationFromDistance(double referenceTime, float distance) public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View File

@ -388,7 +388,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}, },
}; };
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
} }
AddStep("load player", () => AddStep("load player", () =>

View File

@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Debug.Assert(distanceData != null); Debug.Assert(distanceData != null);
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
double beatLength; double beatLength;
#pragma warning disable 618 #pragma warning disable 618
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
#pragma warning restore 618 #pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
else else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
SpanCount = repeatsData?.SpanCount() ?? 1; SpanCount = repeatsData?.SpanCount() ?? 1;
StartTime = (int)Math.Round(hitObject.StartTime); StartTime = (int)Math.Round(hitObject.StartTime);

View File

@ -13,6 +13,7 @@ SliderTickRate:1
[TimingPoints] [TimingPoints]
0,500,4,1,0,100,1,0 0,500,4,1,0,100,1,0
10000,-150,4,1,0,100,1,0
[HitObjects] [HitObjects]
51,192,500,128,0,1500:1:0:0:0: 51,192,500,128,0,1500:1:0:0:0:

View File

@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Mania.UI
// For non-mania beatmap, speed changes should only happen through timing points // For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset) if (!isForCurrentRuleset)
p.DifficultyPoint = new DifficultyControlPoint(); p.EffectPoint = new EffectControlPoint();
} }
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);

View File

@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -179,15 +180,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; public float GetBeatSnapDistanceAt(HitObject referenceObject) => (float)beat_length;
public float DurationToDistance(double referenceTime, double duration) => (float)duration; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
config.SetValue(OsuSetting.AutoCursorSize, true); config.SetValue(OsuSetting.AutoCursorSize, true);
gameplayState.Beatmap.Difficulty.CircleSize = val; gameplayState.Beatmap.Difficulty.CircleSize = val;
Scheduler.AddOnce(() => loadContent(false)); Scheduler.AddOnce(loadContent);
}); });
AddStep("test cursor container", () => loadContent(false)); AddStep("test cursor container", () => loadContent(false));
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize); AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize);
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", () => loadContent()); AddStep("load content", loadContent);
AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale); AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
@ -98,7 +98,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin()))); AddStep("load content", () => loadContent(false, () => new SkinProvidingContainer(new TopLeftCursorSkin())));
} }
private void loadContent(bool automated = true, Func<SkinProvidingContainer> skinProvider = null) private void loadContent() => loadContent(false);
private void loadContent(bool automated, Func<SkinProvidingContainer> skinProvider = null)
{ {
SetContents(_ => SetContents(_ =>
{ {

View File

@ -407,8 +407,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
SelectedMods.Value = new[] { new OsuModClassic() }; SelectedMods.Value = new[] { new OsuModClassic() };
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
@ -439,6 +437,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public TestSlider() public TestSlider()
{ {
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
DefaultsApplied += _ => DefaultsApplied += _ =>
{ {
HeadCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows = new TestHitWindows();

View File

@ -13,6 +13,7 @@ using osuTK.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -328,10 +329,14 @@ 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 LegacyControlPointInfo();
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.Add(0, new DifficultyControlPoint { SliderVelocity = speedMultiplier });
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); slider.ApplyDefaults(cpi, new BeatmapDifficulty
{
CircleSize = circleSize,
SliderTickRate = 3
});
var drawable = CreateDrawableSlider(slider); var drawable = CreateDrawableSlider(slider);

View File

@ -348,6 +348,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider_start, StartTime = time_slider_start,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PerfectCurve, new[]
{ {
Vector2.Zero, Vector2.Zero,
@ -362,8 +363,6 @@ 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 += _ =>

View File

@ -369,8 +369,6 @@ 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 += _ =>
@ -399,6 +397,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public TestSlider() public TestSlider()
{ {
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
DefaultsApplied += _ => DefaultsApplied += _ =>
{ {
HeadCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows = new TestHitWindows();

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Osu.Beatmaps namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset, LegacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset,
// prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance. // prior to v8, speed multipliers don't adjust for how many ticks are generated over the same distance.
// this results in more (or less) ticks being generated in <v8 maps for the same time duration. // this results in more (or less) ticks being generated in <v8 maps for the same time duration.
TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(original.StartTime).SliderVelocity : 1
}.Yield(); }.Yield();
case IHasDuration endTimeData: case IHasDuration endTimeData:

View File

@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider() private void updateSlider()
{ {
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
bodyPiece.UpdateFrom(HitObject); bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle); headCirclePiece.UpdateFrom(HitObject.HeadCircle);

View File

@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updatePath() private void updatePath()
{ {
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance; HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
editorBeatmap?.Update(HitObject); editorBeatmap?.Update(HitObject);
} }

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{ {
public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
: base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime) : base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
{ {
Masking = true; Masking = true;
} }

View File

@ -140,9 +140,8 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
@ -175,7 +174,6 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = e.Time, StartTime = e.Time,
Position = Position, Position = Position,
StackHeight = StackHeight, StackHeight = StackHeight,
SampleControlPoint = SampleControlPoint,
}); });
break; break;

View File

@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = obj.DifficultyControlPoint;
double beatLength; double beatLength;
#pragma warning disable 618 #pragma warning disable 618
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
#pragma warning restore 618 #pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
else else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;

View File

@ -63,9 +63,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
tickSpacing = timingPoint.BeatLength / TickRate; tickSpacing = timingPoint.BeatLength / TickRate;

View File

@ -192,15 +192,15 @@ namespace osu.Game.Tests.Beatmaps.Formats
var difficultyPoint = controlPoints.DifficultyPointAt(0); var difficultyPoint = controlPoints.DifficultyPointAt(0);
Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
difficultyPoint = controlPoints.DifficultyPointAt(48428); difficultyPoint = controlPoints.DifficultyPointAt(48428);
Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
difficultyPoint = controlPoints.DifficultyPointAt(116999); difficultyPoint = controlPoints.DifficultyPointAt(116999);
Assert.AreEqual(116999, difficultyPoint.Time); Assert.AreEqual(116999, difficultyPoint.Time);
Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1); Assert.AreEqual(0.75, difficultyPoint.SliderVelocity, 0.1);
var soundPoint = controlPoints.SamplePointAt(0); var soundPoint = controlPoints.SamplePointAt(0);
Assert.AreEqual(956, soundPoint.Time); Assert.AreEqual(956, soundPoint.Time);
@ -227,7 +227,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(effectPoint.KiaiMode); Assert.IsTrue(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(119637); effectPoint = controlPoints.EffectPointAt(116637);
Assert.AreEqual(95901, effectPoint.Time); Assert.AreEqual(95901, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
@ -249,10 +249,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3)); Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(3));
Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3)); Assert.That(controlPoints.SamplePoints.Count, Is.EqualTo(3));
Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(1500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(2500).SliderVelocity, Is.EqualTo(0.75).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(3500).SliderVelocity, Is.EqualTo(1.5).Within(0.1));
Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True); Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True);
Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True); Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True);
@ -279,10 +279,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu")) using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var controlPoints = decoder.Decode(stream).ControlPointInfo; var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(0).SliderVelocity, Is.EqualTo(0.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1)); Assert.That(controlPoints.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1).Within(0.1));
} }
} }
@ -394,12 +394,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu")) using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var controlPointInfo = decoder.Decode(stream).ControlPointInfo; var controlPointInfo = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1)); Assert.That(controlPointInfo.DifficultyPointAt(5).SliderVelocity, Is.EqualTo(1));
Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10)); Assert.That(controlPointInfo.DifficultyPointAt(1000).SliderVelocity, Is.EqualTo(10));
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d)); Assert.That(controlPointInfo.DifficultyPointAt(2000).SliderVelocity, Is.EqualTo(1.8518518518518519d));
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5)); Assert.That(controlPointInfo.DifficultyPointAt(3000).SliderVelocity, Is.EqualTo(0.5));
} }
} }

View File

@ -46,8 +46,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
sort(decoded.beatmap); sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); compareBeatmaps(decoded, decodedAfterEncode);
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
} }
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
@ -62,8 +61,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
sort(decoded.beatmap); sort(decoded.beatmap);
sort(decodedAfterEncode.beatmap); sort(decodedAfterEncode.beatmap);
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize())); compareBeatmaps(decoded, decodedAfterEncode);
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
} }
[TestCaseSource(nameof(allBeatmaps))] [TestCaseSource(nameof(allBeatmaps))]
@ -77,12 +75,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name); var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
// in this process, we may lose some detail in the control points section. compareBeatmaps(decoded, decodedAfterEncode);
// let's focus on only the hitobjects.
var originalHitObjects = decoded.beatmap.HitObjects.Serialize();
var newHitObjects = decodedAfterEncode.beatmap.HitObjects.Serialize();
Assert.That(newHitObjects, Is.EqualTo(originalHitObjects));
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
{ {
@ -97,7 +90,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
// completely ignore "legacy" types, which have been moved to HitObjects. // completely ignore "legacy" types, which have been moved to HitObjects.
// even though these would mostly be ignored by the Add call, they will still be available in groups, // even though these would mostly be ignored by the Add call, they will still be available in groups,
// which isn't what we want to be testing here. // which isn't what we want to be testing here.
if (point is SampleControlPoint) if (point is SampleControlPoint || point is DifficultyControlPoint)
continue; continue;
newControlPoints.Add(point.Time, point.DeepClone()); newControlPoints.Add(point.Time, point.DeepClone());
@ -107,6 +100,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private void compareBeatmaps((IBeatmap beatmap, TestLegacySkin skin) expected, (IBeatmap beatmap, TestLegacySkin skin) actual)
{
// Check all control points that are still considered to be at a global level.
Assert.That(expected.beatmap.ControlPointInfo.TimingPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.TimingPoints.Serialize()));
Assert.That(expected.beatmap.ControlPointInfo.EffectPoints.Serialize(), Is.EqualTo(actual.beatmap.ControlPointInfo.EffectPoints.Serialize()));
// Check all hitobjects.
Assert.That(expected.beatmap.HitObjects.Serialize(), Is.EqualTo(actual.beatmap.HitObjects.Serialize()));
// Check skin.
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
}
[Test] [Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError() public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{ {
@ -156,7 +162,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name) private (IBeatmap beatmap, TestLegacySkin skin) decodeFromLegacy(Stream stream, string name)
{ {
using (var reader = new LineBufferedReader(stream)) using (var reader = new LineBufferedReader(stream))
{ {
@ -174,7 +180,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap) private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin skin) fullBeatmap)
{ {
var (beatmap, beatmapSkin) = fullBeatmap; var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream(); var stream = new MemoryStream();

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
@ -55,8 +56,6 @@ namespace osu.Game.Tests.Editing
composer.EditorBeatmap.Difficulty.SliderMultiplier = 1; composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
composer.EditorBeatmap.ControlPointInfo.Clear(); composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }); composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 });
}); });
@ -73,13 +72,13 @@ namespace osu.Game.Tests.Editing
[TestCase(2)] [TestCase(2)]
public void TestSpeedMultiplier(float multiplier) public void TestSpeedMultiplier(float multiplier)
{ {
AddStep($"set multiplier = {multiplier}", () => assertSnapDistance(100 * multiplier, new HitObject
{ {
composer.EditorBeatmap.ControlPointInfo.Clear(); DifficultyControlPoint = new DifficultyControlPoint
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); {
SliderVelocity = multiplier
}
}); });
assertSnapDistance(100 * multiplier);
} }
[TestCase(1)] [TestCase(1)]
@ -197,20 +196,20 @@ namespace osu.Game.Tests.Editing
assertSnappedDistance(400, 400); assertSnappedDistance(400, 400);
} }
private void assertSnapDistance(float expectedDistance) private void assertSnapDistance(float expectedDistance, HitObject hitObject = null)
=> AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(0) == expectedDistance); => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()) == expectedDistance);
private void assertDurationToDistance(double duration, float expectedDistance) private void assertDurationToDistance(double duration, float expectedDistance)
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(0, duration) == expectedDistance); => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance);
private void assertDistanceToDuration(float distance, double expectedDuration) private void assertDistanceToDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(0, distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration);
private void assertSnappedDuration(float distance, double expectedDuration) private void assertSnappedDuration(float distance, double expectedDuration)
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(0, distance) == expectedDuration); => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.GetSnappedDurationFromDistance(new HitObject(), distance) == expectedDuration);
private void assertSnappedDistance(float distance, float expectedDistance) private void assertSnappedDistance(float distance, float expectedDistance)
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(0, distance) == expectedDistance); => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.GetSnappedDistanceFromDistance(new HitObject(), distance) == expectedDistance);
private class TestHitObjectComposer : OsuHitObjectComposer private class TestHitObjectComposer : OsuHitObjectComposer
{ {

View File

@ -46,7 +46,7 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddRedundantDifficulty() public void TestAddRedundantDifficulty()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new DifficultyControlPoint()); // is redundant cpi.Add(0, new DifficultyControlPoint()); // is redundant
cpi.Add(1000, new DifficultyControlPoint()); // is redundant cpi.Add(1000, new DifficultyControlPoint()); // is redundant
@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant cpi.Add(1000, new DifficultyControlPoint { SliderVelocity = 2 }); // is not redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
@ -159,7 +159,7 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddControlPointToGroup() public void TestAddControlPointToGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
@ -174,23 +174,23 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestAddDuplicateControlPointToGroup() public void TestAddDuplicateControlPointToGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
group.Add(new DifficultyControlPoint()); group.Add(new DifficultyControlPoint());
group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); group.Add(new DifficultyControlPoint { SliderVelocity = 2 });
Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); Assert.That(group.ControlPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1));
Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); Assert.That(cpi.DifficultyPoints.First().SliderVelocity, Is.EqualTo(2));
} }
[Test] [Test]
public void TestRemoveControlPointFromGroup() public void TestRemoveControlPointFromGroup()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
var group = cpi.GroupAt(1000, true); var group = cpi.GroupAt(1000, true);
Assert.That(cpi.Groups.Count, Is.EqualTo(1)); Assert.That(cpi.Groups.Count, Is.EqualTo(1));
@ -208,14 +208,14 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestOrdering() public void TestOrdering()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new TimingControlPoint()); cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });
@ -230,14 +230,14 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestClear() public void TestClear()
{ {
var cpi = new ControlPointInfo(); var cpi = new LegacyControlPointInfo();
cpi.Add(0, new TimingControlPoint()); cpi.Add(0, new TimingControlPoint());
cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); cpi.Add(1000, new TimingControlPoint { BeatLength = 500 });
cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); cpi.Add(10000, new TimingControlPoint { BeatLength = 200 });
cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); cpi.Add(5000, new TimingControlPoint { BeatLength = 100 });
cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); cpi.Add(3000, new DifficultyControlPoint { SliderVelocity = 2 });
cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SliderVelocity = 4 });
cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 });
cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true });

View File

@ -163,5 +163,11 @@ namespace osu.Game.Tests.Visual.Audio
} }
private void waitTrackPlay() => AddWaitStep("Let track play", 10); private void waitTrackPlay() => AddWaitStep("Let track play", 10);
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
track?.Dispose();
}
} }
} }

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Editing
public new float DistanceSpacing => base.DistanceSpacing; public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(double? endTime = null) public TestDistanceSnapGrid(double? endTime = null)
: base(grid_position, 0, endTime) : base(new HitObject(), grid_position, 0, endTime)
{ {
} }
@ -158,15 +159,15 @@ namespace osu.Game.Tests.Visual.Editing
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => 10; public float GetBeatSnapDistanceAt(HitObject referenceObject) => 10;
public float DurationToDistance(double referenceTime, double duration) => (float)duration; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration;
public double DistanceToDuration(double referenceTime, float distance) => distance; public double DistanceToDuration(HitObject referenceObject, float distance) => distance;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0; public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => 0;
} }
} }
} }

View File

@ -93,9 +93,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint> private IList<MultiplierControlPoint> testControlPoints => new List<MultiplierControlPoint>
{ {
new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } }, new MultiplierControlPoint(time_range) { EffectPoint = { ScrollSpeed = 1.25 } },
new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } }, new MultiplierControlPoint(1.5 * time_range) { EffectPoint = { ScrollSpeed = 1 } },
new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } } new MultiplierControlPoint(2 * time_range) { EffectPoint = { ScrollSpeed = 1.5 } }
}; };
[Test] [Test]

View File

@ -564,11 +564,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
} }
}); });
AddRepeatStep("click spectate button", () => AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click ready button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerReadyButton>().Single()); InputManager.MoveMouseTo(readyButton);
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}, 2); });
AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("click start button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("wait for player", () => Stack.CurrentScreen is Player); AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
@ -582,6 +589,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen); AddUntilStep("wait for results", () => Stack.CurrentScreen is ResultsScreen);
} }
private MultiplayerReadyButton readyButton => this.ChildrenOfType<MultiplayerReadyButton>().Single();
private void createRoom(Func<Room> room) private void createRoom(Func<Room> room)
{ {
AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true); AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType<LoungeSubScreen>().SingleOrDefault()?.IsLoaded == true);

View File

@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
() => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<FillFlowContainer>().OfType<IHasText>().First().Text == collectionName))); () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName)));
private IconButton getAddOrRemoveButton(int index) private IconButton getAddOrRemoveButton(int index)
=> getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single(); => getCollectionDropdownItems().ElementAt(index).ChildrenOfType<IconButton>().Single();

View File

@ -15,11 +15,9 @@ namespace osu.Game.Beatmaps.ControlPoints
/// The time at which the control point takes effect. /// The time at which the control point takes effect.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public double Time => controlPointGroup?.Time ?? 0; public double Time { get; set; }
private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.Time;
public void AttachGroup(ControlPointGroup pointGroup) => controlPointGroup = pointGroup;
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
@ -46,6 +44,7 @@ namespace osu.Game.Beatmaps.ControlPoints
public virtual void CopyFrom(ControlPoint other) public virtual void CopyFrom(ControlPoint other)
{ {
Time = other.Time;
} }
} }
} }

View File

@ -33,14 +33,6 @@ namespace osu.Game.Beatmaps.ControlPoints
private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default); private readonly SortedList<TimingControlPoint> timingPoints = new SortedList<TimingControlPoint>(Comparer<TimingControlPoint>.Default);
/// <summary>
/// All difficulty points.
/// </summary>
[JsonProperty]
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <summary> /// <summary>
/// All effect points. /// All effect points.
/// </summary> /// </summary>
@ -55,14 +47,6 @@ namespace osu.Game.Beatmaps.ControlPoints
[JsonIgnore] [JsonIgnore]
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray(); 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>
[NotNull]
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
/// <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>
@ -100,7 +84,6 @@ namespace osu.Game.Beatmaps.ControlPoints
{ {
groups.Clear(); groups.Clear();
timingPoints.Clear(); timingPoints.Clear();
difficultyPoints.Clear();
effectPoints.Clear(); effectPoints.Clear();
} }
@ -277,10 +260,6 @@ namespace osu.Game.Beatmaps.ControlPoints
case EffectControlPoint _: case EffectControlPoint _:
existing = EffectPointAt(time); existing = EffectPointAt(time);
break; break;
case DifficultyControlPoint _:
existing = DifficultyPointAt(time);
break;
} }
return newPoint?.IsRedundant(existing) == true; return newPoint?.IsRedundant(existing) == true;
@ -298,9 +277,8 @@ namespace osu.Game.Beatmaps.ControlPoints
effectPoints.Add(typed); effectPoints.Add(typed);
break; break;
case DifficultyControlPoint typed: default:
difficultyPoints.Add(typed); throw new ArgumentException($"A control point of unexpected type {controlPoint.GetType()} was added to this {nameof(ControlPointInfo)}");
break;
} }
} }
@ -315,10 +293,6 @@ namespace osu.Game.Beatmaps.ControlPoints
case EffectControlPoint typed: case EffectControlPoint typed:
effectPoints.Remove(typed); effectPoints.Remove(typed);
break; break;
case DifficultyControlPoint typed:
difficultyPoints.Remove(typed);
break;
} }
} }

View File

@ -7,17 +7,20 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
/// <remarks>
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
/// </remarks>
public class DifficultyControlPoint : ControlPoint public class DifficultyControlPoint : ControlPoint
{ {
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
{ {
SpeedMultiplierBindable = { Disabled = true }, SliderVelocityBindable = { Disabled = true },
}; };
/// <summary> /// <summary>
/// The speed multiplier at this control point. /// The slider velocity at this control point.
/// </summary> /// </summary>
public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) public readonly BindableDouble SliderVelocityBindable = new BindableDouble(1)
{ {
Precision = 0.01, Precision = 0.01,
Default = 1, Default = 1,
@ -28,21 +31,21 @@ namespace osu.Game.Beatmaps.ControlPoints
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1; public override Color4 GetRepresentingColour(OsuColour colours) => colours.Lime1;
/// <summary> /// <summary>
/// The speed multiplier at this control point. /// The slider velocity at this control point.
/// </summary> /// </summary>
public double SpeedMultiplier public double SliderVelocity
{ {
get => SpeedMultiplierBindable.Value; get => SliderVelocityBindable.Value;
set => SpeedMultiplierBindable.Value = value; set => SliderVelocityBindable.Value = value;
} }
public override bool IsRedundant(ControlPoint existing) public override bool IsRedundant(ControlPoint existing)
=> existing is DifficultyControlPoint existingDifficulty => existing is DifficultyControlPoint existingDifficulty
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier; && SliderVelocity == existingDifficulty.SliderVelocity;
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)
{ {
SpeedMultiplier = ((DifficultyControlPoint)other).SpeedMultiplier; SliderVelocity = ((DifficultyControlPoint)other).SliderVelocity;
base.CopyFrom(other); base.CopyFrom(other);
} }

View File

@ -12,7 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
{ {
KiaiModeBindable = { Disabled = true }, KiaiModeBindable = { Disabled = true },
OmitFirstBarLineBindable = { Disabled = true } OmitFirstBarLineBindable = { Disabled = true },
ScrollSpeedBindable = { Disabled = true }
}; };
/// <summary> /// <summary>
@ -20,6 +21,26 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary> /// </summary>
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
/// <summary>
/// The relative scroll speed at this control point.
/// </summary>
public readonly BindableDouble ScrollSpeedBindable = new BindableDouble(1)
{
Precision = 0.01,
Default = 1,
MinValue = 0.01,
MaxValue = 10
};
/// <summary>
/// The relative scroll speed.
/// </summary>
public double ScrollSpeed
{
get => ScrollSpeedBindable.Value;
set => ScrollSpeedBindable.Value = value;
}
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple; public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
/// <summary> /// <summary>
@ -49,12 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints
=> !OmitFirstBarLine => !OmitFirstBarLine
&& existing is EffectControlPoint existingEffect && existing is EffectControlPoint existingEffect
&& KiaiMode == existingEffect.KiaiMode && KiaiMode == existingEffect.KiaiMode
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine; && OmitFirstBarLine == existingEffect.OmitFirstBarLine
&& ScrollSpeed == existingEffect.ScrollSpeed;
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)
{ {
KiaiMode = ((EffectControlPoint)other).KiaiMode; KiaiMode = ((EffectControlPoint)other).KiaiMode;
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine; OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
base.CopyFrom(other); base.CopyFrom(other);
} }

View File

@ -8,6 +8,9 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints namespace osu.Game.Beatmaps.ControlPoints
{ {
/// <remarks>
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
/// </remarks>
public class SampleControlPoint : ControlPoint public class SampleControlPoint : ControlPoint
{ {
public const string DEFAULT_BANK = "normal"; public const string DEFAULT_BANK = "normal";

View File

@ -384,14 +384,21 @@ namespace osu.Game.Beatmaps.Formats
addControlPoint(time, new LegacyDifficultyControlPoint(beatLength) addControlPoint(time, new LegacyDifficultyControlPoint(beatLength)
#pragma warning restore 618 #pragma warning restore 618
{ {
SpeedMultiplier = speedMultiplier, SliderVelocity = speedMultiplier,
}, timingChange); }, timingChange);
addControlPoint(time, new EffectControlPoint var effectPoint = new EffectControlPoint
{ {
KiaiMode = kiaiMode, KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature, OmitFirstBarLine = omitFirstBarSignature,
}, timingChange); };
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
// scrolling rulesets use effect points rather than difficulty points for scroll speed adjustments.
if (!isOsuRuleset)
effectPoint.ScrollSpeed = speedMultiplier;
addControlPoint(time, effectPoint, timingChange);
addControlPoint(time, new LegacySampleControlPoint addControlPoint(time, new LegacySampleControlPoint
{ {

View File

@ -170,33 +170,30 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.ControlPointInfo.Groups.Count == 0) if (beatmap.ControlPointInfo.Groups.Count == 0)
return; return;
var legacyControlPoints = new LegacyControlPointInfo();
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
legacyControlPoints.Add(point.Time, point.DeepClone());
writer.WriteLine("[TimingPoints]"); writer.WriteLine("[TimingPoints]");
if (!(beatmap.ControlPointInfo is LegacyControlPointInfo)) SampleControlPoint lastRelevantSamplePoint = null;
DifficultyControlPoint lastRelevantDifficultyPoint = null;
bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0;
// iterate over hitobjects and pull out all required sample and difficulty changes
extractDifficultyControlPoints(beatmap.HitObjects);
extractSampleControlPoints(beatmap.HitObjects);
// handle scroll speed, which is stored as "slider velocity" in legacy formats.
// this is relevant for scrolling ruleset beatmaps.
if (!isOsuRuleset)
{ {
var legacyControlPoints = new LegacyControlPointInfo(); foreach (var point in legacyControlPoints.EffectPoints)
legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed });
foreach (var point in beatmap.ControlPointInfo.AllControlPoints)
legacyControlPoints.Add(point.Time, point.DeepClone());
beatmap.ControlPointInfo = legacyControlPoints;
SampleControlPoint lastRelevantSamplePoint = null;
// iterate over hitobjects and pull out all required sample changes
foreach (var h in beatmap.HitObjects)
{
var hSamplePoint = h.SampleControlPoint;
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
{
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
lastRelevantSamplePoint = hSamplePoint;
}
}
} }
foreach (var group in beatmap.ControlPointInfo.Groups) foreach (var group in legacyControlPoints.Groups)
{ {
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault(); var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
@ -209,16 +206,16 @@ namespace osu.Game.Beatmaps.Formats
} }
// Output any remaining effects as secondary non-timing control point. // Output any remaining effects as secondary non-timing control point.
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time); var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time);
writer.Write(FormattableString.Invariant($"{group.Time},")); writer.Write(FormattableString.Invariant($"{group.Time},"));
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},")); writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},"));
outputControlPointAt(group.Time, false); outputControlPointAt(group.Time, false);
} }
void outputControlPointAt(double time, bool isTimingPoint) void outputControlPointAt(double time, bool isTimingPoint)
{ {
var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); var samplePoint = legacyControlPoints.SamplePointAt(time);
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); var effectPoint = legacyControlPoints.EffectPointAt(time);
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
@ -230,7 +227,7 @@ namespace osu.Game.Beatmaps.Formats
if (effectPoint.OmitFirstBarLine) if (effectPoint.OmitFirstBarLine)
effectFlags |= LegacyEffectFlags.OmitFirstBarLine; effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},")); writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
@ -238,6 +235,55 @@ namespace osu.Game.Beatmaps.Formats
writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
writer.WriteLine(); writer.WriteLine();
} }
IEnumerable<DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
{
if (!isOsuRuleset)
yield break;
foreach (var hitObject in hitObjects)
{
yield return hitObject.DifficultyControlPoint;
foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects))
yield return nested;
}
}
void extractDifficultyControlPoints(IEnumerable<HitObject> hitObjects)
{
foreach (var hDifficultyPoint in collectDifficultyControlPoints(hitObjects).OrderBy(dp => dp.Time))
{
if (!hDifficultyPoint.IsRedundant(lastRelevantDifficultyPoint))
{
legacyControlPoints.Add(hDifficultyPoint.Time, hDifficultyPoint);
lastRelevantDifficultyPoint = hDifficultyPoint;
}
}
}
IEnumerable<SampleControlPoint> collectSampleControlPoints(IEnumerable<HitObject> hitObjects)
{
foreach (var hitObject in hitObjects)
{
yield return hitObject.SampleControlPoint;
foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects))
yield return nested;
}
}
void extractSampleControlPoints(IEnumerable<HitObject> hitObject)
{
foreach (var hSamplePoint in collectSampleControlPoints(hitObject).OrderBy(sp => sp.Time))
{
if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint))
{
legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint);
lastRelevantSamplePoint = hSamplePoint;
}
}
}
} }
private void handleColours(TextWriter writer) private void handleColours(TextWriter writer)

View File

@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps.Formats
public LegacyDifficultyControlPoint() public LegacyDifficultyControlPoint()
{ {
SpeedMultiplierBindable.Precision = double.Epsilon; SliderVelocityBindable.Precision = double.Epsilon;
} }
public override void CopyFrom(ControlPoint other) public override void CopyFrom(ControlPoint other)

View File

@ -1,9 +1,10 @@
// 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.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables; using osu.Framework.Lists;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Beatmaps.Legacy namespace osu.Game.Beatmaps.Legacy
@ -14,9 +15,9 @@ namespace osu.Game.Beatmaps.Legacy
/// All sound points. /// All sound points.
/// </summary> /// </summary>
[JsonProperty] [JsonProperty]
public IBindableList<SampleControlPoint> SamplePoints => samplePoints; public IReadOnlyList<SampleControlPoint> SamplePoints => samplePoints;
private readonly BindableList<SampleControlPoint> samplePoints = new BindableList<SampleControlPoint>(); private readonly SortedList<SampleControlPoint> samplePoints = new SortedList<SampleControlPoint>(Comparer<SampleControlPoint>.Default);
/// <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"/>.
@ -26,35 +27,76 @@ namespace osu.Game.Beatmaps.Legacy
[NotNull] [NotNull]
public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT); public SampleControlPoint SamplePointAt(double time) => BinarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
/// <summary>
/// All difficulty points.
/// </summary>
[JsonProperty]
public IReadOnlyList<DifficultyControlPoint> DifficultyPoints => difficultyPoints;
private readonly SortedList<DifficultyControlPoint> difficultyPoints = new SortedList<DifficultyControlPoint>(Comparer<DifficultyControlPoint>.Default);
/// <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>
[NotNull]
public DifficultyControlPoint DifficultyPointAt(double time) => BinarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
public override void Clear() public override void Clear()
{ {
base.Clear(); base.Clear();
samplePoints.Clear(); samplePoints.Clear();
difficultyPoints.Clear();
} }
protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint) protected override bool CheckAlreadyExisting(double time, ControlPoint newPoint)
{ {
if (newPoint is SampleControlPoint) switch (newPoint)
{ {
var existing = BinarySearch(SamplePoints, time); case SampleControlPoint _:
return newPoint.IsRedundant(existing); // intentionally don't use SamplePointAt (we always need to consider the first sample point).
} var existing = BinarySearch(SamplePoints, time);
return newPoint.IsRedundant(existing);
return base.CheckAlreadyExisting(time, newPoint); case DifficultyControlPoint _:
return newPoint.IsRedundant(DifficultyPointAt(time));
default:
return base.CheckAlreadyExisting(time, newPoint);
}
} }
protected override void GroupItemAdded(ControlPoint controlPoint) protected override void GroupItemAdded(ControlPoint controlPoint)
{ {
if (controlPoint is SampleControlPoint typed) switch (controlPoint)
samplePoints.Add(typed); {
case SampleControlPoint typed:
samplePoints.Add(typed);
return;
base.GroupItemAdded(controlPoint); case DifficultyControlPoint typed:
difficultyPoints.Add(typed);
return;
default:
base.GroupItemAdded(controlPoint);
break;
}
} }
protected override void GroupItemRemoved(ControlPoint controlPoint) protected override void GroupItemRemoved(ControlPoint controlPoint)
{ {
if (controlPoint is SampleControlPoint typed) switch (controlPoint)
samplePoints.Remove(typed); {
case SampleControlPoint typed:
samplePoints.Remove(typed);
break;
case DifficultyControlPoint typed:
difficultyPoints.Remove(typed);
break;
}
base.GroupItemRemoved(controlPoint); base.GroupItemRemoved(controlPoint);
} }

View File

@ -1,11 +1,14 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
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.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -141,12 +144,12 @@ namespace osu.Game.Graphics.Containers
Child = box = new Box { RelativeSizeAxes = Axes.Both }; Child = box = new Box { RelativeSizeAxes = Axes.Both };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
Colour = defaultColour = colours.Gray8; Colour = defaultColour = colours.Gray8;
hoverColour = colours.GrayF; hoverColour = colours.GrayF;
highlightColour = colours.Green; highlightColour = colourProvider?.Highlight1 ?? colours.Green;
} }
public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None) public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None)

View File

@ -1,8 +1,9 @@
// 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.
#nullable enable
using System.Linq; using System.Linq;
using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -14,13 +15,15 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
{ {
private const float corner_radius = 4; private const float corner_radius = 5;
private Color4 accentColour; private Color4 accentColour;
@ -34,11 +37,11 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
if (accentColour == default) if (accentColour == default)
accentColour = colours.PinkDarker; accentColour = colourProvider?.Light4 ?? colours.PinkDarker;
updateAccentColour(); updateAccentColour();
} }
@ -59,14 +62,13 @@ namespace osu.Game.Graphics.UserInterface
{ {
public override bool HandleNonPositionalInput => State == MenuState.Open; public override bool HandleNonPositionalInput => State == MenuState.Open;
private Sample sampleOpen; private Sample? sampleOpen;
private Sample sampleClose; private Sample? sampleClose;
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring // todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
public OsuDropdownMenu() public OsuDropdownMenu()
{ {
CornerRadius = corner_radius; CornerRadius = corner_radius;
BackgroundColour = Color4.Black.Opacity(0.5f);
MaskingContainer.CornerRadius = corner_radius; MaskingContainer.CornerRadius = corner_radius;
Alpha = 0; Alpha = 0;
@ -75,9 +77,11 @@ namespace osu.Game.Graphics.UserInterface
ItemsContainer.Padding = new MarginPadding(5); ItemsContainer.Padding = new MarginPadding(5);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio) private void load(OverlayColourProvider? colourProvider, AudioManager audio)
{ {
BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); sampleOpen = audio.Samples.Get(@"UI/dropdown-open");
sampleClose = audio.Samples.Get(@"UI/dropdown-close"); sampleClose = audio.Samples.Get(@"UI/dropdown-close");
} }
@ -159,6 +163,8 @@ namespace osu.Game.Graphics.UserInterface
{ {
BackgroundColourHover = accentColour ?? nonAccentHoverColour; BackgroundColourHover = accentColour ?? nonAccentHoverColour;
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour; BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
BackgroundColour = BackgroundColourHover.Opacity(0);
UpdateBackgroundColour(); UpdateBackgroundColour();
UpdateForegroundColour(); UpdateForegroundColour();
} }
@ -178,8 +184,6 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BackgroundColour = Color4.Transparent;
nonAccentHoverColour = colours.PinkDarker; nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f); nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours(); updateColours();
@ -187,16 +191,29 @@ namespace osu.Game.Graphics.UserInterface
AddInternal(new HoverSounds()); AddInternal(new HoverSounds());
} }
protected override void UpdateBackgroundColour()
{
if (!IsPreSelected && !IsSelected)
{
Background.FadeOut(600, Easing.OutQuint);
return;
}
Background.FadeIn(100, Easing.OutQuint);
Background.FadeColour(IsPreSelected ? BackgroundColourHover : BackgroundColourSelected, 100, Easing.OutQuint);
}
protected override void UpdateForegroundColour() protected override void UpdateForegroundColour()
{ {
base.UpdateForegroundColour(); base.UpdateForegroundColour();
if (Foreground.Children.FirstOrDefault() is Content content) content.Chevron.Alpha = IsHovered ? 1 : 0; if (Foreground.Children.FirstOrDefault() is Content content)
content.Hovering = IsHovered;
} }
protected override Drawable CreateContent() => new Content(); protected override Drawable CreateContent() => new Content();
protected new class Content : FillFlowContainer, IHasText protected new class Content : CompositeDrawable, IHasText
{ {
public LocalisableString Text public LocalisableString Text
{ {
@ -207,32 +224,64 @@ namespace osu.Game.Graphics.UserInterface
public readonly OsuSpriteText Label; public readonly OsuSpriteText Label;
public readonly SpriteIcon Chevron; public readonly SpriteIcon Chevron;
private const float chevron_offset = -3;
public Content() public Content()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Direction = FillDirection.Horizontal;
Children = new Drawable[] InternalChildren = new Drawable[]
{ {
Chevron = new SpriteIcon Chevron = new SpriteIcon
{ {
AlwaysPresent = true,
Icon = FontAwesome.Solid.ChevronRight, Icon = FontAwesome.Solid.ChevronRight,
Colour = Color4.Black,
Alpha = 0.5f,
Size = new Vector2(8), Size = new Vector2(8),
Alpha = 0,
X = chevron_offset,
Margin = new MarginPadding { Left = 3, Right = 3 }, Margin = new MarginPadding { Left = 3, Right = 3 },
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
Label = new OsuSpriteText Label = new OsuSpriteText
{ {
X = 15,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
}; };
} }
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider? colourProvider)
{
Chevron.Colour = colourProvider?.Background5 ?? Color4.Black;
}
private bool hovering;
public bool Hovering
{
get => hovering;
set
{
if (value == hovering)
return;
hovering = value;
if (hovering)
{
Chevron.FadeIn(400, Easing.OutQuint);
Chevron.MoveToX(0, 400, Easing.OutQuint);
}
else
{
Chevron.FadeOut(200);
Chevron.MoveToX(chevron_offset, 200, Easing.In);
}
}
}
} }
} }
@ -267,7 +316,7 @@ namespace osu.Game.Graphics.UserInterface
public OsuDropdownHeader() public OsuDropdownHeader()
{ {
Foreground.Padding = new MarginPadding(4); Foreground.Padding = new MarginPadding(10);
AutoSizeAxes = Axes.None; AutoSizeAxes = Axes.None;
Margin = new MarginPadding { Bottom = 4 }; Margin = new MarginPadding { Bottom = 4 };
@ -303,8 +352,7 @@ namespace osu.Game.Graphics.UserInterface
Icon = FontAwesome.Solid.ChevronDown, Icon = FontAwesome.Solid.ChevronDown,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Horizontal = 5 }, Size = new Vector2(16),
Size = new Vector2(12),
}, },
} }
} }
@ -313,11 +361,11 @@ namespace osu.Game.Graphics.UserInterface
AddInternal(new HoverClickSounds()); AddInternal(new HoverClickSounds());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
BackgroundColour = Color4.Black.Opacity(0.5f); BackgroundColour = colourProvider?.Background5 ?? Color4.Black.Opacity(0.5f);
BackgroundColourHover = colours.PinkDarker; BackgroundColourHover = colourProvider?.Light4 ?? colours.PinkDarker;
} }
} }
} }

View File

@ -2,11 +2,8 @@
// 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 osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -15,30 +12,13 @@ namespace osu.Game.Graphics.UserInterface
{ {
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
protected override DropdownMenu CreateMenu() => new SlimMenu();
private class SlimDropdownHeader : OsuDropdownHeader private class SlimDropdownHeader : OsuDropdownHeader
{ {
public SlimDropdownHeader() public SlimDropdownHeader()
{ {
Height = 25; Height = 25;
Icon.Size = new Vector2(16);
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
} }
protected override void LoadComplete()
{
base.LoadComplete();
BackgroundColour = Color4.Black.Opacity(0.25f);
}
}
private class SlimMenu : OsuDropdownMenu
{
public SlimMenu()
{
BackgroundColour = Color4.Black.Opacity(0.7f);
}
} }
} }
} }

View File

@ -1,12 +1,15 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; 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.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osuTK; using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2 namespace osu.Game.Graphics.UserInterfaceV2
@ -44,6 +47,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
/// </summary> /// </summary>
protected readonly T Component; protected readonly T Component;
private readonly Box background;
private readonly GridContainer grid; private readonly GridContainer grid;
private readonly OsuTextFlowContainer labelText; private readonly OsuTextFlowContainer labelText;
private readonly OsuTextFlowContainer descriptionText; private readonly OsuTextFlowContainer descriptionText;
@ -62,10 +66,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex("1c2125"),
}, },
new FillFlowContainer new FillFlowContainer
{ {
@ -146,9 +149,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour osuColour) private void load(OverlayColourProvider? colourProvider, OsuColour osuColour)
{ {
background.Colour = colourProvider?.Background4 ?? Color4Extensions.FromHex(@"1c2125");
descriptionText.Colour = osuColour.Yellow; descriptionText.Colour = osuColour.Yellow;
} }

View File

@ -1,6 +1,8 @@
// 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.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -10,6 +12,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -66,11 +69,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours) private void load(OverlayColourProvider? colourProvider, OsuColour colours)
{ {
enabledColour = colours.BlueDark; enabledColour = colourProvider?.Highlight1 ?? colours.BlueDark;
disabledColour = colours.Gray3; disabledColour = colourProvider?.Background3 ?? colours.Gray3;
switchContainer.Colour = enabledColour; switchContainer.Colour = enabledColour;
fill.Colour = disabledColour; fill.Colour = disabledColour;

View File

@ -31,10 +31,12 @@ namespace osu.Game.Overlays.Notifications
set set
{ {
progress = value; progress = value;
Scheduler.AddOnce(() => progressBar.Progress = progress); Scheduler.AddOnce(updateProgress, progress);
} }
} }
private void updateProgress(float progress) => progressBar.Progress = progress;
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

View File

@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
@ -27,6 +28,11 @@ namespace osu.Game.Overlays.Settings
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString())); public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString()));
public SettingsDropdown()
{
FlowContent.Spacing = new Vector2(0, 10);
}
protected sealed override Drawable CreateControl() => CreateDropdown(); protected sealed override Drawable CreateControl() => CreateDropdown();
protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl(); protected virtual OsuDropdown<T> CreateDropdown() => new DropdownControl();
@ -35,7 +41,6 @@ namespace osu.Game.Overlays.Settings
{ {
public DropdownControl() public DropdownControl()
{ {
Margin = new MarginPadding { Top = 5 };
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }

View File

@ -16,7 +16,6 @@ namespace osu.Game.Overlays.Settings
{ {
public DropdownControl() public DropdownControl()
{ {
Margin = new MarginPadding { Top = 5 };
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
} }

View File

@ -13,7 +13,6 @@ using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -389,41 +388,42 @@ namespace osu.Game.Rulesets.Edit
return new SnapResult(screenSpacePosition, targetTime, playfield); return new SnapResult(screenSpacePosition, targetTime, playfield);
} }
public override float GetBeatSnapDistanceAt(double referenceTime) public override float GetBeatSnapDistanceAt(HitObject referenceObject)
{ {
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor);
return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
} }
public override float DurationToDistance(double referenceTime, double duration) public override float DurationToDistance(HitObject referenceObject, double duration)
{ {
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject));
} }
public override double DistanceToDuration(double referenceTime, float distance) public override double DistanceToDuration(HitObject referenceObject, float distance)
{ {
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime);
return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength;
} }
public override double GetSnappedDurationFromDistance(double referenceTime, float distance) public override double GetSnappedDurationFromDistance(HitObject referenceObject, float distance)
=> BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime; => BeatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime;
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) public override float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance)
{ {
double actualDuration = referenceTime + DistanceToDuration(referenceTime, distance); double startTime = referenceObject.StartTime;
double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, referenceTime); double actualDuration = startTime + DistanceToDuration(referenceObject, distance);
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); double snappedEndTime = BeatSnapProvider.SnapTime(actualDuration, startTime);
double beatLength = BeatSnapProvider.GetBeatLengthAtTime(startTime);
// we don't want to exceed the actual duration and snap to a point in the future. // we don't want to exceed the actual duration and snap to a point in the future.
// as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it.
if (snappedEndTime > actualDuration + 1) if (snappedEndTime > actualDuration + 1)
snappedEndTime -= beatLength; snappedEndTime -= beatLength;
return DurationToDistance(referenceTime, snappedEndTime - referenceTime); return DurationToDistance(referenceObject, snappedEndTime - startTime);
} }
#endregion #endregion
@ -466,15 +466,15 @@ namespace osu.Game.Rulesets.Edit
public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) => public virtual SnapResult SnapScreenSpacePositionToValidPosition(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, null); new SnapResult(screenSpacePosition, null);
public abstract float GetBeatSnapDistanceAt(double referenceTime); public abstract float GetBeatSnapDistanceAt(HitObject referenceObject);
public abstract float DurationToDistance(double referenceTime, double duration); public abstract float DurationToDistance(HitObject referenceObject, double duration);
public abstract double DistanceToDuration(double referenceTime, float distance); public abstract double DistanceToDuration(HitObject referenceObject, float distance);
public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract double GetSnappedDurationFromDistance(HitObject referenceObject, float distance);
public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance);
#endregion #endregion
} }

View File

@ -1,6 +1,7 @@
// 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 osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
@ -27,41 +28,41 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Retrieves the distance between two points within a timing point that are one beat length apart. /// Retrieves the distance between two points within a timing point that are one beat length apart.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns> /// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns>
float GetBeatSnapDistanceAt(double referenceTime); float GetBeatSnapDistanceAt(HitObject referenceObject);
/// <summary> /// <summary>
/// Converts a duration to a distance. /// Converts a duration to a distance.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="duration"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="duration">The duration to convert.</param> /// <param name="duration">The duration to convert.</param>
/// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns> /// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns>
float DurationToDistance(double referenceTime, double duration); float DurationToDistance(HitObject referenceObject, double duration);
/// <summary> /// <summary>
/// Converts a distance to a duration. /// Converts a distance to a duration.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns>
double DistanceToDuration(double referenceTime, float distance); double DistanceToDuration(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts a distance to a snapped duration. /// Converts a distance to a snapped duration.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns>
double GetSnappedDurationFromDistance(double referenceTime, float distance); double GetSnappedDurationFromDistance(HitObject referenceObject, float distance);
/// <summary> /// <summary>
/// Converts an unsnapped distance to a snapped distance. /// Converts an unsnapped distance to a snapped distance.
/// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>. /// The returned distance will always be floored (as to never exceed the provided <paramref name="distance"/>.
/// </summary> /// </summary>
/// <param name="referenceTime">The time of the timing point which <paramref name="distance"/> resides in.</param> /// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
/// <param name="distance">The distance to convert.</param> /// <param name="distance">The distance to convert.</param>
/// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns> /// <returns>A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.</returns>
float GetSnappedDistanceFromDistance(double referenceTime, float distance); float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance);
} }
} }

View File

@ -67,7 +67,8 @@ namespace osu.Game.Rulesets.Objects
} }
} }
public SampleControlPoint SampleControlPoint; public SampleControlPoint SampleControlPoint = SampleControlPoint.DEFAULT;
public DifficultyControlPoint DifficultyControlPoint = DifficultyControlPoint.DEFAULT;
/// <summary> /// <summary>
/// Whether this <see cref="HitObject"/> is in Kiai time. /// Whether this <see cref="HitObject"/> is in Kiai time.
@ -94,6 +95,12 @@ namespace osu.Game.Rulesets.Objects
foreach (var nested in nestedHitObjects) foreach (var nested in nestedHitObjects)
nested.StartTime += offset; nested.StartTime += offset;
if (DifficultyControlPoint != DifficultyControlPoint.DEFAULT)
DifficultyControlPoint.Time = time.NewValue;
if (SampleControlPoint != SampleControlPoint.DEFAULT)
SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
}; };
} }
@ -105,16 +112,21 @@ namespace osu.Game.Rulesets.Objects
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default)
{ {
var legacyInfo = controlPointInfo as LegacyControlPointInfo;
if (legacyInfo != null)
{
DifficultyControlPoint = (DifficultyControlPoint)legacyInfo.DifficultyPointAt(StartTime).DeepClone();
DifficultyControlPoint.Time = StartTime;
}
ApplyDefaultsToSelf(controlPointInfo, difficulty); ApplyDefaultsToSelf(controlPointInfo, difficulty);
if (controlPointInfo is LegacyControlPointInfo legacyInfo) // This is done here after ApplyDefaultsToSelf as we may require custom defaults to be applied to have an accurate end time.
if (legacyInfo != null)
{ {
// This is done here since ApplyDefaultsToSelf may be used to determine the end time SampleControlPoint = (SampleControlPoint)legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency).DeepClone();
SampleControlPoint = legacyInfo.SamplePointAt(this.GetEndTime() + control_point_leniency); SampleControlPoint.Time = this.GetEndTime() + control_point_leniency;
}
else
{
SampleControlPoint ??= SampleControlPoint.DEFAULT;
} }
nestedHitObjects.Clear(); nestedHitObjects.Clear();

View File

@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * DifficultyControlPoint.SliderVelocity;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
} }

View File

@ -7,7 +7,7 @@ using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Timing namespace osu.Game.Rulesets.Timing
{ {
/// <summary> /// <summary>
/// A control point which adds an aggregated multiplier based on the provided <see cref="TimingPoint"/>'s BeatLength and <see cref="DifficultyPoint"/>'s SpeedMultiplier. /// A control point which adds an aggregated multiplier based on the provided <see cref="TimingPoint"/>'s BeatLength and <see cref="EffectPoint"/>'s SpeedMultiplier.
/// </summary> /// </summary>
public class MultiplierControlPoint : IComparable<MultiplierControlPoint> public class MultiplierControlPoint : IComparable<MultiplierControlPoint>
{ {
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Timing
/// <summary> /// <summary>
/// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides. /// The aggregate multiplier which this <see cref="MultiplierControlPoint"/> provides.
/// </summary> /// </summary>
public double Multiplier => Velocity * DifficultyPoint.SpeedMultiplier * BaseBeatLength / TimingPoint.BeatLength; public double Multiplier => Velocity * EffectPoint.ScrollSpeed * BaseBeatLength / TimingPoint.BeatLength;
/// <summary> /// <summary>
/// The base beat length to scale the <see cref="TimingPoint"/> provided multiplier relative to. /// The base beat length to scale the <see cref="TimingPoint"/> provided multiplier relative to.
@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Timing
public TimingControlPoint TimingPoint = new TimingControlPoint(); public TimingControlPoint TimingPoint = new TimingControlPoint();
/// <summary> /// <summary>
/// The <see cref="DifficultyControlPoint"/> that provides additional difficulty information for this <see cref="MultiplierControlPoint"/>. /// The <see cref="EffectControlPoint"/> that provides additional difficulty information for this <see cref="MultiplierControlPoint"/>.
/// </summary> /// </summary>
public DifficultyControlPoint DifficultyPoint = new DifficultyControlPoint(); public EffectControlPoint EffectPoint = new EffectControlPoint();
/// <summary> /// <summary>
/// Creates a <see cref="MultiplierControlPoint"/>. This is required for JSON serialization /// Creates a <see cref="MultiplierControlPoint"/>. This is required for JSON serialization

View File

@ -140,25 +140,32 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point
var lastTimingPoint = new TimingControlPoint(); var lastTimingPoint = new TimingControlPoint();
var lastDifficultyPoint = new DifficultyControlPoint(); var lastEffectPoint = new EffectControlPoint();
var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default); var allPoints = new SortedList<ControlPoint>(Comparer<ControlPoint>.Default);
allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints);
allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); allPoints.AddRange(Beatmap.ControlPointInfo.EffectPoints);
// Generate the timing points, making non-timing changes use the previous timing change and vice-versa // Generate the timing points, making non-timing changes use the previous timing change and vice-versa
var timingChanges = allPoints.Select(c => var timingChanges = allPoints.Select(c =>
{ {
if (c is TimingControlPoint timingPoint) switch (c)
lastTimingPoint = timingPoint; {
else if (c is DifficultyControlPoint difficultyPoint) case TimingControlPoint timingPoint:
lastDifficultyPoint = difficultyPoint; lastTimingPoint = timingPoint;
break;
case EffectControlPoint difficultyPoint:
lastEffectPoint = difficultyPoint;
break;
}
return new MultiplierControlPoint(c.Time) return new MultiplierControlPoint(c.Time)
{ {
Velocity = Beatmap.Difficulty.SliderMultiplier, Velocity = Beatmap.Difficulty.SliderMultiplier,
BaseBeatLength = baseBeatLength, BaseBeatLength = baseBeatLength,
TimingPoint = lastTimingPoint, TimingPoint = lastTimingPoint,
DifficultyPoint = lastDifficultyPoint EffectPoint = lastEffectPoint
}; };
}); });

View File

@ -5,14 +5,15 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
{ {
public abstract class CircularDistanceSnapGrid : DistanceSnapGrid public abstract class CircularDistanceSnapGrid : DistanceSnapGrid
{ {
protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
: base(startPosition, startTime, endTime) : base(referenceObject, startPosition, startTime, endTime)
{ {
} }
@ -79,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 normalisedDirection = direction * new Vector2(1f / distance);
Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius; Vector2 snappedPosition = StartPosition + normalisedDirection * radialCount * radius;
return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(StartTime, (snappedPosition - StartPosition).Length)); return (snappedPosition, StartTime + SnapProvider.GetSnappedDurationFromDistance(ReferenceObject, (snappedPosition - StartPosition).Length));
} }
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Layout; using osu.Framework.Layout;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components namespace osu.Game.Screens.Edit.Compose.Components
@ -54,15 +55,20 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit);
private readonly double? endTime; private readonly double? endTime;
protected readonly HitObject ReferenceObject;
/// <summary> /// <summary>
/// Creates a new <see cref="DistanceSnapGrid"/>. /// Creates a new <see cref="DistanceSnapGrid"/>.
/// </summary> /// </summary>
/// <param name="referenceObject">A reference object to gather relevant difficulty values from.</param>
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param> /// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param> /// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param> /// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null) protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
{ {
ReferenceObject = referenceObject;
this.endTime = endTime; this.endTime = endTime;
StartPosition = startPosition; StartPosition = startPosition;
StartTime = startTime; StartTime = startTime;
@ -80,7 +86,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updateSpacing() private void updateSpacing()
{ {
DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject);
if (endTime == null) if (endTime == null)
MaxIntervals = int.MaxValue; MaxIntervals = int.MaxValue;
@ -88,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
// +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors
double maxDuration = endTime.Value - StartTime + 1; double maxDuration = endTime.Value - StartTime + 1;
MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(ReferenceObject, DistanceSpacing));
} }
gridCache.Invalidate(); gridCache.Invalidate();

View File

@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
public DifficultyPointPiece(DifficultyControlPoint point) public DifficultyPointPiece(DifficultyControlPoint point)
: base(point) : base(point)
{ {
speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); speedMultiplier = point.SliderVelocityBindable.GetBoundCopy();
Y = Height; Y = Height;
} }

View File

@ -15,6 +15,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osuTK; using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline namespace osu.Game.Screens.Edit.Compose.Components.Timeline
@ -298,14 +299,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private double getTimeFromPosition(Vector2 localPosition) => private double getTimeFromPosition(Vector2 localPosition) =>
(localPosition.X / Content.DrawWidth) * track.Length; (localPosition.X / Content.DrawWidth) * track.Length;
public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); public float GetBeatSnapDistanceAt(HitObject referenceObject) => throw new NotImplementedException();
public float DurationToDistance(double referenceTime, double duration) => throw new NotImplementedException(); public float DurationToDistance(HitObject referenceObject, double duration) => throw new NotImplementedException();
public double DistanceToDuration(double referenceTime, float distance) => throw new NotImplementedException(); public double DistanceToDuration(HitObject referenceObject, float distance) => throw new NotImplementedException();
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); public double GetSnappedDurationFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException();
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => throw new NotImplementedException(); public float GetSnappedDistanceFromDistance(HitObject referenceObject, float distance) => throw new NotImplementedException();
} }
} }

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit
{ {
new Box new Box
{ {
Colour = ColourProvider.Dark4, Colour = ColourProvider.Background3,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
roundedContent = new Container roundedContent = new Container

View File

@ -4,21 +4,22 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
internal class DifficultySection : Section<DifficultyControlPoint> internal class DifficultySection : Section<DifficultyControlPoint>
{ {
private SliderWithTextBoxInput<double> multiplierSlider; private SliderWithTextBoxInput<double> sliderVelocitySlider;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Flow.AddRange(new[] Flow.AddRange(new[]
{ {
multiplierSlider = new SliderWithTextBoxInput<double>("Speed Multiplier") sliderVelocitySlider = new SliderWithTextBoxInput<double>("Slider Velocity")
{ {
Current = new DifficultyControlPoint().SpeedMultiplierBindable, Current = new DifficultyControlPoint().SliderVelocityBindable,
KeyboardStep = 0.1f KeyboardStep = 0.1f
} }
}); });
@ -28,27 +29,27 @@ namespace osu.Game.Screens.Edit.Timing
{ {
if (point.NewValue != null) if (point.NewValue != null)
{ {
var selectedPointBindable = point.NewValue.SpeedMultiplierBindable; var selectedPointBindable = point.NewValue.SliderVelocityBindable;
// there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint).
// generally that level of precision could only be set by externally editing the .osu file, so at the point // generally that level of precision could only be set by externally editing the .osu file, so at the point
// a user is looking to update this within the editor it should be safe to obliterate this additional precision. // a user is looking to update this within the editor it should be safe to obliterate this additional precision.
double expectedPrecision = new DifficultyControlPoint().SpeedMultiplierBindable.Precision; double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision;
if (selectedPointBindable.Precision < expectedPrecision) if (selectedPointBindable.Precision < expectedPrecision)
selectedPointBindable.Precision = expectedPrecision; selectedPointBindable.Precision = expectedPrecision;
multiplierSlider.Current = selectedPointBindable; sliderVelocitySlider.Current = selectedPointBindable;
multiplierSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); sliderVelocitySlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
} }
} }
protected override DifficultyControlPoint CreatePoint() protected override DifficultyControlPoint CreatePoint()
{ {
var reference = Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time); var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.DifficultyPointAt(SelectedGroup.Value.Time) ?? DifficultyControlPoint.DEFAULT;
return new DifficultyControlPoint return new DifficultyControlPoint
{ {
SpeedMultiplier = reference.SpeedMultiplier, SliderVelocity = reference.SliderVelocity,
}; };
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
@ -13,13 +14,20 @@ namespace osu.Game.Screens.Edit.Timing
private LabelledSwitchButton kiai; private LabelledSwitchButton kiai;
private LabelledSwitchButton omitBarLine; private LabelledSwitchButton omitBarLine;
private SliderWithTextBoxInput<double> scrollSpeedSlider;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Flow.AddRange(new[] Flow.AddRange(new Drawable[]
{ {
kiai = new LabelledSwitchButton { Label = "Kiai Time" }, kiai = new LabelledSwitchButton { Label = "Kiai Time" },
omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" }, omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" },
scrollSpeedSlider = new SliderWithTextBoxInput<double>("Scroll Speed")
{
Current = new EffectControlPoint().ScrollSpeedBindable,
KeyboardStep = 0.1f
}
}); });
} }
@ -32,6 +40,9 @@ namespace osu.Game.Screens.Edit.Timing
omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable;
omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
scrollSpeedSlider.Current = point.NewValue.ScrollSpeedBindable;
scrollSpeedSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState());
} }
} }
@ -42,7 +53,8 @@ namespace osu.Game.Screens.Edit.Timing
return new EffectControlPoint return new EffectControlPoint
{ {
KiaiMode = reference.KiaiMode, KiaiMode = reference.KiaiMode,
OmitFirstBarLine = reference.OmitFirstBarLine OmitFirstBarLine = reference.OmitFirstBarLine,
ScrollSpeed = reference.ScrollSpeed,
}; };
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
public DifficultyRowAttribute(DifficultyControlPoint difficulty) public DifficultyRowAttribute(DifficultyControlPoint difficulty)
: base(difficulty, "difficulty") : base(difficulty, "difficulty")
{ {
speedMultiplier = difficulty.SpeedMultiplierBindable.GetBoundCopy(); speedMultiplier = difficulty.SliderVelocityBindable.GetBoundCopy();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
}, },
text = new AttributeText(Point) text = new AttributeText(Point)
{ {
Width = 40, Width = 45,
}, },
}); });

View File

@ -12,14 +12,18 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
{ {
private readonly Bindable<bool> kiaiMode; private readonly Bindable<bool> kiaiMode;
private readonly Bindable<bool> omitBarLine; private readonly Bindable<bool> omitBarLine;
private readonly BindableNumber<double> scrollSpeed;
private AttributeText kiaiModeBubble; private AttributeText kiaiModeBubble;
private AttributeText omitBarLineBubble; private AttributeText omitBarLineBubble;
private AttributeText text;
public EffectRowAttribute(EffectControlPoint effect) public EffectRowAttribute(EffectControlPoint effect)
: base(effect, "effect") : base(effect, "effect")
{ {
kiaiMode = effect.KiaiModeBindable.GetBoundCopy(); kiaiMode = effect.KiaiModeBindable.GetBoundCopy();
omitBarLine = effect.OmitFirstBarLineBindable.GetBoundCopy(); omitBarLine = effect.OmitFirstBarLineBindable.GetBoundCopy();
scrollSpeed = effect.ScrollSpeedBindable.GetBoundCopy();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -27,12 +31,20 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
{ {
Content.AddRange(new Drawable[] Content.AddRange(new Drawable[]
{ {
new AttributeProgressBar(Point)
{
Current = scrollSpeed,
},
text = new AttributeText(Point) { Width = 45 },
kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, kiaiModeBubble = new AttributeText(Point) { Text = "kiai" },
omitBarLineBubble = new AttributeText(Point) { Text = "no barline" }, omitBarLineBubble = new AttributeText(Point) { Text = "no barline" },
}); });
kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true); kiaiMode.BindValueChanged(enabled => kiaiModeBubble.FadeTo(enabled.NewValue ? 1 : 0), true);
omitBarLine.BindValueChanged(enabled => omitBarLineBubble.FadeTo(enabled.NewValue ? 1 : 0), true); omitBarLine.BindValueChanged(enabled => omitBarLineBubble.FadeTo(enabled.NewValue ? 1 : 0), true);
scrollSpeed.BindValueChanged(_ => updateText(), true);
} }
private void updateText() => text.Text = $"{scrollSpeed.Value:n2}x";
} }
} }

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
@ -42,6 +43,15 @@ namespace osu.Game.Screens.Edit.Timing
} }
} }
protected override SampleControlPoint CreatePoint() => new SampleControlPoint(); // TODO: remove protected override SampleControlPoint CreatePoint()
{
var reference = (Beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(SelectedGroup.Value.Time) ?? SampleControlPoint.DEFAULT;
return new SampleControlPoint
{
SampleBank = reference.SampleBank,
SampleVolume = reference.SampleVolume,
};
}
} }
} }

View File

@ -150,6 +150,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
notifyRoomsUpdated(); notifyRoomsUpdated();
} }
private void notifyRoomsUpdated() => Scheduler.AddOnce(() => RoomsUpdated?.Invoke()); private void notifyRoomsUpdated()
{
Scheduler.AddOnce(invokeRoomsUpdated);
void invokeRoomsUpdated() => RoomsUpdated?.Invoke();
}
} }
} }

View File

@ -7,7 +7,6 @@ using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Extensions.ExceptionExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -99,14 +98,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider, OsuColour colours)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d"), Colour = colourProvider.Background4
}, },
new GridContainer new GridContainer
{ {
@ -249,7 +248,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f), Colour = colourProvider.Background5
}, },
new FillFlowContainer new FillFlowContainer
{ {

View File

@ -79,11 +79,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void load() private void load()
{ {
isConnected.BindTo(client.IsConnected); isConnected.BindTo(client.IsConnected);
isConnected.BindValueChanged(c => Scheduler.AddOnce(() => isConnected.BindValueChanged(c => Scheduler.AddOnce(poll), true);
{ }
if (isConnected.Value && IsLoaded)
PollImmediately(); private void poll()
}), true); {
if (isConnected.Value && IsLoaded)
PollImmediately();
} }
protected override Task Poll() protected override Task Poll()

View File

@ -19,15 +19,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
base.LoadComplete(); base.LoadComplete();
Client.RoomUpdated += OnRoomUpdated; Client.RoomUpdated += invokeOnRoomUpdated;
Client.UserLeft += invokeUserLeft;
Client.UserLeft += UserLeft; Client.UserKicked += invokeUserKicked;
Client.UserKicked += UserKicked; Client.UserJoined += invokeUserJoined;
Client.UserJoined += UserJoined;
OnRoomUpdated(); OnRoomUpdated();
} }
private void invokeOnRoomUpdated() => Scheduler.AddOnce(OnRoomUpdated);
private void invokeUserJoined(MultiplayerRoomUser user) => Scheduler.AddOnce(UserJoined, user);
private void invokeUserKicked(MultiplayerRoomUser user) => Scheduler.AddOnce(UserKicked, user);
private void invokeUserLeft(MultiplayerRoomUser user) => Scheduler.AddOnce(UserLeft, user);
/// <summary> /// <summary>
/// Invoked when a user has joined the room. /// Invoked when a user has joined the room.
/// </summary> /// </summary>
@ -63,10 +67,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
if (Client != null) if (Client != null)
{ {
Client.UserLeft -= UserLeft; Client.RoomUpdated -= invokeOnRoomUpdated;
Client.UserKicked -= UserKicked; Client.UserLeft -= invokeUserLeft;
Client.UserJoined -= UserJoined; Client.UserKicked -= invokeUserKicked;
Client.RoomUpdated -= OnRoomUpdated; Client.UserJoined -= invokeUserJoined;
} }
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
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;
@ -77,14 +76,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OverlayColourProvider colourProvider, OsuColour colours)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d"), Colour = colourProvider.Background4
}, },
new GridContainer new GridContainer
{ {
@ -256,7 +255,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4Extensions.FromHex(@"28242d").Darken(0.5f).Opacity(1f), Colour = colourProvider.Background5
}, },
new FillFlowContainer new FillFlowContainer
{ {

View File

@ -71,7 +71,6 @@ namespace osu.Game.Screens.Play
this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ =>
{ {
OnComplete?.Invoke(); OnComplete?.Invoke();
Expire();
}); });
failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic); failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic);

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.6.0" /> <PackageReference Include="Realm" Version="10.6.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1013.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1014.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
<PackageReference Include="Sentry" Version="3.9.4" /> <PackageReference Include="Sentry" Version="3.9.4" />
<PackageReference Include="SharpCompress" Version="0.29.0" /> <PackageReference Include="SharpCompress" Version="0.29.0" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1013.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1014.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1004.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1013.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1014.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" /> <PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />