diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index e4fdb3d471..38ba244f28 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -73,7 +73,9 @@ namespace osu.Game.Beatmaps
new BeatmapModelManager(realm, storage, onlineLookupQueue);
///
- /// Create a new .
+ /// Create a new beatmap set, backed by a model,
+ /// with a single difficulty which is backed by a model
+ /// and represented by the returned usable .
///
public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user)
{
@@ -105,6 +107,34 @@ namespace osu.Game.Beatmaps
return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First()));
}
+ ///
+ /// Add a new difficulty to the beatmap set represented by the provided .
+ /// The new difficulty will be backed by a model
+ /// and represented by the returned .
+ ///
+ public WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo)
+ {
+ // 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 newBeatmap = new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone())
+ };
+
+ foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints)
+ newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
+
+ var createdBeatmapInfo = beatmapModelManager.AddDifficultyToBeatmapSet(beatmapSetInfo, newBeatmap);
+ return GetWorkingBeatmap(createdBeatmapInfo);
+ }
+
+ // TODO: add back support for making a copy of another difficulty
+ // (likely via a separate `CopyDifficulty()` method).
+
///
/// Delete a beatmap difficulty.
///
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index f6666a6ea9..3a24c4808f 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -7,6 +7,7 @@ using Newtonsoft.Json;
using osu.Framework.Testing;
using osu.Game.Models;
using osu.Game.Users;
+using osu.Game.Utils;
using Realms;
#nullable enable
@@ -16,7 +17,7 @@ namespace osu.Game.Beatmaps
[ExcludeFromDynamicCompile]
[Serializable]
[MapTo("BeatmapMetadata")]
- public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo
+ public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo, IDeepCloneable
{
public string Title { get; set; } = string.Empty;
@@ -57,5 +58,18 @@ namespace osu.Game.Beatmaps
IUser IBeatmapMetadataInfo.Author => Author;
public override string ToString() => this.GetDisplayTitle();
+
+ public BeatmapMetadata DeepClone() => new BeatmapMetadata(Author.DeepClone())
+ {
+ Title = Title,
+ TitleUnicode = TitleUnicode,
+ Artist = Artist,
+ ArtistUnicode = ArtistUnicode,
+ Source = Source,
+ Tags = Tags,
+ PreviewTime = PreviewTime,
+ AudioFile = AudioFile,
+ BackgroundFile = BackgroundFile
+ };
}
}
diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs
index e8104f2ecb..2ab5ac1db9 100644
--- a/osu.Game/Beatmaps/BeatmapModelManager.cs
+++ b/osu.Game/Beatmaps/BeatmapModelManager.cs
@@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps
public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null)
{
var setInfo = beatmapInfo.BeatmapSet;
-
Debug.Assert(setInfo != null);
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`.
@@ -85,6 +84,24 @@ namespace osu.Game.Beatmaps
WorkingBeatmapCache?.Invalidate(beatmapInfo);
}
+ ///
+ /// Add a new difficulty to the beatmap set represented by the provided .
+ ///
+ public BeatmapInfo AddDifficultyToBeatmapSet(BeatmapSetInfo beatmapSetInfo, Beatmap beatmap)
+ {
+ return Realm.Run(realm =>
+ {
+ var beatmapInfo = beatmap.BeatmapInfo;
+
+ beatmapSetInfo.Beatmaps.Add(beatmapInfo);
+ beatmapInfo.BeatmapSet = beatmapSetInfo;
+
+ Save(beatmapInfo, beatmap);
+
+ return beatmapInfo.Detach();
+ });
+ }
+
private static string getFilename(BeatmapInfo beatmapInfo)
{
var metadata = beatmapInfo.Metadata;
@@ -103,9 +120,9 @@ namespace osu.Game.Beatmaps
public void Update(BeatmapSetInfo item)
{
- Realm.Write(realm =>
+ Realm.Write(r =>
{
- var existing = realm.Find(item.ID);
+ var existing = r.Find(item.ID);
item.CopyChangesToRealm(existing);
});
}
diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs
index 7a0ca2c85a..f89bbbe19d 100644
--- a/osu.Game/Database/RealmObjectExtensions.cs
+++ b/osu.Game/Database/RealmObjectExtensions.cs
@@ -58,7 +58,16 @@ namespace osu.Game.Database
if (existing != null)
copyChangesToRealm(beatmap, existing);
else
- d.Beatmaps.Add(beatmap);
+ {
+ var newBeatmap = new BeatmapInfo
+ {
+ ID = beatmap.ID,
+ BeatmapSet = d,
+ Ruleset = d.Realm.Find(beatmap.Ruleset.ShortName)
+ };
+ d.Beatmaps.Add(newBeatmap);
+ copyChangesToRealm(beatmap, newBeatmap);
+ }
}
});
diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs
index 5fccff597c..18c849cf0a 100644
--- a/osu.Game/Models/RealmUser.cs
+++ b/osu.Game/Models/RealmUser.cs
@@ -2,12 +2,14 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Game.Database;
using osu.Game.Users;
+using osu.Game.Utils;
using Realms;
namespace osu.Game.Models
{
- public class RealmUser : EmbeddedObject, IUser, IEquatable
+ public class RealmUser : EmbeddedObject, IUser, IEquatable, IDeepCloneable
{
public int OnlineID { get; set; } = 1;
@@ -22,5 +24,7 @@ namespace osu.Game.Models
return OnlineID == other.OnlineID && Username == other.Username;
}
+
+ public RealmUser DeepClone() => (RealmUser)this.Detach().MemberwiseClone();
}
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 61c5fd2ca4..df8e326c5b 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -822,11 +822,14 @@ namespace osu.Game.Screens.Edit
var rulesetItems = new List