1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 19:23:20 +08:00

Add support for creating new blank difficulties

This commit is contained in:
Bartłomiej Dach 2022-01-23 17:49:17 +01:00
parent b613aedeb8
commit dc96c4888b
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
7 changed files with 95 additions and 11 deletions

View File

@ -73,7 +73,9 @@ namespace osu.Game.Beatmaps
new BeatmapModelManager(realm, storage, onlineLookupQueue); new BeatmapModelManager(realm, storage, onlineLookupQueue);
/// <summary> /// <summary>
/// Create a new <see cref="WorkingBeatmap"/>. /// Create a new beatmap set, backed by a <see cref="BeatmapSetInfo"/> model,
/// with a single difficulty which is backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned usable <see cref="WorkingBeatmap"/>.
/// </summary> /// </summary>
public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user) public WorkingBeatmap CreateNew(RulesetInfo ruleset, APIUser user)
{ {
@ -105,6 +107,34 @@ namespace osu.Game.Beatmaps
return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First())); return imported.PerformRead(s => GetWorkingBeatmap(s.Beatmaps.First()));
} }
/// <summary>
/// Add a new difficulty to the beatmap set represented by the provided <see cref="BeatmapSetInfo"/>.
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned <see cref="WorkingBeatmap"/>.
/// </summary>
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).
/// <summary> /// <summary>
/// Delete a beatmap difficulty. /// Delete a beatmap difficulty.
/// </summary> /// </summary>

View File

@ -7,6 +7,7 @@ using Newtonsoft.Json;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Utils;
using Realms; using Realms;
#nullable enable #nullable enable
@ -16,7 +17,7 @@ namespace osu.Game.Beatmaps
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
[Serializable] [Serializable]
[MapTo("BeatmapMetadata")] [MapTo("BeatmapMetadata")]
public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo public class BeatmapMetadata : RealmObject, IBeatmapMetadataInfo, IDeepCloneable<BeatmapMetadata>
{ {
public string Title { get; set; } = string.Empty; public string Title { get; set; } = string.Empty;
@ -57,5 +58,18 @@ namespace osu.Game.Beatmaps
IUser IBeatmapMetadataInfo.Author => Author; IUser IBeatmapMetadataInfo.Author => Author;
public override string ToString() => this.GetDisplayTitle(); 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
};
} }
} }

View File

@ -49,7 +49,6 @@ namespace osu.Game.Beatmaps
public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null) public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin? beatmapSkin = null)
{ {
var setInfo = beatmapInfo.BeatmapSet; var setInfo = beatmapInfo.BeatmapSet;
Debug.Assert(setInfo != null); Debug.Assert(setInfo != null);
// Difficulty settings must be copied first due to the clone in `Beatmap<>.BeatmapInfo_Set`. // 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); WorkingBeatmapCache?.Invalidate(beatmapInfo);
} }
/// <summary>
/// Add a new difficulty to the beatmap set represented by the provided <see cref="BeatmapSetInfo"/>.
/// </summary>
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) private static string getFilename(BeatmapInfo beatmapInfo)
{ {
var metadata = beatmapInfo.Metadata; var metadata = beatmapInfo.Metadata;
@ -103,9 +120,9 @@ namespace osu.Game.Beatmaps
public void Update(BeatmapSetInfo item) public void Update(BeatmapSetInfo item)
{ {
Realm.Write(realm => Realm.Write(r =>
{ {
var existing = realm.Find<BeatmapSetInfo>(item.ID); var existing = r.Find<BeatmapSetInfo>(item.ID);
item.CopyChangesToRealm(existing); item.CopyChangesToRealm(existing);
}); });
} }

View File

@ -58,7 +58,16 @@ namespace osu.Game.Database
if (existing != null) if (existing != null)
copyChangesToRealm(beatmap, existing); copyChangesToRealm(beatmap, existing);
else else
d.Beatmaps.Add(beatmap); {
var newBeatmap = new BeatmapInfo
{
ID = beatmap.ID,
BeatmapSet = d,
Ruleset = d.Realm.Find<RulesetInfo>(beatmap.Ruleset.ShortName)
};
d.Beatmaps.Add(newBeatmap);
copyChangesToRealm(beatmap, newBeatmap);
}
} }
}); });

View File

@ -2,12 +2,14 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Game.Database;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Utils;
using Realms; using Realms;
namespace osu.Game.Models namespace osu.Game.Models
{ {
public class RealmUser : EmbeddedObject, IUser, IEquatable<RealmUser> public class RealmUser : EmbeddedObject, IUser, IEquatable<RealmUser>, IDeepCloneable<RealmUser>
{ {
public int OnlineID { get; set; } = 1; public int OnlineID { get; set; } = 1;
@ -22,5 +24,7 @@ namespace osu.Game.Models
return OnlineID == other.OnlineID && Username == other.Username; return OnlineID == other.OnlineID && Username == other.Username;
} }
public RealmUser DeepClone() => (RealmUser)this.Detach().MemberwiseClone();
} }
} }

View File

@ -822,11 +822,14 @@ namespace osu.Game.Screens.Edit
var rulesetItems = new List<MenuItem>(); var rulesetItems = new List<MenuItem>();
foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID)) foreach (var ruleset in rulesets.AvailableRulesets.OrderBy(ruleset => ruleset.OnlineID))
rulesetItems.Add(new EditorMenuItem(ruleset.Name)); rulesetItems.Add(new EditorMenuItem(ruleset.Name, MenuItemType.Standard, () => createNewDifficulty(ruleset)));
return new EditorMenuItem("Create new difficulty") { Items = rulesetItems }; return new EditorMenuItem("Create new difficulty") { Items = rulesetItems };
} }
private void createNewDifficulty(RulesetInfo rulesetInfo)
=> loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo.BeatmapSet, rulesetInfo, GetState());
private EditorMenuItem createDifficultySwitchMenu() private EditorMenuItem createDifficultySwitchMenu()
{ {
var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet; var beatmapSet = playableBeatmap.BeatmapInfo.BeatmapSet;
@ -850,7 +853,7 @@ namespace osu.Game.Screens.Edit
return new EditorMenuItem("Change difficulty") { Items = difficultyItems }; return new EditorMenuItem("Change difficulty") { Items = difficultyItems };
} }
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, GetState(nextBeatmap)); protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleSwitchToExistingDifficulty(nextBeatmap, GetState(nextBeatmap));
private void cancelExit() private void cancelExit()
{ {

View File

@ -10,6 +10,7 @@ using osu.Framework.Screens;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -78,7 +79,13 @@ namespace osu.Game.Screens.Edit
} }
} }
public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState) public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState)
=> scheduleDifficultySwitch(() => beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo), editorState);
public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState)
=> scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState);
private void scheduleDifficultySwitch(Func<WorkingBeatmap> nextBeatmap, EditorState editorState)
{ {
scheduledDifficultySwitch?.Cancel(); scheduledDifficultySwitch?.Cancel();
ValidForResume = true; ValidForResume = true;
@ -87,7 +94,7 @@ namespace osu.Game.Screens.Edit
scheduledDifficultySwitch = Schedule(() => scheduledDifficultySwitch = Schedule(() =>
{ {
Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap); Beatmap.Value = nextBeatmap.Invoke();
state = editorState; state = editorState;
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap. // This screen is a weird exception to the rule that nothing after song select changes the global beatmap.