From 176bb4a4e22901a37761ab6e1812663d3ec6c7dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 10 Feb 2022 15:25:54 +0900 Subject: [PATCH 01/37] Update desktop projects to target .NET 6 --- .run/osu! (Second Client).run.xml | 8 ++++---- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 4 ++-- osu.Desktop/osu.Desktop.csproj | 2 +- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Mania.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Osu.Tests.csproj | 4 ++-- .../osu.Game.Rulesets.Taiko.Tests.csproj | 4 ++-- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 4 ++-- osu.iOS.props | 2 +- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.run/osu! (Second Client).run.xml b/.run/osu! (Second Client).run.xml index 599b4b986b..9a471df902 100644 --- a/.run/osu! (Second Client).run.xml +++ b/.run/osu! (Second Client).run.xml @@ -1,8 +1,8 @@ - - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 3c6aaa39ca..cb922c5a58 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.EmptyFreeform.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0719dd30df..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.Pippidon.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index d0db43cc81..33ad0ac4f7 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.EmptyScrolling.Tests - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 0719dd30df..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -20,7 +20,7 @@ WinExe - net5.0 + net6.0 osu.Game.Rulesets.Pippidon.Tests - \ No newline at end of file + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 89b9ffb94b..5e203af1f2 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,6 +1,6 @@  - net5.0 + net6.0 WinExe true A free-to-win rhythm game. Rhythm is just a *click* away! diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 57b914bee6..434c0e0367 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 Exe false diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 13f2e25f05..fc6d900567 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index d51a6da4f9..ddad2adfea 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index fea2e408f6..bd4c3d3345 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -10,9 +10,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index ad3713e047..a6b8eb8651 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -9,9 +9,9 @@ WinExe - net5.0 + net6.0 - \ No newline at end of file + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 3b115d43e5..acf1e8470b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -12,7 +12,7 @@ WinExe - net5.0 + net6.0 tests.ruleset diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 130fcfaca1..c7314a4969 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -11,7 +11,7 @@ WinExe - net5.0 + net6.0 @@ -20,4 +20,4 @@ - \ No newline at end of file + diff --git a/osu.iOS.props b/osu.iOS.props index 5978f6d685..7e5ab37257 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -63,7 +63,7 @@ - + $(NoWarn);NU1605 From 908c31c68764370e44e9e3a6055aa881a7ce1971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 16:02:25 +0900 Subject: [PATCH 02/37] Update stream read operations to use new helper methods --- osu.Game/Database/ImportTask.cs | 5 ++--- osu.Game/IO/Archives/ArchiveReader.cs | 14 +++----------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index cd9e396d13..d75c1a73e6 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -4,6 +4,7 @@ #nullable enable using System.IO; +using osu.Framework.Extensions; using osu.Game.IO.Archives; using osu.Game.Stores; using osu.Game.Utils; @@ -63,9 +64,7 @@ namespace osu.Game.Database if (!(stream is MemoryStream memoryStream)) { // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). - byte[] buffer = new byte[stream.Length]; - stream.Read(buffer, 0, (int)stream.Length); - memoryStream = new MemoryStream(buffer); + memoryStream = new MemoryStream(stream.ReadAllBytesToArray()); } if (ZipUtils.IsZipArchive(memoryStream)) diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 1d8da16c72..dab70eaf70 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.IO.Stores; namespace osu.Game.IO.Archives @@ -35,14 +36,7 @@ namespace osu.Game.IO.Archives public virtual byte[] Get(string name) { using (Stream input = GetStream(name)) - { - if (input == null) - return null; - - byte[] buffer = new byte[input.Length]; - input.Read(buffer); - return buffer; - } + return input?.ReadAllBytesToArray(); } public async Task GetAsync(string name, CancellationToken cancellationToken = default) @@ -52,9 +46,7 @@ namespace osu.Game.IO.Archives if (input == null) return null; - byte[] buffer = new byte[input.Length]; - await input.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - return buffer; + return await input.ReadAllBytesToArrayAsync(cancellationToken).ConfigureAwait(false); } } } From 6005daeba8bda373622123167bc533e2738704d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Feb 2022 16:02:42 +0900 Subject: [PATCH 03/37] Fix fire-and-forget async calls to use `WaitSafely` --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 8f6ba6375f..7661cf671b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -829,7 +829,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + })).WaitSafely()); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); @@ -860,11 +860,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("add item as other user", () => client.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem { BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID - }))); + })).WaitSafely()); AddUntilStep("item arrived in playlist", () => client.Room?.Playlist.Count == 2); - AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2)); + AddStep("delete item as other user", () => client.RemoveUserPlaylistItem(1234, 2).WaitSafely()); AddUntilStep("item removed from playlist", () => client.Room?.Playlist.Count == 1); AddStep("exit gameplay as initial user", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); 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 04/37] 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; From 0d1171b7fae94ea769598bb2b5f0dff194c6c32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 17:56:51 +0100 Subject: [PATCH 05/37] Adjust existing test coverage to pass --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index a14c9aded3..89a9307f9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -10,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; @@ -92,7 +93,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewDifficulty() + public void TestCreateNewDifficulty([Values] bool sameRuleset) { string firstDifficultyName = Guid.NewGuid().ToString(); string secondDifficultyName = Guid.NewGuid().ToString(); @@ -111,7 +112,14 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("can save again", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo)); + + if (sameRuleset) + { + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + } + AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; @@ -154,7 +162,7 @@ namespace osu.Game.Tests.Visual.Editing } [Test] - public void TestCreateNewBeatmapFailsWithSameNamedDifficulties() + public void TestCreateNewBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset) { Guid setId = Guid.Empty; const string duplicate_difficulty_name = "duplicate"; @@ -168,7 +176,14 @@ namespace osu.Game.Tests.Visual.Editing return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1); }); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(sameRuleset ? new OsuRuleset().RulesetInfo : new CatchRuleset().RulesetInfo)); + + if (sameRuleset) + { + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction()); + } + AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; From eb939547a9978e0d4e47a65f90c3b5b902b7d988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:03:54 +0100 Subject: [PATCH 06/37] Add test coverage for difficulty copy flow --- .../Editing/TestSceneEditorBeatmapCreation.cs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 89a9307f9f..91f667d8b0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -9,13 +9,17 @@ using osu.Framework.Allocation; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; using osu.Game.Tests.Resources; +using osuTK; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -99,6 +103,21 @@ namespace osu.Game.Tests.Visual.Editing string secondDifficultyName = Guid.NewGuid().ToString(); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + new HitCircle + { + Position = new Vector2(0), + StartTime = 0 + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE, + StartTime = 1000 + } + })); + AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { @@ -126,6 +145,80 @@ namespace osu.Game.Tests.Visual.Editing return difficultyName != null && difficultyName != firstDifficultyName; }); + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == secondDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + }); + } + + [Test] + public void TestCopyDifficulty() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + string secondDifficultyName = Guid.NewGuid().ToString(); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] + { + new HitCircle + { + Position = new Vector2(0), + StartTime = 0 + }, + new HitCircle + { + Position = OsuPlayfield.BASE_SIZE, + StartTime = 1000 + } + })); + + AddStep("save beatmap", () => Editor.Save()); + AddAssert("new beatmap persisted", () => + { + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + + return beatmap != null + && beatmap.DifficultyName == firstDifficultyName + && set != null + && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); + }); + AddAssert("can save again", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick()); + + AddUntilStep("wait for created", () => + { + string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => From fd1c8c361444f1bd7077379d64a1c03e5576c0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:26:19 +0100 Subject: [PATCH 07/37] Add failing test coverage for correct beatmap difficulty copy --- osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 91f667d8b0..672e643e60 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -187,6 +187,7 @@ namespace osu.Game.Tests.Visual.Editing StartTime = 1000 } })); + AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -218,6 +219,7 @@ namespace osu.Game.Tests.Visual.Editing return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; }); AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); + AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From 1bf5375e746f6be78a992594fda76b821b72cb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:40:51 +0100 Subject: [PATCH 08/37] Fix `BeatmapInfo`-associated member not copying --- osu.Game/Beatmaps/BeatmapManager.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index bd9cdba9fb..c350cfc111 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -118,18 +118,12 @@ namespace osu.Game.Beatmaps var referenceBeatmap = creationParameters.ReferenceBeatmap; var targetBeatmapSet = creationParameters.BeatmapSet; - 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. - targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); - newBeatmapInfo.BeatmapSet = targetBeatmapSet; - + BeatmapInfo newBeatmapInfo; IBeatmap newBeatmap; if (creationParameters.ClearAllObjects) { + 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()); @@ -137,9 +131,19 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - newBeatmap.BeatmapInfo = newBeatmapInfo; + newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); + // assign a new ID to the clone. + newBeatmapInfo.ID = Guid.NewGuid(); + // clear difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName = string.Empty; } + // 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; + beatmapModelManager.Save(newBeatmapInfo, newBeatmap); workingBeatmapCache.Invalidate(targetBeatmapSet); From 1292722a00e331933852f1842455a3e25108c4cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:44:12 +0100 Subject: [PATCH 09/37] Add failing test coverage for correct combo colour copy --- .../Editing/TestSceneEditorBeatmapCreation.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 672e643e60..90b1d3a6f9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -6,6 +6,8 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -188,6 +190,16 @@ namespace osu.Game.Tests.Visual.Editing } })); AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4); + AddStep("set combo colours", () => + { + var beatmapSkin = EditorBeatmap.BeatmapSkin.AsNonNull(); + beatmapSkin.ComboColours.Clear(); + beatmapSkin.ComboColours.AddRange(new[] + { + new Colour4(255, 0, 0, 255), + new Colour4(0, 0, 255, 255) + }); + }); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -220,6 +232,7 @@ namespace osu.Game.Tests.Visual.Editing }); AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2); AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); + AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2); AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From a144d6f8d67b0fe6c90021b3309682f323da9322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 18:50:11 +0100 Subject: [PATCH 10/37] Fix beatmap skin properties not copying --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 16 +++++---- .../Edit/NewDifficultyCreationParameters.cs | 34 ++++++++++++++++--- 3 files changed, 39 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index c350cfc111..a9d0576696 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -144,7 +144,7 @@ namespace osu.Game.Beatmaps targetBeatmapSet.Beatmaps.Add(newBeatmapInfo); newBeatmapInfo.BeatmapSet = targetBeatmapSet; - beatmapModelManager.Save(newBeatmapInfo, newBeatmap); + beatmapModelManager.Save(newBeatmapInfo, newBeatmap, creationParameters.ReferenceBeatmapSkin); workingBeatmapCache.Invalidate(targetBeatmapSet); return GetWorkingBeatmap(newBeatmap.BeatmapInfo); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c5578287e3..37d4dce60e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -9,6 +9,7 @@ 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; @@ -853,13 +854,14 @@ namespace osu.Game.Screens.Edit private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool clearAllObjects) => loader?.ScheduleSwitchToNewDifficulty(new NewDifficultyCreationParameters - { - BeatmapSet = editorBeatmap.BeatmapInfo.BeatmapSet, - Ruleset = rulesetInfo, - ReferenceBeatmap = playableBeatmap, - ClearAllObjects = clearAllObjects, - EditorState = GetState() - }); + ( + editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(), + rulesetInfo, + playableBeatmap, + editorBeatmap.BeatmapSkin, + clearAllObjects, + GetState() + )); private EditorMenuItem createDifficultySwitchMenu() { diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs index dd03fd3644..aa7dac609b 100644 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs @@ -1,8 +1,11 @@ // Copyright (c) ppy Pty Ltd . 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 { @@ -11,26 +14,47 @@ namespace osu.Game.Screens.Edit /// /// The that should contain the newly-created difficulty. /// - public BeatmapSetInfo BeatmapSet { get; set; } + public BeatmapSetInfo BeatmapSet { get; } /// /// The that the new difficulty should be playable for. /// - public RulesetInfo Ruleset { get; set; } + public RulesetInfo Ruleset { get; } /// /// A reference upon which the new difficulty should be based. /// - public IBeatmap ReferenceBeatmap { get; set; } + public IBeatmap ReferenceBeatmap { get; } + + /// + /// A reference that the new difficulty should base its own skin upon. + /// + public ISkin? ReferenceBeatmapSkin { get; } /// /// Whether all objects should be cleared from the new difficulty. /// - public bool ClearAllObjects { get; set; } + public bool ClearAllObjects { get; } /// /// The saved state of the previous which should be restored upon opening the newly-created difficulty. /// - public EditorState EditorState { get; set; } + public EditorState EditorState { get; } + + public NewDifficultyCreationParameters( + BeatmapSetInfo beatmapSet, + RulesetInfo ruleset, + IBeatmap referenceBeatmap, + ISkin? referenceBeatmapSkin, + bool clearAllObjects, + EditorState editorState) + { + BeatmapSet = beatmapSet; + Ruleset = ruleset; + ReferenceBeatmap = referenceBeatmap; + ReferenceBeatmapSkin = referenceBeatmapSkin; + ClearAllObjects = clearAllObjects; + EditorState = editorState; + } } } From 6fd663a718b30de3b3064edc4a986d4cc61dfa2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 19:51:02 +0100 Subject: [PATCH 11/37] Apply some renames to convey difference between creation options better --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- .../Screens/Edit/NewDifficultyCreationParameters.cs | 13 +++++++++---- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index a9d0576696..87051ef650 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -113,7 +113,7 @@ namespace osu.Game.Beatmaps /// The new difficulty will be backed by a model /// and represented by the returned . /// - public virtual WorkingBeatmap CreateNewBlankDifficulty(NewDifficultyCreationParameters creationParameters) + public virtual WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) { var referenceBeatmap = creationParameters.ReferenceBeatmap; var targetBeatmapSet = creationParameters.BeatmapSet; @@ -121,7 +121,7 @@ namespace osu.Game.Beatmaps BeatmapInfo newBeatmapInfo; IBeatmap newBeatmap; - if (creationParameters.ClearAllObjects) + if (creationParameters.CreateBlank) { newBeatmapInfo = new BeatmapInfo(creationParameters.Ruleset, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone()); newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs index 472f0e8948..138e13bda1 100644 --- a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs +++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty) { - HeaderText = "Would you like to clear all objects?"; + HeaderText = "Would you like to create a blank difficulty?"; Icon = FontAwesome.Regular.Clone; diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 169b601a94..be3e68c857 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Edit { try { - return beatmapManager.CreateNewBlankDifficulty(creationParameters); + return beatmapManager.CreateNewDifficulty(creationParameters); } catch (Exception ex) { diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs index aa7dac609b..a6458a9456 100644 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs @@ -32,9 +32,14 @@ namespace osu.Game.Screens.Edit public ISkin? ReferenceBeatmapSkin { get; } /// - /// Whether all objects should be cleared from the new difficulty. + /// Whether the new difficulty should be blank. /// - public bool ClearAllObjects { get; } + /// + /// A blank difficulty will have no objects, no control points other than timing points taken from + /// and will not share values with , + /// but it will share metadata and timing information with . + /// + public bool CreateBlank { get; } /// /// The saved state of the previous which should be restored upon opening the newly-created difficulty. @@ -46,14 +51,14 @@ namespace osu.Game.Screens.Edit RulesetInfo ruleset, IBeatmap referenceBeatmap, ISkin? referenceBeatmapSkin, - bool clearAllObjects, + bool createBlank, EditorState editorState) { BeatmapSet = beatmapSet; Ruleset = ruleset; ReferenceBeatmap = referenceBeatmap; ReferenceBeatmapSkin = referenceBeatmapSkin; - ClearAllObjects = clearAllObjects; + CreateBlank = createBlank; EditorState = editorState; } } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index ff09598eef..8c8a106791 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(NewDifficultyCreationParameters creationParameters) + public override WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) { // don't actually care about properly creating a difficulty for this context. return TestBeatmap; From 90c48de9f8d68d2cdafcdde863ea671320449e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 20:01:47 +0100 Subject: [PATCH 12/37] Add failing test coverage for save of copied beatmap keeping old beatmap file --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 90b1d3a6f9..0025e88e62 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -12,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; @@ -236,16 +237,24 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); + + BeatmapInfo refetchedBeatmap = null; + Live refetchedBeatmapSet = null; + + AddStep("refetch from database", () => + { + refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); + }); + AddAssert("new beatmap persisted", () => { - var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); - var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); - - return beatmap != null - && beatmap.DifficultyName == secondDifficultyName - && set != null - && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + return refetchedBeatmap != null + && refetchedBeatmap.DifficultyName == secondDifficultyName + && refetchedBeatmapSet != null + && refetchedBeatmapSet.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); }); + AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } [Test] From ecd6a68c6f0814cbd9a646f4dca4abf758162276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Feb 2022 20:05:32 +0100 Subject: [PATCH 13/37] Clear hash when creating copy of existing difficulty --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 87051ef650..4fa07ff518 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -136,6 +136,8 @@ namespace osu.Game.Beatmaps newBeatmapInfo.ID = Guid.NewGuid(); // clear difficulty name to avoid clashes on save. newBeatmapInfo.DifficultyName = string.Empty; + // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + newBeatmapInfo.Hash = string.Empty; } // populate circular beatmap set info <-> beatmap info references manually. From 13abc392bd139305ac876df335139580ce2f4035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 13 Feb 2022 18:54:52 +0100 Subject: [PATCH 14/37] Add failing test coverage for not copying online properties --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 0025e88e62..b4559ac9fa 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -201,6 +201,11 @@ namespace osu.Game.Tests.Visual.Editing new Colour4(0, 0, 255, 255) }); }); + AddStep("set status & online ID", () => + { + EditorBeatmap.BeatmapInfo.OnlineID = 123456; + EditorBeatmap.BeatmapInfo.Status = BeatmapOnlineStatus.WIP; + }); AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => @@ -235,6 +240,9 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4); AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2); + AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None); + AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); From 5dabc9282c0e1e3be5538cd4c7996d67eae6197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 13 Feb 2022 19:04:11 +0100 Subject: [PATCH 15/37] Change `BeatmapInfo` copy logic to be opt-in rather than opt-out --- osu.Game/Beatmaps/BeatmapManager.cs | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4fa07ff518..4dd0e08dab 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -131,13 +131,28 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); - // assign a new ID to the clone. - newBeatmapInfo.ID = Guid.NewGuid(); - // clear difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName = string.Empty; - // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. - newBeatmapInfo.Hash = string.Empty; + + var referenceBeatmapInfo = referenceBeatmap.BeatmapInfo; + newBeatmap.BeatmapInfo = newBeatmapInfo = new BeatmapInfo(referenceBeatmapInfo.Ruleset, referenceBeatmapInfo.Difficulty.Clone(), referenceBeatmapInfo.Metadata.DeepClone()) + { + // Only selected appropriate properties are copied over. + // Things like database ID, online status/ID, MD5 hash, star rating, etc. are omitted + // because they should not be copied over and/or they will be recomputed on save. + AudioLeadIn = referenceBeatmapInfo.AudioLeadIn, + StackLeniency = referenceBeatmapInfo.StackLeniency, + SpecialStyle = referenceBeatmapInfo.SpecialStyle, + LetterboxInBreaks = referenceBeatmapInfo.LetterboxInBreaks, + WidescreenStoryboard = referenceBeatmapInfo.WidescreenStoryboard, + EpilepsyWarning = referenceBeatmapInfo.EpilepsyWarning, + SamplesMatchPlaybackRate = referenceBeatmapInfo.SamplesMatchPlaybackRate, + DistanceSpacing = referenceBeatmapInfo.DistanceSpacing, + BeatDivisor = referenceBeatmapInfo.BeatDivisor, + GridSize = referenceBeatmapInfo.GridSize, + TimelineZoom = referenceBeatmapInfo.TimelineZoom, + Countdown = referenceBeatmapInfo.Countdown, + CountdownOffset = referenceBeatmapInfo.CountdownOffset, + Bookmarks = (int[])referenceBeatmapInfo.Bookmarks.Clone() + }; } // populate circular beatmap set info <-> beatmap info references manually. From e304c031dcdffdcbd66f0f896ee160f79ee7a86d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 21:53:56 +0900 Subject: [PATCH 16/37] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 1a2859c851..6b3142fbdc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a9c0226951..847832f8ac 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4a1dc53281..00c9653146 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From c5019fefb0c9ed0a212167851acc082fe5f4e618 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:35:08 +0900 Subject: [PATCH 17/37] Update CI runs to target net6.0 --- .github/workflows/ci.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c52802cf6..ec3816d541 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,10 +20,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # FIXME: libavformat is not included in Ubuntu. Let's fix that. # https://github.com/ppy/osu-framework/issues/4349 @@ -65,10 +65,10 @@ jobs: run: | $VM_ASSETS/select-xamarin-sdk-v2.sh --mono=6.12 --android=11.2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. @@ -84,10 +84,10 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono # cannot accept .sln(f) files as arguments. @@ -102,17 +102,17 @@ jobs: - name: Checkout uses: actions/checkout@v2 - # FIXME: Tools won't run in .NET 5.0 unless you install 3.1.x LTS side by side. + # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side. # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e - name: Install .NET 3.1.x LTS uses: actions/setup-dotnet@v1 with: dotnet-version: "3.1.x" - - name: Install .NET 5.0.x + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: - dotnet-version: "5.0.x" + dotnet-version: "6.0.x" - name: Restore Tools run: dotnet tool restore From 70ba6fb7dc2e48712886a6ae3ad4eb22079e4136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:35:30 +0900 Subject: [PATCH 18/37] Update .NET version in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b1dfcab416..7ace47a74f 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir Please make sure you have the following prerequisites: -- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) installed. +- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed. - When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). - When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When running on Linux, please have a system-wide FFmpeg installation available to support video decoding. From 9b7d9d42bcd01f84375cc38d5f6bf4ab74f996a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:48:04 +0900 Subject: [PATCH 19/37] Update reference to `NetAnalyzers` --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 894ea25c8b..6e7015fa26 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,7 +18,7 @@ - + $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset From 9ad7b5d51caab9433c2eaaecbce6a1e4dc687516 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 14 Feb 2022 22:52:30 +0900 Subject: [PATCH 20/37] Remove no longer required `NoWarn` spec --- Directory.Build.props | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 6e7015fa26..c1682638c2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,13 +32,8 @@ NU1701: DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway. This is required due to https://github.com/NuGet/Home/issues/5740 - - CA9998: - Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated. - The entire package will be able to be removed after migrating to .NET 5, - as analysers are shipped as part of the .NET 5 SDK anyway. --> - $(NoWarn);NU1701;CA9998 + $(NoWarn);NU1701 false From b581ca14cca29e7c8fb28bcfbb55e0aec0ae7389 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:32:38 +0900 Subject: [PATCH 21/37] Update usages in line with `BorderColour` type change --- osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs index deb2e6baf6..c6477d1781 100644 --- a/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/SwitchButton.cs @@ -114,7 +114,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 private class CircularBorderContainer : CircularContainer { - public void TransformBorderTo(SRGBColour colour) + public void TransformBorderTo(ColourInfo colour) => this.TransformTo(nameof(BorderColour), colour, 250, Easing.OutQuint); } } From 02f58a82fc4682df861a6069a12316d3bf474084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:35:08 +0900 Subject: [PATCH 22/37] Use `WaitSafely()` in tests where it was not already being used --- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 3 ++- .../Multiplayer/TestSceneMultiplayerPlaylist.cs | 10 +++++----- .../Multiplayer/TestSceneMultiplayerQueueList.cs | 12 ++++++------ .../Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index c7eeff81fe..cd14a98751 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using NUnit.Framework; +using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; @@ -71,7 +72,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers - })); + }).WaitSafely()); AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 936798e6b4..5dd9fb9fe2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] @@ -97,19 +97,19 @@ namespace osu.Game.Tests.Visual.Multiplayer addItemStep(); addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(1, 0); assertItemInQueueListStep(2, 0); assertItemInQueueListStep(3, 1); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(2, 0); assertItemInHistoryListStep(1, 1); assertItemInQueueListStep(3, 0); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); assertItemInHistoryListStep(3, 0); assertItemInHistoryListStep(2, 1); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestListsClearedWhenRoomLeft() { addItemStep(); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); AddStep("leave room", () => RoomManager.PartRoom()); AddUntilStep("wait for room part", () => !RoomJoined); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index ddf794b437..80b20ed59f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -65,13 +65,13 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0); }); - AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); } [Test] public void TestDeleteButtonAlwaysVisibleForHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); @@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 })); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestCurrentItemDoesNotHaveDeleteButton() { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely()); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Multiplayer assertDeleteButtonVisibility(0, false); assertDeleteButtonVisibility(1, true); - AddStep("finish current item", () => Client.FinishCurrentItem()); + AddStep("finish current item", () => Client.FinishCurrentItem().WaitSafely()); AddUntilStep("wait for next item to be selected", () => Client.Room?.Settings.PlaylistItemId == 2); AddUntilStep("wait for two items in playlist", () => playlist.ChildrenOfType().Count() == 2); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapID = importedBeatmap.OnlineID, }); - Client.AddUserPlaylistItem(userId(), item); + Client.AddUserPlaylistItem(userId(), item).WaitSafely(); itemId = item.ID; }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 781f0a1824..2837d75553 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change match type", () => client.ChangeSettings(new MultiplayerRoomSettings { MatchType = MatchType.TeamVersus - })); + }).WaitSafely()); AddUntilStep("api room updated to team versus", () => client.APIRoom?.Type.Value == MatchType.TeamVersus); } From 8da0800d7fc70d213ab5401e97d537f308d75cf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 02:47:35 +0900 Subject: [PATCH 23/37] Update `ChangeFocus` usage in line with framework changes --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 3fd56ece58..27743e709f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -246,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { base.LoadComplete(); - Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox)); passwordTextBox.OnCommit += (_, __) => performJoin(); } From 4bd58cfde160398726aaffb80e93d630a34029f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 18:52:19 +0100 Subject: [PATCH 24/37] Update one more custom transform with `BorderColour` type change --- osu.Game/Overlays/Volume/MuteButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index bcc9394aba..e9d3b31207 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -79,13 +79,13 @@ namespace osu.Game.Overlays.Volume protected override bool OnHover(HoverEvent e) { - Content.TransformTo, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint); + Content.TransformTo, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - Content.TransformTo, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint); + Content.TransformTo, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint); } } } From 7e75fa7117ebabf0e5cb1c8ad1b5e5d79349047a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:52:08 +0100 Subject: [PATCH 25/37] Revert "Change `BeatmapInfo` copy logic to be opt-in rather than opt-out" This reverts commit 5dabc9282c0e1e3be5538cd4c7996d67eae6197e. --- osu.Game/Beatmaps/BeatmapManager.cs | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4dd0e08dab..4fa07ff518 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -131,28 +131,13 @@ namespace osu.Game.Beatmaps else { newBeatmap = referenceBeatmap.Clone(); - - var referenceBeatmapInfo = referenceBeatmap.BeatmapInfo; - newBeatmap.BeatmapInfo = newBeatmapInfo = new BeatmapInfo(referenceBeatmapInfo.Ruleset, referenceBeatmapInfo.Difficulty.Clone(), referenceBeatmapInfo.Metadata.DeepClone()) - { - // Only selected appropriate properties are copied over. - // Things like database ID, online status/ID, MD5 hash, star rating, etc. are omitted - // because they should not be copied over and/or they will be recomputed on save. - AudioLeadIn = referenceBeatmapInfo.AudioLeadIn, - StackLeniency = referenceBeatmapInfo.StackLeniency, - SpecialStyle = referenceBeatmapInfo.SpecialStyle, - LetterboxInBreaks = referenceBeatmapInfo.LetterboxInBreaks, - WidescreenStoryboard = referenceBeatmapInfo.WidescreenStoryboard, - EpilepsyWarning = referenceBeatmapInfo.EpilepsyWarning, - SamplesMatchPlaybackRate = referenceBeatmapInfo.SamplesMatchPlaybackRate, - DistanceSpacing = referenceBeatmapInfo.DistanceSpacing, - BeatDivisor = referenceBeatmapInfo.BeatDivisor, - GridSize = referenceBeatmapInfo.GridSize, - TimelineZoom = referenceBeatmapInfo.TimelineZoom, - Countdown = referenceBeatmapInfo.Countdown, - CountdownOffset = referenceBeatmapInfo.CountdownOffset, - Bookmarks = (int[])referenceBeatmapInfo.Bookmarks.Clone() - }; + newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); + // assign a new ID to the clone. + newBeatmapInfo.ID = Guid.NewGuid(); + // clear difficulty name to avoid clashes on save. + newBeatmapInfo.DifficultyName = string.Empty; + // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + newBeatmapInfo.Hash = string.Empty; } // populate circular beatmap set info <-> beatmap info references manually. From 40cfee34211387866835612abf142b893b5498c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:54:40 +0100 Subject: [PATCH 26/37] Explicitly reset online ID and beatmap status on copy --- osu.Game/Beatmaps/BeatmapManager.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4fa07ff518..884209ee56 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -136,8 +136,11 @@ namespace osu.Game.Beatmaps newBeatmapInfo.ID = Guid.NewGuid(); // clear difficulty name to avoid clashes on save. newBeatmapInfo.DifficultyName = string.Empty; - // also clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. + // 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; } // populate circular beatmap set info <-> beatmap info references manually. From 1685e214d36463e225971c6ffef3fad73aae0735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 19:59:54 +0100 Subject: [PATCH 27/37] Adjust test coverage to cover desired copy naming scheme --- .../Editing/TestSceneEditorBeatmapCreation.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b4559ac9fa..0a2f622da1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -172,10 +172,10 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCopyDifficulty() { - string firstDifficultyName = Guid.NewGuid().ToString(); - string secondDifficultyName = Guid.NewGuid().ToString(); + string originalDifficultyName = Guid.NewGuid().ToString(); + string copyDifficultyName = $"{originalDifficultyName} (copy)"; - AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { @@ -210,11 +210,11 @@ namespace osu.Game.Tests.Visual.Editing AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => { - var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == firstDifficultyName); + var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName); var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); return beatmap != null - && beatmap.DifficultyName == firstDifficultyName + && beatmap.DifficultyName == originalDifficultyName && set != null && set.PerformRead(s => s.Beatmaps.Single().ID == beatmap.ID); }); @@ -228,9 +228,10 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { string difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName != firstDifficultyName; + return difficultyName != null && difficultyName != originalDifficultyName; }); + AddAssert("created difficulty has copy suffix in name", () => EditorBeatmap.BeatmapInfo.DifficultyName == copyDifficultyName); AddAssert("created difficulty has timing point", () => { var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); @@ -243,7 +244,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("status not copied", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.None); AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1); - AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName); AddStep("save beatmap", () => Editor.Save()); BeatmapInfo refetchedBeatmap = null; @@ -251,16 +251,19 @@ namespace osu.Game.Tests.Visual.Editing AddStep("refetch from database", () => { - refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == secondDifficultyName); + refetchedBeatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == copyDifficultyName); refetchedBeatmapSet = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID); }); AddAssert("new beatmap persisted", () => { return refetchedBeatmap != null - && refetchedBeatmap.DifficultyName == secondDifficultyName + && refetchedBeatmap.DifficultyName == copyDifficultyName && refetchedBeatmapSet != null - && refetchedBeatmapSet.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName)); + && refetchedBeatmapSet.PerformRead(s => + s.Beatmaps.Count == 2 + && s.Beatmaps.Any(b => b.DifficultyName == originalDifficultyName) + && s.Beatmaps.Any(b => b.DifficultyName == copyDifficultyName)); }); AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2)); } From 62214471647382ff03bf1449db2c03bde8cfa9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 20:19:12 +0100 Subject: [PATCH 28/37] Append copy suffix on creating copy of difficulty --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 9 ++++++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 884209ee56..d1b8e88743 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -134,8 +134,8 @@ namespace osu.Game.Beatmaps newBeatmap.BeatmapInfo = newBeatmapInfo = referenceBeatmap.BeatmapInfo.Clone(); // assign a new ID to the clone. newBeatmapInfo.ID = Guid.NewGuid(); - // clear difficulty name to avoid clashes on save. - newBeatmapInfo.DifficultyName = string.Empty; + // 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. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 37d4dce60e..7a3c4f2a19 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -857,7 +857,7 @@ namespace osu.Game.Screens.Edit ( editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull(), rulesetInfo, - playableBeatmap, + editorBeatmap, editorBeatmap.BeatmapSkin, clearAllObjects, GetState() diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index be3e68c857..505a57f157 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -84,7 +84,14 @@ namespace osu.Game.Screens.Edit { try { - return beatmapManager.CreateNewDifficulty(creationParameters); + 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)); } catch (Exception ex) { From e45a2ae0fc9b24af51454589be31923cb80543ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 14 Feb 2022 20:56:05 +0100 Subject: [PATCH 29/37] Restructure difficulty copy flow to adapt to latest changes --- osu.Game/Beatmaps/BeatmapManager.cs | 83 +++++++++++-------- .../Screens/Edit/CreateNewDifficultyDialog.cs | 12 +-- osu.Game/Screens/Edit/Editor.cs | 27 ++---- osu.Game/Screens/Edit/EditorLoader.cs | 21 ++--- .../Edit/NewDifficultyCreationParameters.cs | 65 --------------- osu.Game/Tests/Visual/EditorTestScene.cs | 8 +- 6 files changed, 83 insertions(+), 133 deletions(-) delete mode 100644 osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index d1b8e88743..777d5db2ad 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -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 } /// - /// Add a new difficulty to the beatmap set represented by the provided . + /// Add a new difficulty to the provided based on the provided . /// The new difficulty will be backed by a model /// and represented by the returned . /// - public virtual WorkingBeatmap CreateNewDifficulty(NewDifficultyCreationParameters creationParameters) + /// + /// Contrary to , this method does not preserve hitobjects and beatmap-level settings from . + /// The created beatmap will have zero hitobjects and will have default settings (including difficulty settings), but will preserve metadata and existing timing points. + /// + /// The to add the new difficulty to. + /// The to use as a baseline reference when creating the new difficulty. + /// The ruleset with which the new difficulty should be created. + 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); + } + + /// + /// Add a copy of the provided to the provided . + /// The new difficulty will be backed by a model + /// and represented by the returned . + /// + /// + /// Contrary to , this method creates a nearly-exact copy of + /// (with the exception of a few key properties that cannot be copied under any circumstance, like difficulty name, beatmap hash, or online status). + /// + /// The to add the copy to. + /// The to be copied. + 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). - /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs index 138e13bda1..aa6ca280ee 100644 --- a/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs +++ b/osu.Game/Screens/Edit/CreateNewDifficultyDialog.cs @@ -10,11 +10,11 @@ namespace osu.Game.Screens.Edit { /// /// 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. + /// A value of in the createCopy 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. /// - 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 { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 7a3c4f2a19..c2775ae101 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -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 /// /// Creates an instance representing the current state of the editor. /// - /// - /// The next beatmap to be shown, in the case of difficulty switch. + /// + /// The ruleset of the next beatmap to be shown, in the case of difficulty switch. /// indicates that the beatmap will not be changing. /// - 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 }; /// @@ -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() { diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 505a57f157..0a2b8437fa 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -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); diff --git a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs b/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs deleted file mode 100644 index a6458a9456..0000000000 --- a/osu.Game/Screens/Edit/NewDifficultyCreationParameters.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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 - { - /// - /// The that should contain the newly-created difficulty. - /// - public BeatmapSetInfo BeatmapSet { get; } - - /// - /// The that the new difficulty should be playable for. - /// - public RulesetInfo Ruleset { get; } - - /// - /// A reference upon which the new difficulty should be based. - /// - public IBeatmap ReferenceBeatmap { get; } - - /// - /// A reference that the new difficulty should base its own skin upon. - /// - public ISkin? ReferenceBeatmapSkin { get; } - - /// - /// Whether the new difficulty should be blank. - /// - /// - /// A blank difficulty will have no objects, no control points other than timing points taken from - /// and will not share values with , - /// but it will share metadata and timing information with . - /// - public bool CreateBlank { get; } - - /// - /// The saved state of the previous which should be restored upon opening the newly-created difficulty. - /// - 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; - } - } -} diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 8c8a106791..24015590e2 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -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; From f5d0eb41cb1041c999921ada5c271411de00513f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 15 Feb 2022 02:42:37 +0300 Subject: [PATCH 30/37] Update further `ChangeFocus` usages --- osu.Game/Overlays/Login/LoginPanel.cs | 3 ++- osu.Game/Overlays/LoginOverlay.cs | 2 +- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Login/LoginPanel.cs b/osu.Game/Overlays/Login/LoginPanel.cs index d1e5bfe809..481abd48ab 100644 --- a/osu.Game/Overlays/Login/LoginPanel.cs +++ b/osu.Game/Overlays/Login/LoginPanel.cs @@ -183,7 +183,8 @@ namespace osu.Game.Overlays.Login break; } - if (form != null) GetContainingInputManager()?.ChangeFocus(form); + if (form != null) + ScheduleAfterChildren(() => GetContainingInputManager()?.ChangeFocus(form)); }); public override bool AcceptsFocus => true; diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index f3562aa6d9..9b2d7ca1ee 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays panel.Bounding = true; this.FadeIn(transition_time, Easing.OutQuint); - GetContainingInputManager().ChangeFocus(panel); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(panel)); } protected override void PopOut() diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index f0ca3e1bbc..571dfb3f6f 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Edit.Setup base.LoadComplete(); if (string.IsNullOrEmpty(ArtistTextBox.Current.Value)) - GetContainingInputManager().ChangeFocus(ArtistTextBox); + ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(ArtistTextBox)); ArtistTextBox.Current.BindValueChanged(artist => transferIfRomanised(artist.NewValue, RomanisedArtistTextBox)); TitleTextBox.Current.BindValueChanged(title => transferIfRomanised(title.NewValue, RomanisedTitleTextBox)); From 60153bb69df75819e7e2830596884bbab8b04624 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:22:14 +0900 Subject: [PATCH 31/37] Update nuget packages to highest usable versions EF packages are intentionally pinned to 5.0.14 as higher versions no longer support `netstandard2.1`, which we require for xamarin projects. --- ...u.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- ....Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- osu.Desktop/osu.Desktop.csproj | 9 +++++--- .../osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- .../osu.Game.Tournament.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 22 +++++++++---------- 13 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index cb922c5a58..c305872288 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..ec90885cd9 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 33ad0ac4f7..f2e143a9c5 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5ecd9cc675..ec90885cd9 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 5e203af1f2..b1117bf796 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -26,10 +26,13 @@ - + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 434c0e0367..2bdb6a650c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index fc6d900567..a6c614f22f 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ddad2adfea..ff49d6d4dd 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index bd4c3d3345..a2e54f5cdc 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index a6b8eb8651..debddae037 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index acf1e8470b..40969c8b29 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index c7314a4969..9fd73f2c1b 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 847832f8ac..7b50c804ff 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,27 +18,27 @@ - + - - + + - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + From 28b45fa8999ba897fa93c0f4a4a5fc1e3700a71e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:40:58 +0900 Subject: [PATCH 32/37] Add assertions against null reference for connection usages --- .../Multiplayer/OnlineMultiplayerClient.cs | 29 +++++++++++++++++++ .../Online/Spectator/OnlineSpectatorClient.cs | 11 +++++++ 2 files changed, 40 insertions(+) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 3794bec228..ad898759ff 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -4,6 +4,7 @@ #nullable enable using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -79,6 +80,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } @@ -87,6 +90,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.FromCanceled(new CancellationToken(true)); + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.LeaveRoom)); } @@ -95,6 +100,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId); } @@ -103,6 +110,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId); } @@ -111,6 +120,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeSettings), settings); } @@ -119,6 +130,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeState), newState); } @@ -127,6 +140,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeBeatmapAvailability), newBeatmapAvailability); } @@ -135,6 +150,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.ChangeUserMods), newMods); } @@ -143,6 +160,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.SendMatchRequest), request); } @@ -151,6 +170,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.StartMatch)); } @@ -159,6 +180,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.AbortGameplay)); } @@ -167,6 +190,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } @@ -175,6 +200,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item); } @@ -183,6 +210,8 @@ namespace osu.Game.Online.Multiplayer if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 753796158e..ddde69c627 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -3,6 +3,7 @@ #nullable enable +using System.Diagnostics; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; @@ -51,6 +52,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); } @@ -59,6 +62,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), data); } @@ -67,6 +72,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state); } @@ -75,6 +82,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } @@ -83,6 +92,8 @@ namespace osu.Game.Online.Spectator if (!IsConnected.Value) return Task.CompletedTask; + Debug.Assert(connection != null); + return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } } From 8ec28dc8bcc1ab691af17e3124b046a6a27a1037 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 13:41:10 +0900 Subject: [PATCH 33/37] Update `OsuDbContext` in line with EF changes --- osu.Game/Database/OsuDbContext.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 441b090a6e..79183b6f0e 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; using osu.Framework.Statistics; @@ -12,8 +11,9 @@ using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Scoring; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; +using SQLitePCL; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace osu.Game.Database { @@ -40,10 +40,10 @@ namespace osu.Game.Database static OsuDbContext() { // required to initialise native SQLite libraries on some platforms. - SQLitePCL.Batteries_V2.Init(); + Batteries_V2.Init(); // https://github.com/aspnet/EntityFrameworkCore/issues/9994#issuecomment-508588678 - SQLitePCL.raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); + raw.sqlite3_config(2 /*SQLITE_CONFIG_MULTITHREAD*/); } /// @@ -116,7 +116,6 @@ namespace osu.Game.Database optionsBuilder // this is required for the time being due to the way we are querying in places like BeatmapStore. // if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. - .ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) .UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseLoggerFactory(logger.Value); } From 334fe1f1203ac02f507132bd1d64f00139dfb81d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 15:23:14 +0900 Subject: [PATCH 34/37] Add `AsSplitQuery` specification to avoid optimisation recommendation log messages --- osu.Game/Database/EFToRealmMigrator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 0bb5388d55..c9deee19fe 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -215,7 +215,8 @@ namespace osu.Game.Database .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(s => s.Metadata); + .Include(s => s.Metadata) + .AsSplitQuery(); log("Beginning beatmaps migration to realm"); @@ -344,7 +345,8 @@ namespace osu.Game.Database .Include(s => s.Ruleset) .Include(s => s.BeatmapInfo) .Include(s => s.Files) - .ThenInclude(f => f.FileInfo); + .ThenInclude(f => f.FileInfo) + .AsSplitQuery(); log("Beginning scores migration to realm"); @@ -434,6 +436,7 @@ namespace osu.Game.Database var existingSkins = db.SkinInfo .Include(s => s.Files) .ThenInclude(f => f.FileInfo) + .AsSplitQuery() .ToList(); // previous entries in EF are removed post migration. From 2c3e50a450359d76c95fc4628b391733dc354f4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 15:46:25 +0900 Subject: [PATCH 35/37] Update package references in xamarin `props` files --- osu.Android.props | 2 +- osu.iOS.props | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 6b3142fbdc..eab2be1b72 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 00c9653146..03a105673c 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -79,15 +79,15 @@ - - - + + + - + - - + + From d79845fb1df28e63545aa0f4bf9552e16965d9bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 15 Feb 2022 17:06:33 +0900 Subject: [PATCH 36/37] Revert `NUnit3TestAdaptor` (again) Console output is still broken. See https://github.com/ppy/osu/runs/5196023462?check_suite_focus=true. --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 2bdb6a650c..434c0e0367 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index a6c614f22f..fc6d900567 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index ff49d6d4dd..ddad2adfea 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index a2e54f5cdc..bd4c3d3345 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index debddae037..a6b8eb8651 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 40969c8b29..acf1e8470b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 9fd73f2c1b..c7314a4969 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 6e99fe04c3b71ce5a36609062ceb08bd6fb1e400 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 16 Feb 2022 00:39:43 +0900 Subject: [PATCH 37/37] Revert more NUnit test adapter bumps --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 2 +- .../osu.Game.Rulesets.Pippidon.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index c305872288..cb922c5a58 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index ec90885cd9..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index f2e143a9c5..33ad0ac4f7 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -12,7 +12,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index ec90885cd9..5ecd9cc675 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -12,7 +12,7 @@ - +