private List <Marker> GetMarkers(Beatmap beatmap, Timing timing) { List <Marker> markers = new List <Marker>(); List <TimingPoint> redlines = timing.GetAllRedlines(); foreach (HitObject ho in beatmap.HitObjects) { markers.Add(new Marker(ho)); } foreach (double bookmark in beatmap.GetBookmarks()) { markers.Add(new Marker(bookmark)); } foreach (TimingPoint greenline in timing.TimingPoints) { markers.Add(new Marker(greenline)); } // Sort the markers markers = markers.OrderBy(o => o.Time).ToList(); // Calculate the beats between this marker and the last marker // If there is a redline in between then calculate beats from last marker to the redline and beats from redline to this marker // Time the same is 0 double lastTime = redlines.First().Offset; foreach (Marker marker in markers) { // Get redlines between this and last marker List <TimingPoint> redlinesBetween = redlines.Where(o => o.Offset <marker.Time && o.Offset> lastTime).ToList(); TimingPoint redline = timing.GetRedlineAtTime(lastTime); double beatsFromLastMarker = 0; foreach (TimingPoint redlineBetween in redlinesBetween) { beatsFromLastMarker += (redlineBetween.Offset - lastTime) / redline.MpB; redline = redlineBetween; lastTime = redlineBetween.Offset; } beatsFromLastMarker += (marker.Time - lastTime) / redline.MpB; // Set the variable marker.BeatsFromLastMarker = beatsFromLastMarker; lastTime = marker.Time; } return(markers); }
private string Copy_Timing(TimingCopierVm arg, BackgroundWorker worker, DoWorkEventArgs _) { string[] paths = arg.ExportPath.Split('|'); int mapsDone = 0; var reader = EditorReaderStuff.GetFullEditorReaderOrNot(); foreach (string exportPath in paths) { var editorTo = EditorReaderStuff.GetNewestVersionOrNot(exportPath, reader); var editorFrom = EditorReaderStuff.GetNewestVersionOrNot(arg.ImportPath, reader); Beatmap beatmapTo = editorTo.Beatmap; Beatmap beatmapFrom = editorFrom.Beatmap; Timing timingTo = beatmapTo.BeatmapTiming; Timing timingFrom = beatmapFrom.BeatmapTiming; // Get markers for hitobjects if mode 1 is used List <Marker> markers = new List <Marker>(); if (arg.ResnapMode == "Number of beats between objects stays the same") { markers = GetMarkers(beatmapTo, timingTo); } // Rid the beatmap of redlines // If a greenline exists at the same time as a redline then the redline ceizes to exist // Else convert the redline to a greenline: Inherited = false & MpB = -100 List <TimingPoint> removeList = new List <TimingPoint>(); foreach (TimingPoint redline in timingTo.GetAllRedlines()) { TimingPoint greenlineHere = timingTo.GetGreenlineAtTime(redline.Offset); if (greenlineHere.Offset == redline.Offset) { removeList.Add(redline); } else { redline.Uninherited = false; redline.MpB = -100; } } foreach (TimingPoint tp in removeList) { timingTo.TimingPoints.Remove(tp); } // Make new timing points changes List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>(); // Add redlines List <TimingPoint> redlines = timingFrom.GetAllRedlines(); foreach (TimingPoint tp in redlines) { timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, inherited: true, omitFirstBarLine: true)); } // Apply timing changes TimingPointsChange.ApplyChanges(timingTo, timingPointsChanges); if (arg.ResnapMode == "Number of beats between objects stays the same") { redlines = timingTo.GetAllRedlines(); List <double> newBookmarks = new List <double>(); double lastTime = redlines.FirstOrDefault().Offset; foreach (Marker marker in markers) { // Get redlines between this and last marker TimingPoint redline = timingTo.GetRedlineAtTime(lastTime, redlines.FirstOrDefault()); double beatsFromLastTime = marker.BeatsFromLastMarker; while (true) { List <TimingPoint> redlinesBetween = redlines.Where(o => o.Offset <= lastTime + redline.MpB * beatsFromLastTime && o.Offset > lastTime).ToList(); if (redlinesBetween.Count == 0) { break; } TimingPoint first = redlinesBetween.First(); double diff = first.Offset - lastTime; beatsFromLastTime -= diff / redline.MpB; redline = first; lastTime = first.Offset; } // Last time is the time of the last redline in between double newTime = lastTime + redline.MpB * beatsFromLastTime; newTime = timingTo.Resnap(newTime, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault()); marker.Time = newTime; lastTime = marker.Time; } // Add the bookmarks foreach (Marker marker in markers) { // Check whether the marker is a bookmark if (marker.Object is double) { // Don't resnap bookmarks newBookmarks.Add((double)marker.Object); } } beatmapTo.SetBookmarks(newBookmarks); } else if (arg.ResnapMode == "Just resnap") { // Resnap hitobjects foreach (HitObject ho in beatmapTo.HitObjects) { ho.ResnapSelf(timingTo, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault()); ho.ResnapEnd(timingTo, arg.Snap1, arg.Snap2, firstTp: redlines.FirstOrDefault()); } // Resnap greenlines foreach (TimingPoint tp in timingTo.GetAllGreenlines()) { tp.ResnapSelf(timingTo, arg.Snap1, arg.Snap2, firstTP: redlines.FirstOrDefault()); } } else { // Don't move objects } // Save the file editorTo.SaveFile(); // Update progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(++mapsDone * 100 / paths.Length); } } // Make an accurate message string message = $"Successfully copied timing to {mapsDone} {(mapsDone == 1 ? "beatmap" : "beatmaps")}!"; return(message); }
/// <summary> /// Cleans a map. /// </summary> /// <param name="editor">The editor of the beatmap that is going to be cleaned.</param> /// <param name="args">The arguments for how to clean the beatmap.</param> /// <param name="worker">The BackgroundWorker for updating progress.</param> /// <returns>Number of resnapped objects.</returns> public static MapCleanerResult CleanMap(BeatmapEditor editor, MapCleanerArgs args, BackgroundWorker worker = null) { UpdateProgressBar(worker, 0); Beatmap beatmap = editor.Beatmap; Timing timing = beatmap.BeatmapTiming; Timeline timeline = beatmap.GetTimeline(); GameMode mode = (GameMode)beatmap.General["Mode"].IntValue; double circleSize = beatmap.Difficulty["CircleSize"].DoubleValue; string mapDir = editor.GetParentFolder(); Dictionary <string, string> firstSamples = HitsoundImporter.AnalyzeSamples(mapDir); int objectsResnapped = 0; int samplesRemoved = 0; // Collect Kiai toggles and SliderVelocity changes for mania/taiko List <TimingPoint> kiaiToggles = new List <TimingPoint>(); List <TimingPoint> svChanges = new List <TimingPoint>(); bool lastKiai = false; double lastSV = -100; foreach (TimingPoint tp in timing.TimingPoints) { if (tp.Kiai != lastKiai) { kiaiToggles.Add(tp.Copy()); lastKiai = tp.Kiai; } if (tp.Uninherited) { lastSV = -100; } else { if (tp.MpB != lastSV) { svChanges.Add(tp.Copy()); lastSV = tp.MpB; } } } UpdateProgressBar(worker, 9); // Resnap shit if (args.ResnapObjects) { // Resnap all objects foreach (HitObject ho in beatmap.HitObjects) { bool resnapped = ho.ResnapSelf(timing, args.Snap1, args.Snap2); if (resnapped) { objectsResnapped += 1; } ho.ResnapEnd(timing, args.Snap1, args.Snap2); ho.ResnapPosition(mode, circleSize); } UpdateProgressBar(worker, 18); // Resnap Kiai toggles foreach (TimingPoint tp in kiaiToggles) { tp.ResnapSelf(timing, args.Snap1, args.Snap2); } UpdateProgressBar(worker, 27); // Resnap SliderVelocity changes foreach (TimingPoint tp in svChanges) { tp.ResnapSelf(timing, args.Snap1, args.Snap2); } UpdateProgressBar(worker, 36); } if (args.ResnapBookmarks) { // Resnap the bookmarks List <double> bookmarks = beatmap.GetBookmarks(); List <double> newBookmarks = bookmarks.Select(o => timing.Resnap(o, args.Snap1, args.Snap2)).ToList(); // Remove duplicate bookmarks newBookmarks = newBookmarks.Distinct().ToList(); beatmap.SetBookmarks(newBookmarks); UpdateProgressBar(worker, 45); } // Make new timingpoints List <TimingPointsChange> timingPointsChanges = new List <TimingPointsChange>(); // Add redlines List <TimingPoint> redlines = timing.GetAllRedlines(); foreach (TimingPoint tp in redlines) { timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true)); } UpdateProgressBar(worker, 55); // Add SliderVelocity changes for taiko and mania if (mode == GameMode.Taiko || mode == GameMode.Mania) { foreach (TimingPoint tp in svChanges) { timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true)); } } UpdateProgressBar(worker, 60); // Add Kiai toggles foreach (TimingPoint tp in kiaiToggles) { timingPointsChanges.Add(new TimingPointsChange(tp, kiai: true)); } UpdateProgressBar(worker, 65); // 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)); } // Body hitsounds bool vol = (ho.IsSlider && args.VolumeSliders) || (ho.IsSpinner && args.VolumeSpinners); bool sam = (ho.IsSlider && args.SampleSetSliders && ho.SampleSet == 0); bool ind = (ho.IsSlider && args.SampleSetSliders); bool samplesetActuallyChanged = false; foreach (TimingPoint tp in ho.BodyHitsounds) { if (tp.Volume == 5 && args.RemoveMuting) { vol = false; // Removing sliderbody silencing ind = false; // Removing silent custom index } timingPointsChanges.Add(new TimingPointsChange(tp, volume: vol, index: ind, sampleset: sam)); if (tp.SampleSet != ho.HitsoundTimingPoint.SampleSet) { samplesetActuallyChanged = args.SampleSetSliders && ho.SampleSet == 0; } // True for sampleset change in sliderbody } if (ho.IsSlider && (!samplesetActuallyChanged) && ho.SampleSet == 0) // Case can put sampleset on sliderbody { ho.SampleSet = ho.HitsoundTimingPoint.SampleSet; } if (ho.IsSlider && samplesetActuallyChanged) // Make it start out with the right sampleset { TimingPoint tp = ho.HitsoundTimingPoint.Copy(); tp.Offset = ho.Time; timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true)); } } UpdateProgressBar(worker, 75); // Add timeline hitsounds foreach (TimelineObject tlo in timeline.TimelineObjects) { // Change the samplesets in the hitobjects if (tlo.Origin.IsCircle) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; if (mode == GameMode.Mania) { tlo.Origin.CustomIndex = tlo.FenoCustomIndex; tlo.Origin.SampleVolume = tlo.FenoSampleVolume; } } else if (tlo.Origin.IsSlider) { tlo.Origin.EdgeHitsounds[tlo.Repeat] = tlo.GetHitsounds(); tlo.Origin.EdgeSampleSets[tlo.Repeat] = tlo.FenoSampleSet; tlo.Origin.EdgeAdditionSets[tlo.Repeat] = tlo.FenoAdditionSet; if (tlo.Origin.EdgeAdditionSets[tlo.Repeat] == tlo.Origin.EdgeSampleSets[tlo.Repeat]) // Simplify additions to auto { tlo.Origin.EdgeAdditionSets[tlo.Repeat] = 0; } } else if (tlo.Origin.IsSpinner) { if (tlo.Repeat == 1) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; } } else if (tlo.Origin.IsHoldNote) { if (tlo.Repeat == 0) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; tlo.Origin.CustomIndex = tlo.FenoCustomIndex; tlo.Origin.SampleVolume = tlo.FenoSampleVolume; } } if (tlo.Origin.AdditionSet == tlo.Origin.SampleSet) // Simplify additions to auto { tlo.Origin.AdditionSet = 0; } if (tlo.HasHitsound) // Add greenlines for custom indexes and volumes { TimingPoint tp = tlo.HitsoundTimingPoint.Copy(); bool doUnmute = tlo.FenoSampleVolume == 5 && args.RemoveMuting; bool doMute = args.RemoveUnclickableHitsounds && !args.RemoveMuting && !(tlo.IsCircle || tlo.IsSliderHead || tlo.IsHoldnoteHead); bool ind = !tlo.UsesFilename && !doUnmute; // Index doesnt have to change if custom is overridden by Filename bool vol = !doUnmute; // Remove volume change muted // Index doesn't have to change if the sample it plays currently is the same as the sample it would play with the previous index if (ind) { List <string> nativeSamples = tlo.GetFirstPlayingFilenames(mode, mapDir, firstSamples); int oldIndex = tlo.FenoCustomIndex; int newIndex = tlo.FenoCustomIndex; double latest = double.NegativeInfinity; foreach (TimingPointsChange tpc in timingPointsChanges) { if (tpc.Index && tpc.MyTP.Offset <= tlo.Time && tpc.MyTP.Offset >= latest) { newIndex = tpc.MyTP.SampleIndex; latest = tpc.MyTP.Offset; } } tp.SampleIndex = newIndex; tlo.GiveHitsoundTimingPoint(tp); List <string> newSamples = tlo.GetFirstPlayingFilenames(mode, mapDir, firstSamples); if (nativeSamples.SequenceEqual(newSamples)) { // Index changes dont change sound tp.SampleIndex = newIndex; } else { tp.SampleIndex = oldIndex; } tlo.GiveHitsoundTimingPoint(tp); } tp.Offset = tlo.Time; tp.SampleIndex = tlo.FenoCustomIndex; tp.Volume = doMute ? 5 : tlo.FenoSampleVolume; timingPointsChanges.Add(new TimingPointsChange(tp, volume: vol, index: ind)); } } UpdateProgressBar(worker, 85); // Replace the old timingpoints timing.TimingPoints.Clear(); TimingPointsChange.ApplyChanges(timing, timingPointsChanges); beatmap.GiveObjectsGreenlines(); UpdateProgressBar(worker, 90); // Remove unused samples if (args.RemoveUnusedSamples) { RemoveUnusedSamples(mapDir); } // Complete progressbar UpdateProgressBar(worker, 100); return(new MapCleanerResult(objectsResnapped, samplesRemoved)); }
private string Run_Program2(List <string> arguments, BackgroundWorker worker, DoWorkEventArgs e) { // Retrieve all arguments string path = arguments[0]; bool volumeSliders = arguments[1] == "True"; bool samplesetSliders = arguments[2] == "True"; bool volumeSpinners = arguments[3] == "True"; bool resnapObjects = arguments[4] == "True"; bool resnapBookmarks = arguments[5] == "True"; int snap1 = int.Parse(arguments[6].Split('/')[1]); int snap2 = int.Parse(arguments[7].Split('/')[1]); bool removeSliderendMuting = arguments[8] == "True"; bool removeUnclickabeHitsounds = arguments[9] == "True"; Editor editor = new Editor(path); Timing timing = editor.Beatmap.BeatmapTiming; Timeline timeline = editor.Beatmap.GetTimeline(); int mode = editor.Beatmap.General["Mode"].Value; int num_timingPoints = editor.Beatmap.BeatmapTiming.TimingPoints.Count; int objectsResnapped = 0; // Count total stages int maxStages = 11; // Collect Kiai toggles and SV changes for mania/taiko List <TimingPoint> kiaiToggles = new List <TimingPoint>(); List <TimingPoint> svChanges = new List <TimingPoint>(); bool lastKiai = false; double lastSV = -100; for (int i = 0; i < timing.TimingPoints.Count; i++) { TimingPoint tp = timing.TimingPoints[i]; if (tp.Kiai != lastKiai) { kiaiToggles.Add(tp.Copy()); lastKiai = tp.Kiai; } if (tp.Inherited) { lastSV = -100; } else { if (tp.MpB != lastSV) { svChanges.Add(tp.Copy()); lastSV = tp.MpB; } } UpdateProgressbar(worker, (double)i / timing.TimingPoints.Count, 0, maxStages); } // Resnap shit if (resnapObjects) { // Resnap all objects for (int i = 0; i < editor.Beatmap.HitObjects.Count; i++) { HitObject ho = editor.Beatmap.HitObjects[i]; bool resnapped = ho.ResnapSelf(timing, snap1, snap2); if (resnapped) { objectsResnapped += 1; } ho.ResnapEnd(timing, snap1, snap2); UpdateProgressbar(worker, (double)i / editor.Beatmap.HitObjects.Count, 1, maxStages); } // Resnap Kiai toggles and SV changes for (int i = 0; i < kiaiToggles.Count; i++) { TimingPoint tp = kiaiToggles[i]; tp.ResnapSelf(timing, snap1, snap2); UpdateProgressbar(worker, (double)i / kiaiToggles.Count, 2, maxStages); } for (int i = 0; i < svChanges.Count; i++) { TimingPoint tp = svChanges[i]; tp.ResnapSelf(timing, snap1, snap2); UpdateProgressbar(worker, (double)i / svChanges.Count, 3, maxStages); } } if (resnapBookmarks) { // Resnap the bookmarks List <double> newBookmarks = new List <double>(); List <double> bookmarks = editor.Beatmap.GetBookmarks(); for (int i = 0; i < bookmarks.Count; i++) { double bookmark = bookmarks[i]; newBookmarks.Add(Math.Floor(timing.Resnap(bookmark, snap1, snap2))); UpdateProgressbar(worker, (double)i / bookmarks.Count, 4, maxStages); } editor.Beatmap.SetBookmarks(newBookmarks); } // Maybe mute unclickable timelineobjects if (removeUnclickabeHitsounds) { foreach (TimelineObject tlo in timeline.TimeLineObjects) { if (!(tlo.IsCircle || tlo.IsSliderHead || tlo.IsHoldnoteHead)) // Not clickable { tlo.FenoSampleVolume = 5; // 5% volume mute } } } // Make new timingpoints List <Change> changes = new List <Change>(); // Add redlines List <TimingPoint> redlines = timing.GetAllRedlines(); for (int i = 0; i < redlines.Count; i++) { TimingPoint tp = redlines[i]; changes.Add(new Change(tp, mpb: true, meter: true, inherited: true)); UpdateProgressbar(worker, (double)i / redlines.Count, 5, maxStages); } // Add SV changes for taiko and mania if (mode == 1 || mode == 3) { for (int i = 0; i < svChanges.Count; i++) { TimingPoint tp = svChanges[i]; changes.Add(new Change(tp, mpb: true)); UpdateProgressbar(worker, (double)i / svChanges.Count, 6, maxStages); } } // Add Kiai toggles for (int i = 0; i < kiaiToggles.Count; i++) { TimingPoint tp = kiaiToggles[i]; changes.Add(new Change(tp, kiai: true)); UpdateProgressbar(worker, (double)i / kiaiToggles.Count, 7, maxStages); } // Add Hitobject stuff for (int i = 0; i < editor.Beatmap.HitObjects.Count; i++) { HitObject ho = editor.Beatmap.HitObjects[i]; if (ho.IsSlider) // SV changes { TimingPoint tp = ho.TP.Copy(); tp.Offset = ho.Time; tp.MpB = ho.SV; changes.Add(new Change(tp, mpb: true)); } // Body hitsounds bool vol = (ho.IsSlider && volumeSliders) || (ho.IsSpinner && volumeSpinners); bool sam = (ho.IsSlider && samplesetSliders && ho.SampleSet == 0); bool ind = (ho.IsSlider && samplesetSliders); bool samplesetActuallyChanged = false; foreach (TimingPoint tp in ho.BodyHitsounds) { if (tp.Volume == 5 && removeSliderendMuting) { vol = false; } // Removing sliderbody silencing changes.Add(new Change(tp, volume: vol, index: ind, sampleset: sam)); if (tp.SampleSet != ho.HitsoundTP.SampleSet) { samplesetActuallyChanged = samplesetSliders && ho.SampleSet == 0; } // True for sampleset change in sliderbody } if (ho.IsSlider && (!samplesetActuallyChanged) && ho.SampleSet == 0) // Case can put sampleset on sliderbody { ho.SampleSet = ho.HitsoundTP.SampleSet; ho.SliderExtras = true; } if (ho.IsSlider && samplesetActuallyChanged) // Make it start out with the right sampleset { TimingPoint tp = ho.HitsoundTP.Copy(); tp.Offset = ho.Time; changes.Add(new Change(tp, sampleset: true)); } UpdateProgressbar(worker, (double)i / editor.Beatmap.HitObjects.Count, 8, maxStages); } // Add timeline hitsounds for (int i = 0; i < timeline.TimeLineObjects.Count; i++) { TimelineObject tlo = timeline.TimeLineObjects[i]; // Change the samplesets in the hitobjects if (tlo.Origin.IsCircle) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; if (mode == 3) { tlo.Origin.CustomIndex = tlo.FenoCustomIndex; tlo.Origin.SampleVolume = tlo.FenoSampleVolume; } } else if (tlo.Origin.IsSlider) { tlo.Origin.EdgeHitsounds[tlo.Repeat] = tlo.GetHitsounds(); tlo.Origin.EdgeSampleSets[tlo.Repeat] = tlo.FenoSampleSet; tlo.Origin.EdgeAdditionSets[tlo.Repeat] = tlo.FenoAdditionSet; tlo.Origin.SliderExtras = true; if (tlo.Origin.EdgeAdditionSets[tlo.Repeat] == tlo.Origin.EdgeSampleSets[tlo.Repeat]) // Simplify additions to auto { tlo.Origin.EdgeAdditionSets[tlo.Repeat] = 0; } } else if (tlo.Origin.IsSpinner) { if (tlo.Repeat == 1) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; } } else if (tlo.Origin.IsHoldNote) { if (tlo.Repeat == 0) { tlo.Origin.SampleSet = tlo.FenoSampleSet; tlo.Origin.AdditionSet = tlo.FenoAdditionSet; tlo.Origin.CustomIndex = tlo.FenoCustomIndex; tlo.Origin.SampleVolume = tlo.FenoSampleVolume; } } if (tlo.Origin.AdditionSet == tlo.Origin.SampleSet) // Simplify additions to auto { tlo.Origin.AdditionSet = 0; } if (mode == 0 && tlo.HasHitsound) // Add greenlines for custom indexes and volumes { TimingPoint tp = tlo.Origin.TP.Copy(); tp.Offset = tlo.Time; tp.SampleIndex = tlo.FenoCustomIndex; tp.Volume = tlo.FenoSampleVolume; bool ind = !(tlo.Filename != "" && (tlo.IsCircle || tlo.IsHoldnoteHead || tlo.IsSpinnerEnd)); // Index doesnt have to change if custom is overridden by Filename bool vol = !(tp.Volume == 5 && removeSliderendMuting && (tlo.IsSliderEnd || tlo.IsSpinnerEnd)); // Remove volume change if sliderend muting or spinnerend muting changes.Add(new Change(tp, volume: vol, index: ind)); } UpdateProgressbar(worker, (double)i / timeline.TimeLineObjects.Count, 9, maxStages); } // Add the new timingpoints changes = changes.OrderBy(o => o.TP.Offset).ToList(); List <TimingPoint> newTimingPoints = new List <TimingPoint>(); for (int i = 0; i < changes.Count; i++) { Change c = changes[i]; c.AddChange(newTimingPoints, timing); UpdateProgressbar(worker, (double)i / changes.Count, 10, maxStages); } // Replace the old timingpoints timing.TimingPoints = newTimingPoints; // Save the file editor.SaveFile(); // Complete progressbar if (worker != null && worker.WorkerReportsProgress) { worker.ReportProgress(100); } // Make an accurate message (Softwareporn) int removed = num_timingPoints - newTimingPoints.Count; string message = ""; if (removed < 0) { message += "Succesfully added " + Math.Abs(removed); } else { message += "Succesfully removed " + removed; } if (Math.Abs(removed) == 1) { message += " greenline and resnapped " + objectsResnapped; } else { message += " greenlines and resnapped " + objectsResnapped; } if (Math.Abs(objectsResnapped) == 1) { message += " object!"; } else { message += " objects!"; } return(message); }