diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 3eb8d6c018..a61478f5d5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -10,10 +10,12 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -54,6 +56,12 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints [Resolved] private EditorBeatmap? editorBeatmap { get; set; } + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private BindableBeatDivisor? beatDivisor { get; set; } + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { @@ -119,6 +127,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints return base.OnMouseDown(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsSelected) + return false; + + if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) + { + convertToStream(); + return true; + } + + return false; + } + private void onDefaultsApplied(HitObject _) { computeObjectBounds(); @@ -168,6 +190,48 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } + private void convertToStream() + { + if (editorBeatmap == null || beatDivisor == null) + return; + + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; + + changeHandler?.BeginChange(); + + int i = 0; + double time = HitObject.StartTime; + + while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) + { + // positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()] + // and indicates how many fractional spans of a slider have passed up to time. + double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); + double pathPosition = positionWithRepeats - (int)positionWithRepeats; + // every second span is in the reverse direction - need to reverse the path position. + if (positionWithRepeats % 2 >= 1) + pathPosition = 1 - pathPosition; + + float fruitXValue = HitObject.OriginalX + HitObject.Path.PositionAt(pathPosition).X; + + editorBeatmap.Add(new Fruit + { + StartTime = time, + OriginalX = fruitXValue, + NewCombo = i == 0 && HitObject.NewCombo, + Samples = HitObject.Samples.Select(s => s.With()).ToList() + }); + + i += 1; + time = HitObject.StartTime + i * streamSpacing; + } + + editorBeatmap.Remove(HitObject); + + changeHandler?.EndChange(); + } + private IEnumerable getContextMenuItems() { yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () => @@ -177,6 +241,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) }; + + yield return new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) + }; } protected override void Dispose(bool isDisposing)