예제 #1
0
        private string Complete_Sliders(SliderCompletionatorVm arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot(out var editorReaderException1);

            if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException1 != null)
            {
                throw new Exception("Could not fetch selected hit objects.", editorReaderException1);
            }

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader, out var selected, out var editorReaderException2);

                if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException2 != null)
                {
                    throw new Exception("Could not fetch selected hit objects.", editorReaderException2);
                }

                Beatmap          beatmap       = editor.Beatmap;
                Timing           timing        = beatmap.BeatmapTiming;
                List <HitObject> markedObjects = arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected ? selected :
                                                 arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Bookmarked ? beatmap.GetBookmarkedObjects() :
                                                 arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Time ? beatmap.QueryTimeCode(arg.TimeCode).ToList() :
                                                 beatmap.HitObjects;

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double oldSpatialLength = ho.PixelLength;
                        double newSpatialLength = arg.SpatialLength != -1 ? ho.GetSliderPath(fullLength: true).Distance *arg.SpatialLength : oldSpatialLength;

                        double oldTemporalLength = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double newTemporalLength = arg.TemporalLength != -1 ? timing.GetMpBAtTime(ho.Time) * arg.TemporalLength : oldTemporalLength;

                        double oldSv = timing.GetSvAtTime(ho.Time);
                        double newSv = oldSv / ((newSpatialLength / oldSpatialLength) / (newTemporalLength / oldTemporalLength));

                        if (double.IsNaN(newSv))
                        {
                            throw new Exception("Encountered NaN slider velocity. Make sure none of the inputs are zero.");
                        }

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newSpatialLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    if (ho.IsSlider) // SliderVelocity changes
                    {
                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            if (arg.Quick)
            {
                RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, reader != null));
            }

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
예제 #2
0
        private void UpdatePointsOfInterest()
        {
            if ((ViewModel.ShowRedAnchors || ViewModel.ShowGraphAnchors) && ViewModel.VisibleHitObject != null && ViewModel.VisibleHitObject.IsSlider)
            {
                var sliderPath       = ViewModel.VisibleHitObject.GetSliderPath();
                var maxCompletion    = GetMaxCompletion();
                var hitObjectMarkers = new ObservableCollection <HitObjectElementMarker>();

                if (ViewModel.ShowRedAnchors)
                {
                    var redAnchorCompletions = SliderPathUtil.GetRedAnchorCompletions(sliderPath).ToArray();

                    // Add red anchors to hit object preview
                    foreach (var completion in redAnchorCompletions)
                    {
                        hitObjectMarkers.Add(new HitObjectElementMarker(completion / maxCompletion, 0.2, Brushes.Red));
                    }

                    // Add red anchors to graph
                    if (ViewModel.GraphModeSetting == SlideratorVm.GraphMode.Position)
                    {
                        var markers = new ObservableCollection <GraphMarker>();

                        foreach (var completion in redAnchorCompletions)
                        {
                            markers.Add(new GraphMarker {
                                Orientation     = Orientation.Horizontal, Value = completion,
                                CustomLineBrush = Brushes.Red, Text = null
                            });
                        }

                        Graph.ExtraMarkers = markers;
                    }
                    else
                    {
                        Graph.ExtraMarkers.Clear();
                    }
                }
                if (ViewModel.ShowGraphAnchors)
                {
                    // Add graph anchors to hit objects preview
                    var graphAnchorCompletions = ViewModel.GraphModeSetting == SlideratorVm.GraphMode.Velocity
                        ? Graph.Anchors.Select(a => Graph.Anchors.GetIntegral(0, a.Pos.X) * ViewModel.SvGraphMultiplier)
                        : Graph.Anchors.Select(a => a.Pos.Y);

                    foreach (var completion in graphAnchorCompletions)
                    {
                        hitObjectMarkers.Add(new HitObjectElementMarker(completion / maxCompletion, 0.2, Brushes.DodgerBlue));
                    }
                }

                if (ViewModel.PixelLength < HitObjectElement.MaxPixelLength)
                {
                    GraphHitObjectElement.ExtraMarkers = hitObjectMarkers;
                }
            }
            else
            {
                GraphHitObjectElement.ExtraMarkers.Clear();
                Graph.ExtraMarkers.Clear();
            }
        }
        private string Complete_Sliders(Arguments arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            bool editorRead = EditorReaderStuff.TryGetFullEditorReader(out var reader);

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetBeatmapEditor(path, reader, editorRead, out var selected, out var editorActuallyRead);

                if (arg.SelectionMode == 0 && !editorActuallyRead)
                {
                    return(EditorReaderStuff.SelectedObjectsReadFailText);
                }

                Beatmap          beatmap       = editor.Beatmap;
                Timing           timing        = beatmap.BeatmapTiming;
                List <HitObject> markedObjects = arg.SelectionMode == 0 ? selected :
                                                 arg.SelectionMode == 1 ? beatmap.GetBookmarkedObjects() :
                                                 beatmap.HitObjects;

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double oldSpatialLength = ho.PixelLength;
                        double newSpatialLength = arg.SpatialLength != -1 ? ho.GetSliderPath(fullLength: true).Distance *arg.SpatialLength : oldSpatialLength;

                        double oldTemporalLength = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double newTemporalLength = arg.TemporalLength != -1 ? timing.GetMpBAtTime(ho.Time) * arg.TemporalLength : oldTemporalLength;

                        double oldSv = timing.GetSvAtTime(ho.Time);
                        double newSv = oldSv / ((newSpatialLength / oldSpatialLength) / (newTemporalLength / oldTemporalLength));

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newSpatialLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    if (ho.IsSlider) // SliderVelocity changes
                    {
                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            if (arg.Quick)
            {
                RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, editorRead));
            }

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
예제 #4
0
        private string Complete_Sliders(SliderCompletionatorVm arg, BackgroundWorker worker, DoWorkEventArgs _)
        {
            int slidersCompleted = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot(out var editorReaderException1);

            if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException1 != null)
            {
                throw new Exception("Could not fetch selected hit objects.", editorReaderException1);
            }

            foreach (string path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader, out var selected, out var editorReaderException2);

                if (arg.ImportModeSetting == SliderCompletionatorVm.ImportMode.Selected && editorReaderException2 != null)
                {
                    throw new Exception("Could not fetch selected hit objects.", editorReaderException2);
                }

                Beatmap beatmap = editor.Beatmap;
                Timing  timing  = beatmap.BeatmapTiming;

                List <HitObject> markedObjects = arg.ImportModeSetting switch {
                    SliderCompletionatorVm.ImportMode.Selected => selected,
                    SliderCompletionatorVm.ImportMode.Bookmarked => beatmap.GetBookmarkedObjects(),
                    SliderCompletionatorVm.ImportMode.Time => beatmap.QueryTimeCode(arg.TimeCode).ToList(),
                    SliderCompletionatorVm.ImportMode.Everything => beatmap.HitObjects,
                    _ => throw new ArgumentException("Unexpected import mode.")
                };

                for (int i = 0; i < markedObjects.Count; i++)
                {
                    HitObject ho = markedObjects[i];
                    if (ho.IsSlider)
                    {
                        double mpb = timing.GetMpBAtTime(ho.Time);

                        double oldDuration = timing.CalculateSliderTemporalLength(ho.Time, ho.PixelLength);
                        double oldLength   = ho.PixelLength;
                        double oldSv       = timing.GetSvAtTime(ho.Time);

                        double newDuration = arg.UseEndTime ? arg.EndTime == -1 ? oldDuration : arg.EndTime - ho.Time :
                                             arg.Duration == -1 ? oldDuration : timing.WalkBeatsInMillisecondTime(arg.Duration, ho.Time) - ho.Time;
                        double newLength = arg.Length == -1 ? oldLength : ho.GetSliderPath(fullLength: true).Distance *arg.Length;
                        double newSv     = arg.SliderVelocity == -1 ? oldSv : -100 / arg.SliderVelocity;

                        switch (arg.FreeVariableSetting)
                        {
                        case SliderCompletionatorVm.FreeVariable.Velocity:
                            newSv = -10000 * timing.SliderMultiplier * newDuration / (newLength * mpb);
                            break;

                        case SliderCompletionatorVm.FreeVariable.Duration:
                            // This actually doesn't get used anymore because the .osu doesn't store the duration
                            newDuration = newLength * newSv * mpb / (-10000 * timing.SliderMultiplier);
                            break;

                        case SliderCompletionatorVm.FreeVariable.Length:
                            newLength = -10000 * timing.SliderMultiplier * newDuration / (newSv * mpb);
                            break;

                        default:
                            throw new ArgumentException("Unexpected free variable setting.");
                        }

                        if (double.IsNaN(newSv))
                        {
                            throw new Exception("Encountered NaN slider velocity. Make sure none of the inputs are zero.");
                        }

                        if (newDuration < 0)
                        {
                            throw new Exception("Encountered slider with negative duration. Make sure the end time is greater than the end time of all selected sliders.");
                        }

                        ho.SliderVelocity = newSv;
                        ho.PixelLength    = newLength;

                        // Scale anchors to completion
                        if (arg.MoveAnchors)
                        {
                            ho.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                     ho.GetAllCurvePoints(), ho.SliderType, ho.PixelLength, out var pathType));
                            ho.SliderType = pathType;
                        }

                        slidersCompleted++;
                    }
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }
                }

                // Reconstruct SliderVelocity
                List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>();
                // Add Hitobject stuff
                foreach (HitObject ho in beatmap.HitObjects)
                {
                    // SliderVelocity changes
                    if (ho.IsSlider)
                    {
                        if (markedObjects.Contains(ho) && arg.DelegateToBpm)
                        {
                            var tpAfter = timing.GetRedlineAtTime(ho.Time).Copy();
                            var tpOn    = tpAfter.Copy();

                            tpAfter.Offset = ho.Time;
                            tpOn.Offset    = ho.Time - 1; // This one will be on the slider

                            tpAfter.OmitFirstBarLine = true;
                            tpOn.OmitFirstBarLine    = true;

                            // Express velocity in BPM
                            tpOn.MpB *= ho.SliderVelocity / -100;
                            // NaN SV results in removal of slider ticks
                            ho.SliderVelocity = arg.RemoveSliderTicks ? double.NaN : -100;

                            // Add redlines
                            timingPointsChanges.Add(new TimingPointsChange(tpOn, mpb: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON));
                            timingPointsChanges.Add(new TimingPointsChange(tpAfter, mpb: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON));

                            ho.Time -= 1;
                        }

                        TimingPoint tp = ho.TimingPoint.Copy();
                        tp.Offset = ho.Time;
                        tp.MpB    = ho.SliderVelocity;
                        timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, fuzzyness: Precision.DOUBLE_EPSILON));
                    }
                }

                // Add the new SliderVelocity changes
                TimingPointsChange.ApplyChanges(timing, timingPointsChanges);

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, reader != null, arg.Quick));

            // Make an accurate message
            string message = "";

            if (Math.Abs(slidersCompleted) == 1)
            {
                message += "Successfully completed " + slidersCompleted + " slider!";
            }
            else
            {
                message += "Successfully completed " + slidersCompleted + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }
예제 #5
0
        private string Merge_Sliders(SliderMergerVm arg, BackgroundWorker worker)
        {
            var slidersMerged = 0;

            var reader = EditorReaderStuff.GetFullEditorReaderOrNot(out var editorReaderException1);

            if (arg.ImportModeSetting == 0 && editorReaderException1 != null)
            {
                throw new Exception("Could not fetch selected hit objects.", editorReaderException1);
            }

            foreach (var path in arg.Paths)
            {
                var editor = EditorReaderStuff.GetNewestVersionOrNot(path, reader, out var selected, out var editorReaderException2);

                if (arg.ImportModeSetting == SliderMergerVm.ImportMode.Selected && editorReaderException2 != null)
                {
                    throw new Exception("Could not fetch selected hit objects.", editorReaderException2);
                }

                var beatmap       = editor.Beatmap;
                var markedObjects = arg.ImportModeSetting == 0 ? selected :
                                    arg.ImportModeSetting == SliderMergerVm.ImportMode.Bookmarked ? beatmap.GetBookmarkedObjects() :
                                    arg.ImportModeSetting == SliderMergerVm.ImportMode.Time ? beatmap.QueryTimeCode(arg.TimeCode).ToList() :
                                    beatmap.HitObjects;

                var mergeLast = false;
                for (var i = 0; i < markedObjects.Count - 1; i++)
                {
                    if (worker != null && worker.WorkerReportsProgress)
                    {
                        worker.ReportProgress(i / markedObjects.Count);
                    }

                    var ho1 = markedObjects[i];
                    var ho2 = markedObjects[i + 1];

                    var lastPos1 = ho1.IsSlider
                        ? arg.MergeOnSliderEnd ? ho1.GetSliderPath().PositionAt(1) : ho1.CurvePoints.Last()
                        : ho1.Pos;

                    double dist = Vector2.Distance(lastPos1, ho2.Pos);

                    if (dist > arg.Leniency)
                    {
                        mergeLast = false;
                        continue;
                    }

                    if (ho1.IsSlider && ho2.IsSlider)
                    {
                        if (arg.MergeOnSliderEnd)
                        {
                            // In order to merge on the slider end we first move the anchors such that the last anchor is exactly on the slider end
                            // After that merge as usual
                            ho1.SetAllCurvePoints(SliderPathUtil.MoveAnchorsToLength(
                                                      ho1.GetAllCurvePoints(), ho1.SliderType, ho1.PixelLength, out var pathType));
                            ho1.SliderType = pathType;
                        }

                        var sp1 = BezierConverter.ConvertToBezier(ho1.SliderPath).ControlPoints;
                        var sp2 = BezierConverter.ConvertToBezier(ho2.SliderPath).ControlPoints;

                        double extraLength = 0;
                        switch (arg.ConnectionModeSetting)
                        {
                        case SliderMergerVm.ConnectionMode.Move:
                            Move(sp2, sp1.Last() - sp2.First());
                            break;

                        case SliderMergerVm.ConnectionMode.Linear:
                            sp1.Add(sp1.Last());
                            sp1.Add(sp2.First());
                            extraLength = (ho1.CurvePoints.Last() - ho2.Pos).Length;
                            break;
                        }

                        var mergedAnchors = sp1.Concat(sp2).ToList();
                        mergedAnchors.Round();

                        var linearLinear = arg.LinearOnLinear && IsLinearBezier(sp1) && IsLinearBezier(sp2);
                        if (linearLinear)
                        {
                            for (var j = 0; j < mergedAnchors.Count - 1; j++)
                            {
                                if (mergedAnchors[j] != mergedAnchors[j + 1])
                                {
                                    continue;
                                }
                                mergedAnchors.RemoveAt(j);
                                j--;
                            }
                        }

                        var mergedPath = new SliderPath(linearLinear ? PathType.Linear : PathType.Bezier, mergedAnchors.ToArray(),
                                                        ho1.PixelLength + ho2.PixelLength + extraLength);
                        ho1.SliderPath = mergedPath;

                        beatmap.HitObjects.Remove(ho2);
                        markedObjects.Remove(ho2);
                        i--;

                        slidersMerged++;
                        if (!mergeLast)
                        {
                            slidersMerged++;
                        }
                        mergeLast = true;
                    }
                    else if (ho1.IsSlider && ho2.IsCircle)
                    {
                        var sp1 = BezierConverter.ConvertToBezier(ho1.SliderPath).ControlPoints;

                        sp1.Add(sp1.Last());
                        sp1.Add(ho2.Pos);
                        var extraLength = (ho1.CurvePoints.Last() - ho2.Pos).Length;

                        var mergedAnchors = sp1;
                        mergedAnchors.Round();

                        var linearLinear = arg.LinearOnLinear && IsLinearBezier(sp1);
                        if (linearLinear)
                        {
                            for (var j = 0; j < mergedAnchors.Count - 1; j++)
                            {
                                if (mergedAnchors[j] != mergedAnchors[j + 1])
                                {
                                    continue;
                                }
                                mergedAnchors.RemoveAt(j);
                                j--;
                            }
                        }

                        var mergedPath = new SliderPath(linearLinear ? PathType.Linear : PathType.Bezier, mergedAnchors.ToArray(), ho1.PixelLength + extraLength);
                        ho1.SliderPath = mergedPath;

                        beatmap.HitObjects.Remove(ho2);
                        markedObjects.Remove(ho2);
                        i--;

                        slidersMerged++;
                        if (!mergeLast)
                        {
                            slidersMerged++;
                        }
                        mergeLast = true;
                    }
                    else if (ho1.IsCircle && ho2.IsSlider)
                    {
                        var sp2 = BezierConverter.ConvertToBezier(ho2.SliderPath).ControlPoints;

                        sp2.Insert(0, sp2.First());
                        sp2.Insert(0, ho1.Pos);
                        var extraLength = (ho1.Pos - ho2.Pos).Length;

                        var mergedAnchors = sp2;
                        mergedAnchors.Round();

                        var linearLinear = arg.LinearOnLinear && IsLinearBezier(sp2);
                        if (linearLinear)
                        {
                            for (var j = 0; j < mergedAnchors.Count - 1; j++)
                            {
                                if (mergedAnchors[j] != mergedAnchors[j + 1])
                                {
                                    continue;
                                }
                                mergedAnchors.RemoveAt(j);
                                j--;
                            }
                        }

                        var mergedPath = new SliderPath(linearLinear ? PathType.Linear : PathType.Bezier, mergedAnchors.ToArray(), ho2.PixelLength + extraLength);
                        ho2.SliderPath = mergedPath;

                        beatmap.HitObjects.Remove(ho1);
                        markedObjects.Remove(ho1);
                        i--;

                        slidersMerged++;
                        if (!mergeLast)
                        {
                            slidersMerged++;
                        }
                        mergeLast = true;
                    }
                    else if (ho1.IsCircle && ho2.IsCircle)
                    {
                        var mergedAnchors = new List <Vector2> {
                            ho1.Pos, ho2.Pos
                        };

                        var mergedPath = new SliderPath(arg.LinearOnLinear ? PathType.Linear : PathType.Bezier, mergedAnchors.ToArray(), (ho1.Pos - ho2.Pos).Length);
                        ho1.SliderPath    = mergedPath;
                        ho1.IsCircle      = false;
                        ho1.IsSlider      = true;
                        ho1.Repeat        = 1;
                        ho1.EdgeHitsounds = new List <int> {
                            ho1.GetHitsounds(), ho2.GetHitsounds()
                        };
                        ho1.EdgeSampleSets = new List <SampleSet> {
                            ho1.SampleSet, ho2.SampleSet
                        };
                        ho1.EdgeAdditionSets = new List <SampleSet> {
                            ho1.AdditionSet, ho2.AdditionSet
                        };

                        beatmap.HitObjects.Remove(ho2);
                        markedObjects.Remove(ho2);
                        i--;

                        slidersMerged++;
                        if (!mergeLast)
                        {
                            slidersMerged++;
                        }
                        mergeLast = true;
                    }
                    else
                    {
                        mergeLast = false;
                    }
                }

                // Save the file
                editor.SaveFile();
            }

            // Complete progressbar
            if (worker != null && worker.WorkerReportsProgress)
            {
                worker.ReportProgress(100);
            }

            // Do stuff
            if (arg.Quick)
            {
                RunFinished?.Invoke(this, new RunToolCompletedEventArgs(true, reader != null));
            }

            // Make an accurate message
            var message = "";

            if (Math.Abs(slidersMerged) == 1)
            {
                message += "Successfully merged " + slidersMerged + " slider!";
            }
            else
            {
                message += "Successfully merged " + slidersMerged + " sliders!";
            }
            return(arg.Quick ? "" : message);
        }