mirror of
https://github.com/ppy/osu
synced 2025-02-17 02:47:19 +00:00
Restructure difficulty copy flow to adapt to latest changes
This commit is contained in:
parent
6221447164
commit
e45a2ae0fc
@ -20,7 +20,6 @@ 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;
|
||||
|
||||
@ -109,55 +108,73 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new difficulty to the beatmap set represented by the provided <see cref="BeatmapSetInfo"/>.
|
||||
/// Add a new difficulty to the provided <paramref name="targetBeatmapSet"/> based on the provided <paramref name="referenceWorkingBeatmap"/>.
|
||||
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
|
||||
/// and represented by the returned <see cref="WorkingBeatmap"/>.
|
||||
/// </summary>
|
||||
public virtual WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters)
|
||||
/// <remarks>
|
||||
/// Contrary to <see cref="CopyExistingDifficulty"/>, this method does not preserve hitobjects and beatmap-level settings from <paramref name="referenceWorkingBeatmap"/>.
|
||||
/// The created beatmap will have zero hitobjects and will have default settings (including difficulty settings), but will preserve metadata and existing timing points.
|
||||
/// </remarks>
|
||||
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the new difficulty to.</param>
|
||||
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to use as a baseline reference when creating the new difficulty.</param>
|
||||
/// <param name="rulesetInfo">The ruleset with which the new difficulty should be created.</param>
|
||||
public virtual WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo)
|
||||
{
|
||||
var referenceBeatmap = creationParameters.ReferenceBeatmap;
|
||||
var targetBeatmapSet = creationParameters.BeatmapSet;
|
||||
var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo);
|
||||
|
||||
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone());
|
||||
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
|
||||
foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints)
|
||||
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
|
||||
|
||||
return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a copy of the provided <paramref name="referenceWorkingBeatmap"/> to the provided <paramref name="targetBeatmapSet"/>.
|
||||
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
|
||||
/// and represented by the returned <see cref="WorkingBeatmap"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contrary to <see cref="CreateNewDifficulty"/>, this method creates a nearly-exact copy of <paramref name="referenceWorkingBeatmap"/>
|
||||
/// (with the exception of a few key properties that cannot be copied under any circumstance, like difficulty name, beatmap hash, or online status).
|
||||
/// </remarks>
|
||||
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the copy to.</param>
|
||||
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to be copied.</param>
|
||||
public virtual WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap)
|
||||
{
|
||||
var newBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(referenceWorkingBeatmap.BeatmapInfo.Ruleset).Clone();
|
||||
BeatmapInfo newBeatmapInfo;
|
||||
IBeatmap newBeatmap;
|
||||
|
||||
if (creationParameters.CreateBlank)
|
||||
{
|
||||
newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone());
|
||||
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 = referenceBeatmap.BeatmapInfo.Clone();
|
||||
// assign a new ID to the clone.
|
||||
newBeatmapInfo.ID = Guid.NewGuid();
|
||||
// add "(copy)" suffix to difficulty name to avoid clashes on save.
|
||||
newBeatmapInfo.DifficultyName += " (copy)";
|
||||
// clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps.
|
||||
newBeatmapInfo.Hash = string.Empty;
|
||||
// clear online properties.
|
||||
newBeatmapInfo.OnlineID = -1;
|
||||
newBeatmapInfo.Status = BeatmapOnlineStatus.None;
|
||||
}
|
||||
newBeatmap.BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap.BeatmapInfo.Clone();
|
||||
// assign a new ID to the clone.
|
||||
newBeatmapInfo.ID = Guid.NewGuid();
|
||||
// add "(copy)" suffix to difficulty name to avoid clashes on save.
|
||||
newBeatmapInfo.DifficultyName += " (copy)";
|
||||
// clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps.
|
||||
newBeatmapInfo.Hash = string.Empty;
|
||||
// clear online properties.
|
||||
newBeatmapInfo.OnlineID = -1;
|
||||
newBeatmapInfo.Status = BeatmapOnlineStatus.None;
|
||||
|
||||
return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin);
|
||||
}
|
||||
|
||||
private WorkingBeatmap addDifficultyToSet(BeatmapSetInfo targetBeatmapSet, IBeatmap newBeatmap, ISkin beatmapSkin)
|
||||
{
|
||||
// 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.
|
||||
targetBeatmapSet.Beatmaps.Add(newBeatmapInfo);
|
||||
newBeatmapInfo.BeatmapSet = targetBeatmapSet;
|
||||
targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo);
|
||||
newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet;
|
||||
|
||||
beatmapModelManager.Save(newBeatmapInfo, newBeatmap, creationParameters.ReferenceBeatmapSkin);
|
||||
beatmapModelManager.Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin);
|
||||
|
||||
workingBeatmapCache.Invalidate(targetBeatmapSet);
|
||||
return GetWorkingBeatmap(newBeatmap.BeatmapInfo);
|
||||
}
|
||||
|
||||
// TODO: add back support for making a copy of another difficulty
|
||||
// (likely via a separate `CopyDifficulty()` method).
|
||||
|
||||
/// <summary>
|
||||
/// Delete a beatmap difficulty.
|
||||
/// </summary>
|
||||
|
@ -10,11 +10,11 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate used to create new difficulties.
|
||||
/// A value of <see langword="true"/> in the <c>clearAllObjects</c> parameter
|
||||
/// indicates that the new difficulty should have its hitobjects cleared;
|
||||
/// otherwise, the new difficulty should be an exact copy of an existing one.
|
||||
/// A value of <see langword="true"/> in the <c>createCopy</c> parameter
|
||||
/// indicates that the new difficulty should be an exact copy of an existing one;
|
||||
/// otherwise, the new difficulty should have its hitobjects and beatmap-level settings cleared.
|
||||
/// </summary>
|
||||
public delegate void CreateNewDifficulty(bool clearAllObjects);
|
||||
public delegate void CreateNewDifficulty(bool createCopy);
|
||||
|
||||
public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty)
|
||||
{
|
||||
@ -27,12 +27,12 @@ namespace osu.Game.Screens.Edit
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = "Yeah, let's start from scratch!",
|
||||
Action = () => createNewDifficulty.Invoke(true)
|
||||
Action = () => createNewDifficulty.Invoke(false)
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "No, create an exact copy of this difficulty",
|
||||
Action = () => createNewDifficulty.Invoke(false)
|
||||
Action = () => createNewDifficulty.Invoke(true)
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
|
@ -9,7 +9,6 @@ using JetBrains.Annotations;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -359,14 +358,14 @@ namespace osu.Game.Screens.Edit
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.
|
||||
/// </summary>
|
||||
/// <param name="nextBeatmap">
|
||||
/// The next beatmap to be shown, in the case of difficulty switch.
|
||||
/// <param name="nextRuleset">
|
||||
/// The ruleset of the next beatmap to be shown, in the case of difficulty switch.
|
||||
/// <see langword="null"/> indicates that the beatmap will not be changing.
|
||||
/// </param>
|
||||
public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState
|
||||
public EditorState GetState([CanBeNull] RulesetInfo nextRuleset = null) => new EditorState
|
||||
{
|
||||
Time = clock.CurrentTimeAccurate,
|
||||
ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty
|
||||
ClipboardContent = nextRuleset == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextRuleset.ShortName ? Clipboard.Content.Value : string.Empty
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -845,23 +844,15 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
if (!rulesetInfo.Equals(editorBeatmap.BeatmapInfo.Ruleset))
|
||||
{
|
||||
switchToNewDifficulty(rulesetInfo, true);
|
||||
switchToNewDifficulty(rulesetInfo, false);
|
||||
return;
|
||||
}
|
||||
|
||||
dialogOverlay.Push(new CreateNewDifficultyDialog(clearAllObjects => switchToNewDifficulty(rulesetInfo, clearAllObjects)));
|
||||
dialogOverlay.Push(new CreateNewDifficultyDialog(createCopy => switchToNewDifficulty(rulesetInfo, createCopy)));
|
||||
}
|
||||
|
||||
private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects)
|
||||
=> loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters
|
||||
(
|
||||
editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(),
|
||||
rulesetInfo,
|
||||
editorBeatmap,
|
||||
editorBeatmap.BeatmapSkin,
|
||||
clearAllObjects,
|
||||
GetState()
|
||||
));
|
||||
private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool createCopy)
|
||||
=> loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo, rulesetInfo, createCopy, GetState(rulesetInfo));
|
||||
|
||||
private EditorMenuItem createDifficultySwitchMenu()
|
||||
{
|
||||
@ -886,7 +877,7 @@ namespace osu.Game.Screens.Edit
|
||||
return new EditorMenuItem("Change difficulty") { Items = difficultyItems };
|
||||
}
|
||||
|
||||
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap));
|
||||
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap.Ruleset));
|
||||
|
||||
private void cancelExit()
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
@ -11,6 +12,7 @@ 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;
|
||||
@ -79,19 +81,18 @@ namespace osu.Game.Screens.Edit
|
||||
}
|
||||
}
|
||||
|
||||
public void ScheduleSwitchToNewDifficulty(NewDifficultyCreationParameters creationParameters)
|
||||
public void ScheduleSwitchToNewDifficulty(BeatmapInfo referenceBeatmapInfo, RulesetInfo rulesetInfo, bool createCopy, EditorState editorState)
|
||||
=> scheduleDifficultySwitch(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(creationParameters.ReferenceBeatmap.BeatmapInfo);
|
||||
return beatmapManager.CreateNewDifficulty(new NewDifficultyCreationParameters(
|
||||
refetchedBeatmap.BeatmapSetInfo,
|
||||
refetchedBeatmap.BeatmapInfo.Ruleset,
|
||||
refetchedBeatmap.Beatmap,
|
||||
refetchedBeatmap.Skin,
|
||||
creationParameters.CreateBlank,
|
||||
creationParameters.EditorState));
|
||||
// fetch a fresh detached reference from database to avoid polluting model instances attached to cached working beatmaps.
|
||||
var targetBeatmapSet = beatmapManager.QueryBeatmap(b => b.ID == referenceBeatmapInfo.ID).AsNonNull().BeatmapSet.AsNonNull();
|
||||
var referenceWorkingBeatmap = beatmapManager.GetWorkingBeatmap(referenceBeatmapInfo);
|
||||
|
||||
return createCopy
|
||||
? beatmapManager.CopyExistingDifficulty(targetBeatmapSet, referenceWorkingBeatmap)
|
||||
: beatmapManager.CreateNewDifficulty(targetBeatmapSet, referenceWorkingBeatmap, rulesetInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -100,7 +101,7 @@ namespace osu.Game.Screens.Edit
|
||||
Logger.Error(ex, ex.Message);
|
||||
return Beatmap.Value;
|
||||
}
|
||||
}, creationParameters.EditorState);
|
||||
}, editorState);
|
||||
|
||||
public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState)
|
||||
=> scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState);
|
||||
|
@ -1,65 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Edit
|
||||
{
|
||||
public class NewDifficultyCreationParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="BeatmapSetInfo"/> that should contain the newly-created difficulty.
|
||||
/// </summary>
|
||||
public BeatmapSetInfo BeatmapSet { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RulesetInfo"/> that the new difficulty should be playable for.
|
||||
/// </summary>
|
||||
public RulesetInfo Ruleset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference <see cref="IBeatmap"/> upon which the new difficulty should be based.
|
||||
/// </summary>
|
||||
public IBeatmap ReferenceBeatmap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference <see cref="ISkin"/> that the new difficulty should base its own skin upon.
|
||||
/// </summary>
|
||||
public ISkin? ReferenceBeatmapSkin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the new difficulty should be blank.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A blank difficulty will have no objects, no control points other than timing points taken from <see cref="ReferenceBeatmap"/>
|
||||
/// and will not share <see cref="BeatmapInfo"/> values with <see cref="ReferenceBeatmap"/>,
|
||||
/// but it will share metadata and timing information with <see cref="ReferenceBeatmap"/>.
|
||||
/// </remarks>
|
||||
public bool CreateBlank { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The saved state of the previous <see cref="Editor"/> which should be restored upon opening the newly-created difficulty.
|
||||
/// </summary>
|
||||
public EditorState EditorState { get; }
|
||||
|
||||
public NewDifficultyCreationParameters(
|
||||
BeatmapSetInfo beatmapSet,
|
||||
RulesetInfo ruleset,
|
||||
IBeatmap referenceBeatmap,
|
||||
ISkin? referenceBeatmapSkin,
|
||||
bool createBlank,
|
||||
EditorState editorState)
|
||||
{
|
||||
BeatmapSet = beatmapSet;
|
||||
Ruleset = ruleset;
|
||||
ReferenceBeatmap = referenceBeatmap;
|
||||
ReferenceBeatmapSkin = referenceBeatmapSkin;
|
||||
CreateBlank = createBlank;
|
||||
EditorState = editorState;
|
||||
}
|
||||
}
|
||||
}
|
@ -136,7 +136,13 @@ namespace osu.Game.Tests.Visual
|
||||
return new TestWorkingBeatmapCache(this, audioManager, resources, storage, defaultBeatmap, host);
|
||||
}
|
||||
|
||||
public override WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters)
|
||||
public override WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo)
|
||||
{
|
||||
// don't actually care about properly creating a difficulty for this context.
|
||||
return TestBeatmap;
|
||||
}
|
||||
|
||||
public override WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap)
|
||||
{
|
||||
// don't actually care about properly creating a difficulty for this context.
|
||||
return TestBeatmap;
|
||||
|
Loading…
Reference in New Issue
Block a user