1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 18:03:11 +08:00

Merge branch 'master' into fix-multi-cache-dependencies

This commit is contained in:
Dean Herbert 2022-02-16 09:44:37 +09:00
commit 187870b794
42 changed files with 423 additions and 144 deletions

View File

@ -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

View File

@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (Second Client)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--debug-client-id=1" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@ -12,9 +12,9 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net5.0" />
<option name="PROJECT_TFM" value="net6.0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>
</component>

View File

@ -18,7 +18,7 @@
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
@ -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>$(NoWarn);NU1701;CA9998</NoWarn>
<NoWarn>$(NoWarn);NU1701</NoWarn>
</PropertyGroup>
<PropertyGroup Label="Nuget">
<IsPackable>false</IsPackable>

View File

@ -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.

View File

@ -20,7 +20,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>osu.Game.Rulesets.EmptyFreeform.Tests</RootNamespace>
</PropertyGroup>
</Project>
</Project>

View File

@ -20,7 +20,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
</PropertyGroup>
</Project>
</Project>

View File

@ -20,7 +20,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>osu.Game.Rulesets.EmptyScrolling.Tests</RootNamespace>
</PropertyGroup>
</Project>
</Project>

View File

@ -20,7 +20,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
</PropertyGroup>
</Project>
</Project>

View File

@ -52,10 +52,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.204.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.214.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
<PackageReference Include="Realm" Version="10.8.0" />
<PackageReference Include="Realm" Version="10.9.0" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project">
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<OutputType>WinExe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
@ -26,10 +26,13 @@
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="System.IO.Packaging" Version="5.0.0" />
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.14">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
</ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@ -9,9 +9,9 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -9,9 +9,9 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -10,9 +10,9 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -9,9 +9,9 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -6,15 +6,23 @@ 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;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database;
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;
@ -92,12 +100,27 @@ 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();
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", () =>
{
@ -111,13 +134,27 @@ 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<EditorBeatmap>().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 no objects", () => EditorBeatmap.HitObjects.Count == 0);
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = secondDifficultyName);
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () =>
@ -132,6 +169,105 @@ namespace osu.Game.Tests.Visual.Editing
});
}
[Test]
public void TestCopyDifficulty()
{
string originalDifficultyName = Guid.NewGuid().ToString();
string copyDifficultyName = $"{originalDifficultyName} (copy)";
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[]
{
new HitCircle
{
Position = new Vector2(0),
StartTime = 0
},
new HitCircle
{
Position = OsuPlayfield.BASE_SIZE,
StartTime = 1000
}
}));
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("set status & online ID", () =>
{
EditorBeatmap.BeatmapInfo.OnlineID = 123456;
EditorBeatmap.BeatmapInfo.Status = BeatmapOnlineStatus.WIP;
});
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () =>
{
var beatmap = beatmapManager.QueryBeatmap(b => b.DifficultyName == originalDifficultyName);
var set = beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID);
return beatmap != null
&& beatmap.DifficultyName == originalDifficultyName
&& 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<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
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();
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);
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("save beatmap", () => Editor.Save());
BeatmapInfo refetchedBeatmap = null;
Live<BeatmapSetInfo> refetchedBeatmapSet = null;
AddStep("refetch from database", () =>
{
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 == copyDifficultyName
&& refetchedBeatmapSet != null
&& 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));
}
[Test]
public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties()
{
@ -154,7 +290,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 +304,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<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;

View File

@ -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);
}

View File

@ -826,7 +826,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);
@ -857,11 +857,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());

View File

@ -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);

View File

@ -61,13 +61,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);
@ -79,7 +79,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 }));
@ -98,7 +98,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);
@ -106,7 +106,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<DrawableRoomPlaylistItem>().Count() == 2);
@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
BeatmapID = importedBeatmap.OnlineID,
});
Client.AddUserPlaylistItem(userId(), item);
Client.AddUserPlaylistItem(userId(), item).WaitSafely();
itemId = item.ID;
});

View File

@ -139,7 +139,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);
}

View File

@ -12,7 +12,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>tests.ruleset</CodeAnalysisRuleSet>

View File

@ -11,7 +11,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
@ -20,4 +20,4 @@
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu.Game.Tournament\osu.Game.Tournament.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@ -108,39 +108,73 @@ namespace osu.Game.Beatmaps
}
/// <summary>
/// Add a new difficulty to the beatmap set represented by the provided <see cref="BeatmapSetInfo"/>.
/// Add a new difficulty to the provided <paramref name="targetBeatmapSet"/> based on the provided <paramref name="referenceWorkingBeatmap"/>.
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned <see cref="WorkingBeatmap"/>.
/// </summary>
public virtual WorkingBeatmap CreateNewBlankDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo)
/// <remarks>
/// Contrary to <see cref="CopyExistingDifficulty"/>, this method does not preserve hitobjects and beatmap-level settings from <paramref name="referenceWorkingBeatmap"/>.
/// The created beatmap will have zero hitobjects and will have default settings (including difficulty settings), but will preserve metadata and existing timing points.
/// </remarks>
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the new difficulty to.</param>
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to use as a baseline reference when creating the new difficulty.</param>
/// <param name="rulesetInfo">The ruleset with which the new difficulty should be created.</param>
public virtual WorkingBeatmap CreateNewDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap, RulesetInfo rulesetInfo)
{
// 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 playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo);
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), referenceBeatmap.Metadata.DeepClone());
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone());
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints)
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin);
}
/// <summary>
/// Add a copy of the provided <paramref name="referenceWorkingBeatmap"/> to the provided <paramref name="targetBeatmapSet"/>.
/// The new difficulty will be backed by a <see cref="BeatmapInfo"/> model
/// and represented by the returned <see cref="WorkingBeatmap"/>.
/// </summary>
/// <remarks>
/// Contrary to <see cref="CreateNewDifficulty"/>, this method creates a nearly-exact copy of <paramref name="referenceWorkingBeatmap"/>
/// (with the exception of a few key properties that cannot be copied under any circumstance, like difficulty name, beatmap hash, or online status).
/// </remarks>
/// <param name="targetBeatmapSet">The <see cref="BeatmapSetInfo"/> to add the copy to.</param>
/// <param name="referenceWorkingBeatmap">The <see cref="WorkingBeatmap"/> to be copied.</param>
public virtual WorkingBeatmap CopyExistingDifficulty(BeatmapSetInfo targetBeatmapSet, WorkingBeatmap referenceWorkingBeatmap)
{
var newBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(referenceWorkingBeatmap.BeatmapInfo.Ruleset).Clone();
BeatmapInfo newBeatmapInfo;
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.
beatmapSetInfo.Beatmaps.Add(newBeatmapInfo);
newBeatmapInfo.BeatmapSet = beatmapSetInfo;
targetBeatmapSet.Beatmaps.Add(newBeatmap.BeatmapInfo);
newBeatmap.BeatmapInfo.BeatmapSet = targetBeatmapSet;
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
foreach (var timingPoint in referenceBeatmap.Beatmap.ControlPointInfo.TimingPoints)
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
beatmapModelManager.Save(newBeatmap.BeatmapInfo, newBeatmap, beatmapSkin);
beatmapModelManager.Save(newBeatmapInfo, newBeatmap);
workingBeatmapCache.Invalidate(beatmapSetInfo);
workingBeatmapCache.Invalidate(targetBeatmapSet);
return GetWorkingBeatmap(newBeatmap.BeatmapInfo);
}
// TODO: add back support for making a copy of another difficulty
// (likely via a separate `CopyDifficulty()` method).
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>

View File

@ -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.

View File

@ -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))

View File

@ -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*/);
}
/// <summary>
@ -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);
}

View File

@ -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);
}
}

View File

@ -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<byte[]> 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);
}
}
}

View File

@ -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<MultiplayerRoom>(new CancellationToken(true));
Debug.Assert(connection != null);
return connection.InvokeAsync<MultiplayerRoom>(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);
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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()

View File

@ -79,13 +79,13 @@ namespace osu.Game.Overlays.Volume
protected override bool OnHover(HoverEvent e)
{
Content.TransformTo<Container<Drawable>, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint);
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint);
return false;
}
protected override void OnHoverLost(HoverLostEvent e)
{
Content.TransformTo<Container<Drawable>, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.Edit
{
public class CreateNewDifficultyDialog : PopupDialog
{
/// <summary>
/// Delegate used to create new difficulties.
/// A value of <see langword="true"/> in the <c>createCopy</c> parameter
/// indicates that the new difficulty should be an exact copy of an existing one;
/// otherwise, the new difficulty should have its hitobjects and beatmap-level settings cleared.
/// </summary>
public delegate void CreateNewDifficulty(bool createCopy);
public CreateNewDifficultyDialog(CreateNewDifficulty createNewDifficulty)
{
HeaderText = "Would you like to create a blank difficulty?";
Icon = FontAwesome.Regular.Clone;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = "Yeah, let's start from scratch!",
Action = () => createNewDifficulty.Invoke(false)
},
new PopupDialogCancelButton
{
Text = "No, create an exact copy of this difficulty",
Action = () => createNewDifficulty.Invoke(true)
},
new PopupDialogCancelButton
{
Text = "I changed my mind, I want to keep editing this difficulty",
Action = () => { }
}
};
}
}
}

View File

@ -358,14 +358,14 @@ namespace osu.Game.Screens.Edit
/// <summary>
/// Creates an <see cref="EditorState"/> instance representing the current state of the editor.
/// </summary>
/// <param name="nextBeatmap">
/// The next beatmap to be shown, in the case of difficulty switch.
/// <param name="nextRuleset">
/// The ruleset of the next beatmap to be shown, in the case of difficulty switch.
/// <see langword="null"/> indicates that the beatmap will not be changing.
/// </param>
public EditorState GetState([CanBeNull] BeatmapInfo nextBeatmap = null) => new EditorState
public EditorState GetState([CanBeNull] RulesetInfo nextRuleset = null) => new EditorState
{
Time = clock.CurrentTimeAccurate,
ClipboardContent = nextBeatmap == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextBeatmap.Ruleset.ShortName ? Clipboard.Content.Value : string.Empty
ClipboardContent = nextRuleset == null || editorBeatmap.BeatmapInfo.Ruleset.ShortName == nextRuleset.ShortName ? Clipboard.Content.Value : string.Empty
};
/// <summary>
@ -841,7 +841,18 @@ 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, false);
return;
}
dialogOverlay.Push(new CreateNewDifficultyDialog(createCopy => switchToNewDifficulty(rulesetInfo, createCopy)));
}
private void switchToNewDifficulty(RulesetInfo rulesetInfo, bool createCopy)
=> loader?.ScheduleSwitchToNewDifficulty(editorBeatmap.BeatmapInfo, rulesetInfo, createCopy, GetState(rulesetInfo));
private EditorMenuItem createDifficultySwitchMenu()
{
@ -866,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()
{

View File

@ -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;
@ -80,12 +81,18 @@ namespace osu.Game.Screens.Edit
}
}
public void ScheduleSwitchToNewDifficulty(BeatmapSetInfo beatmapSetInfo, RulesetInfo rulesetInfo, EditorState editorState)
public void ScheduleSwitchToNewDifficulty(BeatmapInfo referenceBeatmapInfo, RulesetInfo rulesetInfo, bool createCopy, EditorState editorState)
=> scheduleDifficultySwitch(() =>
{
try
{
return beatmapManager.CreateNewBlankDifficulty(beatmapSetInfo, rulesetInfo);
// 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)
{

View File

@ -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));

View File

@ -246,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.LoadComplete();
Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextBox));
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(passwordTextBox));
passwordTextBox.OnCommit += (_, __) => performJoin();
}

View File

@ -136,7 +136,13 @@ 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 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;

View File

@ -18,27 +18,27 @@
</None>
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="AutoMapper" Version="11.0.0" />
<PackageReference Include="AutoMapper" Version="11.0.1" />
<PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.40" />
<PackageReference Include="Humanizer" Version="2.13.14" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.42" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="MessagePack" Version="2.3.85" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.11" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="6.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="6.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.LocalisationAnalyser" Version="2021.1210.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.8.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.204.0" />
<PackageReference Include="Realm" Version="10.9.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
<PackageReference Include="Sentry" Version="3.13.0" />
<PackageReference Include="Sentry" Version="3.14.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />

View File

@ -60,10 +60,10 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.204.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
<PropertyGroup>
<NoWarn>$(NoWarn);NU1605</NoWarn>
</PropertyGroup>
@ -79,15 +79,15 @@
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
<PackageReference Include="DiffPlex" Version="1.7.0" />
<PackageReference Include="Humanizer" Version="2.13.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.204.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.214.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2021.805.0" ExcludeAssets="all" />
<PackageReference Include="Realm" Version="10.8.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2021.1221.0" ExcludeAssets="all" />
<PackageReference Include="Realm" Version="10.9.0" />
</ItemGroup>
</Project>