From a2c2b2bbb3dcbd87f9c4c08582b00b98f1096e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 17:52:59 +0100 Subject: [PATCH] Add flow for copying existing difficulty content --- osu.Game/Beatmaps/BeatmapManager.cs | 34 ++++++++------ .../Screens/Edit/CreateNewDifficultyDialog.cs | 45 +++++++++++++++++++ osu.Game/Screens/Edit/Editor.cs | 20 ++++++++- osu.Game/Screens/Edit/EditorLoader.cs | 7 ++- .../Edit/NewDifficultyCreationParameters.cs | 36 +++++++++++++++ osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 6 files changed, 125 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs create mode 100644 osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs 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;