diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 633eb8f15e..bd9cdba9fb 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -20,6 +20,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
+using osu.Game.Screens.Edit;
using osu.Game.Skinning;
using osu.Game.Stores;
@@ -112,29 +113,36 @@ namespace osu.Game.Beatmaps
/// The new difficulty will be backed by a model
/// and represented by the returned .
///
- public virtual WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo)
+ public virtual WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters)
{
- // fetch one of the existing difficulties to copy timing points and metadata from,
- // so that the user doesn't have to fill all of that out again.
- // this silently assumes that all difficulties have the same timing points and metadata,
- // but cases where this isn't true seem rather rare / pathological.
- var referenceBeatmap = GetWorkingBeatmap(beatmapSetInfo.Beatmaps.First());
+ var referenceBeatmap = creationParameters.ReferenceBeatmap;
+ var targetBeatmapSet = creationParameters.BeatmapSet;
- var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone());
+ var newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone());
// populate circular beatmap set info <-> beatmap info references manually.
// several places like `BeatmapModelManager.Save()` or `GetWorkingBeatmap()`
// rely on them being freely traversable in both directions for correct operation.
- beatmapSetInfo.Beatmaps.Add(newBeatmapInfo);
- newBeatmapInfo.BeatmapSet = beatmapSetInfo;
+ targetBeatmapSet.Beatmaps.Add(newBeatmapInfo);
+ newBeatmapInfo.BeatmapSet = targetBeatmapSet;
- var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
- foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints)
- newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
+ IBeatmap newBeatmap;
+
+ if (creationParameters.ClearAllObjects)
+ {
+ newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
+ foreach (var timingPoint in referenceBeatmap.ControlPointInfo.TimingPoints)
+ newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
+ }
+ else
+ {
+ newBeatmap = referenceBeatmap.Clone();
+ newBeatmap.BeatmapInfo = newBeatmapInfo;
+ }
beatmapModelManager.Save(newBeatmapInfo, newBeatmap);
- workingBeatmapCache.Invalidate(beatmapSetInfo);
+ workingBeatmapCache.Invalidate(targetBeatmapSet);
return GetWorkingBeatmap(newBeatmap.BeatmapInfo);
}
diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs
new file mode 100644
index 0000000000..472f0e8948
--- /dev/null
+++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs
@@ -0,0 +1,45 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Overlays.Dialog;
+
+namespace osu.Game.Screens.Edit
+{
+ public class CreateNewDifficultyDialog : PopupDialog
+ {
+ ///
+ /// Delegate used to create new difficulties.
+ /// A value of in the clearAllObjects parameter
+ /// indicates that the new difficulty should have its hitobjects cleared;
+ /// otherwise, the new difficulty should be an exact copy of an existing one.
+ ///
+ public delegate void CreateNewDifficulty(bool clearAllObjects);
+
+ public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty)
+ {
+ HeaderText = "Would you like to clear all objects?";
+
+ Icon = FontAwesome.Regular.Clone;
+
+ Buttons = new PopupDialogButton[]
+ {
+ new PopupDialogOkButton
+ {
+ Text = "Yeah, let's start from scratch!",
+ Action = () => createNewDifficulty.Invoke(true)
+ },
+ new PopupDialogCancelButton
+ {
+ Text = "No, create an exact copy of this difficulty",
+ Action = () => createNewDifficulty.Invoke(false)
+ },
+ new PopupDialogCancelButton
+ {
+ Text = "I changed my mind, I want to keep editing this difficulty",
+ Action = () => { }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 2aec63fa65..c5578287e3 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -841,7 +841,25 @@ namespace osu.Game.Screens.Edit
}
protected void CreateNewDifficulty(RulesetInfo rulesetInfo)
- => loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState());
+ {
+ if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset))
+ {
+ switchToNewDifficulty(rulesetInfo, true);
+ return;
+ }
+
+ dialogOverlay.Push(new CreateNewDifficultyDialog(clearAllObjects => switchToNewDifficulty(rulesetInfo, clearAllObjects)));
+ }
+
+ private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects)
+ => loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters
+ {
+ BeatmapSet = editorBeatmap.BeatmapInfo.BeatmapSet,
+ Ruleset = rulesetInfo,
+ ReferenceBeatmap = playableBeatmap,
+ ClearAllObjects = clearAllObjects,
+ EditorState = GetState()
+ });
private EditorMenuItem createDifficultySwitchMenu()
{
diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs
index de47411fdc..169b601a94 100644
--- a/osu.Game/Screens/Edit/EditorLoader.cs
+++ b/osu.Game/Screens/Edit/EditorLoader.cs
@@ -11,7 +11,6 @@ using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
@@ -80,12 +79,12 @@ namespace osu.Game.Screens.Edit
}
}
- public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState)
+ public void ScheduleSwitchToNewDifficulty(NewDifficultyCreationParameters creationParameters)
=> scheduleDifficultySwitch(() =>
{
try
{
- return beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo);
+ return beatmapManager.CreateNewBlankDifficulty(creationParameters);
}
catch (Exception ex)
{
@@ -94,7 +93,7 @@ namespace osu.Game.Screens.Edit
Logger.Error(ex, ex.Message);
return Beatmap.Value;
}
- }, editorState);
+ }, creationParameters.EditorState);
public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState)
=> scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState);
diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs
new file mode 100644
index 0000000000..dd03fd3644
--- /dev/null
+++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs
@@ -0,0 +1,36 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+
+namespace osu.Game.Screens.Edit
+{
+ public class NewDifficultyCreationParameters
+ {
+ ///
+ /// The that should contain the newly-created difficulty.
+ ///
+ public BeatmapSetInfo BeatmapSet { get; set; }
+
+ ///
+ /// The that the new difficulty should be playable for.
+ ///
+ public RulesetInfo Ruleset { get; set; }
+
+ ///
+ /// A reference upon which the new difficulty should be based.
+ ///
+ public IBeatmap ReferenceBeatmap { get; set; }
+
+ ///
+ /// Whether all objects should be cleared from the new difficulty.
+ ///
+ public bool ClearAllObjects { get; set; }
+
+ ///
+ /// The saved state of the previous which should be restored upon opening the newly-created difficulty.
+ ///
+ public EditorState EditorState { get; set; }
+ }
+}
diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs
index 331bf04644..ff09598eef 100644
--- a/osu.Game/Tests/Visual/EditorTestScene.cs
+++ b/osu.Game/Tests/Visual/EditorTestScene.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual
return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host);
}
- public override WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo)
+ public override WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters)
{
// don't actually care about properly creating a difficulty for this context.
return TestBeatmap;