diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 26393c8edb..0ff48122e2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Mania.Beatmaps.Patterns; @@ -47,6 +49,26 @@ namespace osu.Game.Rulesets.Mania.Beatmaps if (IsForCurrentRuleset) { + if (beatmap.ControlPointInfo is LegacyControlPointInfo legacyControlPoints) + { + // convert all slider velocity adjustments to scroll speed adjustments. + foreach (var controlPoint in legacyControlPoints.DifficultyPoints.ToArray()) + { + double time = controlPoint.Time; + + var reference = legacyControlPoints.EffectPointAt(time); + + var scrollControlPoint = new EffectControlPoint(); + scrollControlPoint.CopyFrom(reference); + scrollControlPoint.ScrollSpeed = controlPoint.SliderVelocity; + + legacyControlPoints.Add(time, scrollControlPoint); + + // remove the DifficultyControlPoint as we don't need them any more. + legacyControlPoints.GroupAt(time).Remove(controlPoint); + } + } + TargetColumns = GetColumnCountForNonConvert(beatmap.BeatmapInfo); if (TargetColumns > ManiaRuleset.MAX_STAGE_KEYS) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index b9244d88fe..eaa15f28ad 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(4, controlPoints.TimingPoints.Count); Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(13, controlPoints.EffectPoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index d075af291c..a9b5793565 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -387,7 +387,6 @@ namespace osu.Game.Beatmaps.Formats { KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, - ScrollSpeed = speedMultiplier, }, timingChange); addControlPoint(time, new LegacySampleControlPoint diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 25fe6e4a30..7672c4039a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -170,24 +170,22 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.ControlPointInfo.Groups.Count == 0) return; + var legacyControlPoints = new LegacyControlPointInfo(); + foreach (var point in beatmap.ControlPointInfo.AllControlPoints) + legacyControlPoints.Add(point.Time, point.DeepClone()); + 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 + foreach (var h in beatmap.HitObjects) { - var legacyControlPoints = new LegacyControlPointInfo(); - - foreach (var point in beatmap.ControlPointInfo.AllControlPoints) - legacyControlPoints.Add(point.Time, point.DeepClone()); - - beatmap.ControlPointInfo = legacyControlPoints; - - SampleControlPoint lastRelevantSamplePoint = null; - DifficultyControlPoint lastRelevantDifficultyPoint = null; - - // iterate over hitobjects and pull out all required sample and difficulty changes - foreach (var h in beatmap.HitObjects) + if (isOsuRuleset) { - var hSamplePoint = h.SampleControlPoint; var hDifficultyPoint = h.DifficultyControlPoint; if (!hDifficultyPoint.IsRedundant(lastRelevantDifficultyPoint)) @@ -195,19 +193,26 @@ namespace osu.Game.Beatmaps.Formats legacyControlPoints.Add(hDifficultyPoint.Time, hDifficultyPoint); lastRelevantDifficultyPoint = hDifficultyPoint; } - - if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint)) - { - legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint); - lastRelevantSamplePoint = hSamplePoint; - } } - // In the future we may want to pull out approach rate changes from EffectPoints (and create DifficultyControlPoints to handle them). - // This requires special consideration due to precision mismatches (see implementation of LegacyDifficultyControlPoint). + var hSamplePoint = h.SampleControlPoint; + + if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint)) + { + legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint); + lastRelevantSamplePoint = hSamplePoint; + } } - foreach (var group in beatmap.ControlPointInfo.Groups) + // handle scroll speed, which is stored as "slider velocity" in legacy formats. + // note that this is only relevant for mania beatmaps. + if (!isOsuRuleset) + { + foreach (var point in legacyControlPoints.EffectPoints) + legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); + } + + foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType().FirstOrDefault(); @@ -220,7 +225,7 @@ namespace osu.Game.Beatmaps.Formats } // Output any remaining effects as secondary non-timing control point. - var difficultyPoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).DifficultyPointAt(group.Time); + var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); writer.Write(FormattableString.Invariant($"{group.Time},")); writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},")); outputControlPointAt(group.Time, false); @@ -228,8 +233,8 @@ namespace osu.Game.Beatmaps.Formats void outputControlPointAt(double time, bool isTimingPoint) { - var samplePoint = ((LegacyControlPointInfo)beatmap.ControlPointInfo).SamplePointAt(time); - var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); + var samplePoint = legacyControlPoints.SamplePointAt(time); + var effectPoint = legacyControlPoints.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); @@ -241,7 +246,7 @@ namespace osu.Game.Beatmaps.Formats if (effectPoint.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($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index f0a0dd1367..c7b54891e7 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -148,10 +148,16 @@ namespace osu.Game.Rulesets.UI.Scrolling // Generate the timing points, making non-timing changes use the previous timing change and vice-versa var timingChanges = allPoints.Select(c => { - if (c is TimingControlPoint timingPoint) - lastTimingPoint = timingPoint; - else if (c is EffectControlPoint difficultyPoint) - lastEffectPoint = difficultyPoint; + switch (c) + { + case TimingControlPoint timingPoint: + lastTimingPoint = timingPoint; + break; + + case EffectControlPoint difficultyPoint: + lastEffectPoint = difficultyPoint; + break; + } return new MultiplierControlPoint(c.Time) { diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 6d23b52c05..c8944d0357 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterfaceV2; @@ -13,13 +14,20 @@ namespace osu.Game.Screens.Edit.Timing private LabelledSwitchButton kiai; private LabelledSwitchButton omitBarLine; + private SliderWithTextBoxInput scrollSpeedSlider; + [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { kiai = new LabelledSwitchButton { Label = "Kiai Time" }, omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" }, + scrollSpeedSlider = new SliderWithTextBoxInput("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.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 { KiaiMode = reference.KiaiMode, - OmitFirstBarLine = reference.OmitFirstBarLine + OmitFirstBarLine = reference.OmitFirstBarLine, + ScrollSpeed = reference.ScrollSpeed, }; } } diff --git a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs index 812407d6da..4a01b2d861 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttributes/EffectRowAttribute.cs @@ -12,6 +12,8 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { private readonly Bindable kiaiMode; private readonly Bindable omitBarLine; + private readonly BindableNumber scrollSpeed; + private AttributeText kiaiModeBubble; private AttributeText omitBarLineBubble; @@ -20,6 +22,7 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { kiaiMode = effect.KiaiModeBindable.GetBoundCopy(); omitBarLine = effect.OmitFirstBarLineBindable.GetBoundCopy(); + scrollSpeed = effect.ScrollSpeedBindable.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -27,6 +30,10 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes { Content.AddRange(new Drawable[] { + new AttributeProgressBar(Point) + { + Current = scrollSpeed, + }, kiaiModeBubble = new AttributeText(Point) { Text = "kiai" }, omitBarLineBubble = new AttributeText(Point) { Text = "no barline" }, });