diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ef729a779f..c728d89ed1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -126,6 +126,12 @@ jobs:
with:
dotnet-version: "6.0.x"
+ # macOS agents recently have empty NuGet config files, resulting in restore failures,
+ # see https://github.com/actions/virtual-environments/issues/5768
+ # Add the global nuget package source manually for now.
+ - name: Setup NuGet.Config
+ run: echo '' > ~/.config/NuGet/NuGet.Config
+
# Contrary to seemingly any other msbuild, msbuild running on macOS/Mono
# cannot accept .sln(f) files as arguments.
# Build just the main game for now.
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index b72df0a306..8b5431e2d6 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -16,3 +16,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead.
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
+M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
+M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
+M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
diff --git a/Directory.Build.props b/Directory.Build.props
index dbc84fb88f..235feea8ce 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,7 +1,7 @@
- 8.0
+ 9.0
true
enable
diff --git a/Gemfile.lock b/Gemfile.lock
index ddab497657..cae682ec2b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,20 +8,20 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
- aws-partitions (1.570.0)
- aws-sdk-core (3.130.0)
+ aws-partitions (1.601.0)
+ aws-sdk-core (3.131.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
- jmespath (~> 1.0)
- aws-sdk-kms (1.55.0)
+ jmespath (~> 1, >= 1.6.1)
+ aws-sdk-kms (1.57.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.113.0)
+ aws-sdk-s3 (1.114.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
- aws-sigv4 (1.4.0)
+ aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -36,7 +36,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.3)
- excon (0.92.1)
+ excon (0.92.3)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@@ -56,8 +56,8 @@ GEM
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
- faraday-multipart (1.0.3)
- multipart-post (>= 1.2, < 3)
+ faraday-multipart (1.0.4)
+ multipart-post (~> 2)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
- fastlane (2.205.1)
+ fastlane (2.206.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -110,9 +110,9 @@ GEM
souyuz (= 0.11.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.16.0)
- google-apis-core (>= 0.4, < 2.a)
- google-apis-core (0.4.2)
+ google-apis-androidpublisher_v3 (0.23.0)
+ google-apis-core (>= 0.6, < 2.a)
+ google-apis-core (0.6.0)
addressable (~> 2.5, >= 2.5.1)
googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.a)
@@ -121,19 +121,19 @@ GEM
retriable (>= 2.0, < 4.a)
rexml
webrick
- google-apis-iamcredentials_v1 (0.10.0)
- google-apis-core (>= 0.4, < 2.a)
- google-apis-playcustomapp_v1 (0.7.0)
- google-apis-core (>= 0.4, < 2.a)
- google-apis-storage_v1 (0.11.0)
- google-apis-core (>= 0.4, < 2.a)
+ google-apis-iamcredentials_v1 (0.12.0)
+ google-apis-core (>= 0.6, < 2.a)
+ google-apis-playcustomapp_v1 (0.9.0)
+ google-apis-core (>= 0.6, < 2.a)
+ google-apis-storage_v1 (0.16.0)
+ google-apis-core (>= 0.6, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
- google-cloud-storage (1.36.1)
+ google-cloud-storage (1.36.2)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@@ -141,7 +141,7 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
- googleauth (1.1.2)
+ googleauth (1.2.0)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -149,12 +149,12 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.4)
+ http-cookie (1.0.5)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.1)
- json (2.6.1)
- jwt (2.3.0)
+ json (2.6.2)
+ jwt (2.4.1)
memoist (0.16.2)
mini_magick (4.11.0)
mini_mime (1.1.2)
@@ -169,10 +169,10 @@ GEM
optparse (0.1.1)
os (1.1.4)
plist (3.6.0)
- public_suffix (4.0.6)
+ public_suffix (4.0.7)
racc (1.6.0)
rake (13.0.6)
- representable (3.1.1)
+ representable (3.2.0)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
@@ -182,9 +182,9 @@ GEM
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
security (0.1.3)
- signet (0.16.1)
+ signet (0.17.0)
addressable (~> 2.8)
- faraday (>= 0.17.5, < 3.0)
+ faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.8)
@@ -205,11 +205,11 @@ GEM
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.8.1)
+ unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
webrick (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.21.0)
+ xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/osu.Android.props b/osu.Android.props
index 8fcd7ef8c0..2ef3bb2dd1 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Android/AndroidMouseSettings.cs b/osu.Android/AndroidMouseSettings.cs
new file mode 100644
index 0000000000..54b787fd17
--- /dev/null
+++ b/osu.Android/AndroidMouseSettings.cs
@@ -0,0 +1,97 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.OS;
+using osu.Framework.Allocation;
+using osu.Framework.Android.Input;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Localisation;
+using osu.Game.Configuration;
+using osu.Game.Localisation;
+using osu.Game.Overlays.Settings;
+using osu.Game.Overlays.Settings.Sections.Input;
+
+namespace osu.Android
+{
+ public class AndroidMouseSettings : SettingsSubsection
+ {
+ private readonly AndroidMouseHandler mouseHandler;
+
+ protected override LocalisableString Header => MouseSettingsStrings.Mouse;
+
+ private Bindable handlerSensitivity = null!;
+
+ private Bindable localSensitivity = null!;
+
+ private Bindable relativeMode = null!;
+
+ public AndroidMouseSettings(AndroidMouseHandler mouseHandler)
+ {
+ this.mouseHandler = mouseHandler;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager osuConfig)
+ {
+ // use local bindable to avoid changing enabled state of game host's bindable.
+ handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy();
+ localSensitivity = handlerSensitivity.GetUnboundCopy();
+
+ relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
+
+ // High precision/pointer capture is only available on Android 8.0 and up
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
+ {
+ AddRange(new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = MouseSettingsStrings.HighPrecisionMouse,
+ TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip,
+ Current = relativeMode,
+ Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" },
+ },
+ new MouseSettings.SensitivitySetting
+ {
+ LabelText = MouseSettingsStrings.CursorSensitivity,
+ Current = localSensitivity,
+ },
+ });
+ }
+
+ AddRange(new Drawable[]
+ {
+ new SettingsCheckbox
+ {
+ LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust,
+ TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip,
+ Current = osuConfig.GetBindable(OsuSetting.MouseDisableWheel),
+ },
+ new SettingsCheckbox
+ {
+ LabelText = MouseSettingsStrings.DisableMouseButtons,
+ Current = osuConfig.GetBindable(OsuSetting.MouseDisableButtons),
+ },
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true);
+
+ handlerSensitivity.BindValueChanged(val =>
+ {
+ bool disabled = localSensitivity.Disabled;
+
+ localSensitivity.Disabled = false;
+ localSensitivity.Value = val.NewValue;
+ localSensitivity.Disabled = disabled;
+ }, true);
+
+ localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
+ }
+ }
+}
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 1edb867e05..636fc7d2df 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -7,7 +7,11 @@ using System;
using Android.App;
using Android.OS;
using osu.Framework.Allocation;
+using osu.Framework.Android.Input;
+using osu.Framework.Input.Handlers;
+using osu.Framework.Platform;
using osu.Game;
+using osu.Game.Overlays.Settings;
using osu.Game.Updater;
using osu.Game.Utils;
using Xamarin.Essentials;
@@ -75,10 +79,28 @@ namespace osu.Android
LoadComponentAsync(new GameplayScreenRotationLocker(), Add);
}
+ public override void SetHost(GameHost host)
+ {
+ base.SetHost(host);
+ host.Window.CursorState |= CursorState.Hidden;
+ }
+
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
+ public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
+ {
+ switch (handler)
+ {
+ case AndroidMouseHandler mh:
+ return new AndroidMouseSettings(mh);
+
+ default:
+ return base.CreateSettingsSubsectionFor(handler);
+ }
+ }
+
private class AndroidBatteryInfo : BatteryInfo
{
public override double ChargeLevel => Battery.ChargeLevel;
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index fc50ca9fa1..90b02c527b 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -26,6 +26,7 @@
true
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index f2531c1cae..d0b6953c30 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -57,7 +57,7 @@ namespace osu.Desktop
client.OnReady += onReady;
// safety measure for now, until we performance test / improve backoff for failed connections.
- client.OnConnectionFailed += (_, __) => client.Deinitialize();
+ client.OnConnectionFailed += (_, _) => client.Deinitialize();
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 6713136343..314a03a73e 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -20,25 +18,34 @@ using osu.Framework;
using osu.Framework.Logging;
using osu.Game.Updater;
using osu.Desktop.Windows;
+using osu.Framework.Input.Handlers;
+using osu.Framework.Input.Handlers.Joystick;
+using osu.Framework.Input.Handlers.Mouse;
+using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Threading;
using osu.Game.IO;
+using osu.Game.IPC;
+using osu.Game.Overlays.Settings;
+using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
- public OsuGameDesktop(string[] args = null)
+ private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
+
+ public OsuGameDesktop(string[]? args = null)
: base(args)
{
}
- public override StableStorage GetStorageForStableInstall()
+ public override StableStorage? GetStorageForStableInstall()
{
try
{
if (Host is DesktopGameHost desktopHost)
{
- string stablePath = getStableInstallPath();
+ string? stablePath = getStableInstallPath();
if (!string.IsNullOrEmpty(stablePath))
return new StableStorage(stablePath, desktopHost);
}
@@ -51,11 +58,11 @@ namespace osu.Desktop
return null;
}
- private string getStableInstallPath()
+ private string? getStableInstallPath()
{
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")) || File.Exists(Path.Combine(p, "osu!.cfg"));
- string stableInstallPath;
+ string? stableInstallPath;
if (OperatingSystem.IsWindows())
{
@@ -83,15 +90,15 @@ namespace osu.Desktop
}
[SupportedOSPlatform("windows")]
- private string getStableInstallPathFromRegistry()
+ private string? getStableInstallPathFromRegistry()
{
- using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
+ using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu"))
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
}
protected override UpdateManager CreateUpdateManager()
{
- string packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
+ string? packageManaged = Environment.GetEnvironmentVariable("OSU_EXTERNAL_UPDATE_PROVIDER");
if (!string.IsNullOrEmpty(packageManaged))
return new NoActionUpdateManager();
@@ -118,6 +125,8 @@ namespace osu.Desktop
LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
LoadComponentAsync(new ElevatedPrivilegesChecker(), Add);
+
+ osuSchemeLinkIPCChannel = new OsuSchemeLinkIPCChannel(Host, this);
}
public override void SetHost(GameHost host)
@@ -134,8 +143,26 @@ namespace osu.Desktop
desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
+ public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
+ {
+ switch (handler)
+ {
+ case ITabletHandler th:
+ return new TabletSettings(th);
+
+ case MouseHandler mh:
+ return new MouseSettings(mh);
+
+ case JoystickHandler jh:
+ return new JoystickSettings(jh);
+
+ default:
+ return base.CreateSettingsSubsectionFor(handler);
+ }
+ }
+
private readonly List importableFiles = new List();
- private ScheduledDelegate importSchedule;
+ private ScheduledDelegate? importSchedule;
private void fileDrop(string[] filePaths)
{
@@ -168,5 +195,11 @@ namespace osu.Desktop
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ osuSchemeLinkIPCChannel?.Dispose();
+ }
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 4ba9cc6a90..712f300671 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -11,6 +11,7 @@ using osu.Framework;
using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform;
+using osu.Game;
using osu.Game.IPC;
using osu.Game.Tournament;
using Squirrel;
@@ -65,19 +66,8 @@ namespace osu.Desktop
{
if (!host.IsPrimaryInstance)
{
- if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
- {
- var importer = new ArchiveImportIPCChannel(host);
-
- foreach (string file in args)
- {
- Console.WriteLine(@"Importing {0}", file);
- if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
- throw new TimeoutException(@"IPC took too long to send");
- }
-
+ if (trySendIPCMessage(host, cwd, args))
return;
- }
// we want to allow multiple instances to be started when in debug.
if (!DebugUtils.IsDebugBuild)
@@ -108,21 +98,49 @@ namespace osu.Desktop
}
}
+ private static bool trySendIPCMessage(IIpcHost host, string cwd, string[] args)
+ {
+ if (args.Length == 1 && args[0].StartsWith(OsuGameBase.OSU_PROTOCOL, StringComparison.Ordinal))
+ {
+ var osuSchemeLinkHandler = new OsuSchemeLinkIPCChannel(host);
+ if (!osuSchemeLinkHandler.HandleLinkAsync(args[0]).Wait(3000))
+ throw new IPCTimeoutException(osuSchemeLinkHandler.GetType());
+
+ return true;
+ }
+
+ if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
+ {
+ var importer = new ArchiveImportIPCChannel(host);
+
+ foreach (string file in args)
+ {
+ Console.WriteLine(@"Importing {0}", file);
+ if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
+ throw new IPCTimeoutException(importer.GetType());
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
[SupportedOSPlatform("windows")]
private static void setupSquirrel()
{
- SquirrelAwareApp.HandleEvents(onInitialInstall: (version, tools) =>
+ SquirrelAwareApp.HandleEvents(onInitialInstall: (_, tools) =>
{
tools.CreateShortcutForThisExe();
tools.CreateUninstallerRegistryEntry();
- }, onAppUpdate: (version, tools) =>
+ }, onAppUpdate: (_, tools) =>
{
tools.CreateUninstallerRegistryEntry();
- }, onAppUninstall: (version, tools) =>
+ }, onAppUninstall: (_, tools) =>
{
tools.RemoveShortcutForThisExe();
tools.RemoveUninstallerRegistryEntry();
- }, onEveryRun: (version, tools, firstRun) =>
+ }, onEveryRun: (_, _, _) =>
{
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
// causes the right-click context menu to function incorrectly.
diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
index bdc24315bf..5ffda6504e 100644
--- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs
+++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Benchmarks
realm = new RealmAccess(storage, OsuGameBase.CLIENT_DATABASE_FILENAME);
- realm.Run(r =>
+ realm.Run(_ =>
{
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
});
@@ -76,7 +76,7 @@ namespace osu.Game.Benchmarks
}
});
- done.Wait();
+ done.Wait(60000);
}
[Benchmark]
@@ -115,7 +115,7 @@ namespace osu.Game.Benchmarks
}
});
- done.Wait();
+ done.Wait(60000);
}
[Benchmark]
diff --git a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
index 54d26a0f3d..0de992c1df 100644
--- a/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.Tests
new JuiceStreamPathVertex(20, -5)
}));
- removeCount = path.RemoveVertices((_, i) => true);
+ removeCount = path.RemoveVertices((_, _) => true);
Assert.That(removeCount, Is.EqualTo(1));
Assert.That(path.Vertices, Is.EqualTo(new[]
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
index 731cb4e135..8dd6f82c57 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("change component scale", () => Player.ChildrenOfType().First().Scale = new Vector2(2f));
AddStep("update target", () => Player.ChildrenOfType().ForEach(LegacySkin.UpdateDrawableTarget));
AddStep("exit player", () => Player.Exit());
- CreateTest(null);
+ CreateTest();
}
AddAssert("legacy HUD combo counter hidden", () =>
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 655edf7e08..4886942dc6 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests
hyperDashCount = 0;
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
- Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
+ Player.DrawableRuleset.FrameStableComponents.OnUpdate += _ =>
{
var catcher = Player.ChildrenOfType().FirstOrDefault();
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
index 0fcdd34ca3..e79da667da 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch
protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
- protected override string ComponentName => Component.ToString().ToLower();
+ protected override string ComponentName => Component.ToString().ToLowerInvariant();
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
index 9951d736c3..2d01153f98 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using Newtonsoft.Json;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch.Difficulty
@@ -31,9 +30,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
}
- public override void FromDatabaseAttributes(IReadOnlyDictionary values)
+ public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo)
{
- base.FromDatabaseAttributes(values);
+ base.FromDatabaseAttributes(values, onlineInfo);
StarRating = values[ATTRIB_ID_AIM];
ApproachRate = values[ATTRIB_ID_APPROACH_RATE];
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
index 01156ab021..f31dc3ef9c 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs
@@ -135,15 +135,15 @@ namespace osu.Game.Rulesets.Catch.Edit
{
switch (BlueprintContainer.CurrentTool)
{
- case SelectTool _:
+ case SelectTool:
if (EditorBeatmap.SelectedHitObjects.Count == 0)
return null;
double minTime = EditorBeatmap.SelectedHitObjects.Min(hitObject => hitObject.StartTime);
return getLastSnappableHitObject(minTime);
- case FruitCompositionTool _:
- case JuiceStreamCompositionTool _:
+ case FruitCompositionTool:
+ case JuiceStreamCompositionTool:
if (!CursorInPlacementArea)
return null;
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
index 4390234b59..889d3909bd 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectUtils.cs
@@ -42,10 +42,10 @@ namespace osu.Game.Rulesets.Catch.Edit
case Droplet droplet:
return droplet is TinyDroplet ? PositionRange.EMPTY : new PositionRange(droplet.OriginalX);
- case JuiceStream _:
+ case JuiceStream:
return GetPositionRange(hitObject.NestedHitObjects);
- case BananaShower _:
+ case BananaShower:
// A banana shower occupies the whole screen width.
return new PositionRange(0, CatchPlayfield.WIDTH);
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
index 319cb1bfc9..5aac521d0b 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchSelectionHandler.cs
@@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Edit
{
switch (hitObject)
{
- case BananaShower _:
+ case BananaShower:
return false;
case JuiceStream juiceStream:
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 6a3ec336d1..efc841dfac 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -389,13 +389,13 @@ namespace osu.Game.Rulesets.Catch.UI
{
switch (source)
{
- case Fruit _:
+ case Fruit:
return caughtFruitPool.Get();
- case Banana _:
+ case Banana:
return caughtBananaPool.Get();
- case Droplet _:
+ case Droplet:
return caughtDropletPool.Get();
default:
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 2b4f497785..90cd7f57b5 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
switch (original)
{
- case IHasDistance _:
+ case IHasDistance:
{
var generator = new DistanceObjectPatternGenerator(Random, original, beatmap, lastPattern, originalBeatmap);
conversion = generator;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
index 8a5161be79..680732f26e 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using Newtonsoft.Json;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Mania.Difficulty
@@ -37,9 +36,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
}
- public override void FromDatabaseAttributes(IReadOnlyDictionary values)
+ public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo)
{
- base.FromDatabaseAttributes(values);
+ base.FromDatabaseAttributes(values, onlineInfo);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 8002410f70..979aaa1cf5 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
switch (m)
{
- case ManiaModNoFail _:
- case ManiaModEasy _:
- case ManiaModHalfTime _:
+ case ManiaModNoFail:
+ case ManiaModEasy:
+ case ManiaModHalfTime:
scoreMultiplier *= 0.5;
break;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 98a492450e..4723416c30 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -146,56 +146,56 @@ namespace osu.Game.Rulesets.Mania
{
switch (mod)
{
- case ManiaModKey1 _:
+ case ManiaModKey1:
value |= LegacyMods.Key1;
break;
- case ManiaModKey2 _:
+ case ManiaModKey2:
value |= LegacyMods.Key2;
break;
- case ManiaModKey3 _:
+ case ManiaModKey3:
value |= LegacyMods.Key3;
break;
- case ManiaModKey4 _:
+ case ManiaModKey4:
value |= LegacyMods.Key4;
break;
- case ManiaModKey5 _:
+ case ManiaModKey5:
value |= LegacyMods.Key5;
break;
- case ManiaModKey6 _:
+ case ManiaModKey6:
value |= LegacyMods.Key6;
break;
- case ManiaModKey7 _:
+ case ManiaModKey7:
value |= LegacyMods.Key7;
break;
- case ManiaModKey8 _:
+ case ManiaModKey8:
value |= LegacyMods.Key8;
break;
- case ManiaModKey9 _:
+ case ManiaModKey9:
value |= LegacyMods.Key9;
break;
- case ManiaModDualStages _:
+ case ManiaModDualStages:
value |= LegacyMods.KeyCoop;
break;
- case ManiaModFadeIn _:
+ case ManiaModFadeIn:
value |= LegacyMods.FadeIn;
value &= ~LegacyMods.Hidden; // this is toggled on in the base call due to inheritance, but we don't want that.
break;
- case ManiaModMirror _:
+ case ManiaModMirror:
value |= LegacyMods.Mirror;
break;
- case ManiaModRandom _:
+ case ManiaModRandom:
value |= LegacyMods.Random;
break;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
index b709f85523..21b362df00 100644
--- a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania
protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
- protected override string ComponentName => Component.ToString().ToLower();
+ protected override string ComponentName => Component.ToString().ToLowerInvariant();
}
public enum ManiaSkinComponents
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index c7c7a6003e..22347d21b8 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Mods
var rng = new Random((int)Seed.Value);
int availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
- var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => rng.Next()).ToList();
+ var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(_ => rng.Next()).ToList();
beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]);
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 3814ad84f1..26572de412 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -57,11 +57,11 @@ namespace osu.Game.Rulesets.Mania.Replays
{
switch (point)
{
- case HitPoint _:
+ case HitPoint:
actions.Add(columnActions[point.Column]);
break;
- case ReleasePoint _:
+ case ReleasePoint:
actions.Remove(columnActions[point.Column]);
break;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
index 3d9fe37e0f..ef9e332253 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs
@@ -10,7 +10,6 @@ using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit;
@@ -41,10 +40,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
- private bool editorComponentsReady => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
- && editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true
- && editor?.ChildrenOfType().FirstOrDefault()?.IsLoaded == true;
-
[TestCase(true)]
[TestCase(false)]
public void TestVelocityChangeSavesCorrectly(bool adjustVelocity)
@@ -52,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
double? velocity = null;
AddStep("enter editor", () => Game.ScreenStack.Push(new EditorLoader()));
- AddUntilStep("wait for editor load", () => editorComponentsReady);
+ AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
AddStep("seek to first control point", () => editorClock.Seek(editorBeatmap.ControlPointInfo.TimingPoints.First().Time));
AddStep("enter slider placement mode", () => InputManager.Key(Key.Number3));
@@ -91,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("exit", () => InputManager.Key(Key.Escape));
AddStep("enter editor (again)", () => Game.ScreenStack.Push(new EditorLoader()));
- AddUntilStep("wait for editor load", () => editorComponentsReady);
+ AddUntilStep("wait for editor load", () => editor?.ReadyForUse == true);
AddStep("seek to slider", () => editorClock.Seek(slider.StartTime));
AddAssert("slider has correct velocity", () => slider.Velocity == velocity);
diff --git a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
index 2c4310202e..7c4ab2f5f4 100644
--- a/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/LegacyMainCirclePieceTest.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Tests
skin.Setup(s => s.GetTexture(It.IsAny())).CallBase();
skin.Setup(s => s.GetTexture(It.IsIn(textureFilenames), It.IsAny(), It.IsAny()))
- .Returns((string componentName, WrapMode _, WrapMode __) => new Texture(1, 1) { AssetName = componentName });
+ .Returns((string componentName, WrapMode _, WrapMode _) => new Texture(1, 1) { AssetName = componentName });
Child = new DependencyProvidingContainer
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
index fe8bba3ed8..4f6d6376bf 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
lastResult = null;
spinner = nextSpinner;
- spinner.OnNewResult += (o, result) => lastResult = result;
+ spinner.OnNewResult += (_, result) => lastResult = result;
}
return lastResult?.Type == HitResult.Great;
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
return false;
spinner = nextSpinner;
- spinner.OnNewResult += (o, result) => results.Add(result);
+ spinner.OnNewResult += (_, result) => results.Add(result);
results.Clear();
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
new file mode 100644
index 0000000000..7a6e19575e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderFollowCircleInput.cs
@@ -0,0 +1,120 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [HeadlessTest]
+ public class TestSceneSliderFollowCircleInput : RateAdjustedBeatmapTestScene
+ {
+ private List? judgementResults;
+ private ScoreAccessibleReplayPlayer? currentPlayer;
+
+ [Test]
+ public void TestMaximumDistanceTrackingWithoutMovement(
+ [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
+ float circleSize,
+ [Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
+ double velocity)
+ {
+ const double time_slider_start = 1000;
+
+ float circleRadius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (circleSize - 5) / 5) / 2;
+ float followCircleRadius = circleRadius * 1.2f;
+
+ performTest(new Beatmap
+ {
+ HitObjects =
+ {
+ new Slider
+ {
+ StartTime = time_slider_start,
+ Position = new Vector2(0, 0),
+ DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity },
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(followCircleRadius, 0),
+ }, followCircleRadius),
+ },
+ },
+ BeatmapInfo =
+ {
+ Difficulty = new BeatmapDifficulty
+ {
+ CircleSize = circleSize,
+ SliderTickRate = 1
+ },
+ Ruleset = new OsuRuleset().RulesetInfo
+ },
+ }, new List
+ {
+ new OsuReplayFrame { Position = new Vector2(-circleRadius + 1, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ });
+
+ AddAssert("Tracking kept", assertMaxJudge);
+ }
+
+ private bool assertMaxJudge() => judgementResults?.Any() == true && judgementResults.All(t => t.Type == t.Judgement.MaxResult);
+
+ private void performTest(Beatmap beatmap, List frames)
+ {
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(beatmap);
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults?.Add(result);
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for completion", () => currentPlayer?.ScoreProcessor.HasCompleted.Value == true);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, new PlayerConfiguration
+ {
+ AllowPause = false,
+ ShowResults = false,
+ })
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index be2e9c7cb5..366793058d 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -66,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Tests
drawableSlider = null;
});
- [SetUpSteps]
- public override void SetUpSteps()
- {
- }
+ protected override bool HasCustomSteps => true;
[TestCase(0)]
[TestCase(1)]
@@ -77,7 +74,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestSnakingEnabled(int sliderIndex)
{
AddStep("enable autoplay", () => autoplay = true);
- base.SetUpSteps();
+ CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
retrieveSlider(sliderIndex);
@@ -101,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestSnakingDisabled(int sliderIndex)
{
AddStep("have autoplay", () => autoplay = true);
- base.SetUpSteps();
+ CreateTest();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
retrieveSlider(sliderIndex);
@@ -121,8 +118,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("enable autoplay", () => autoplay = true);
setSnaking(true);
- base.SetUpSteps();
-
+ CreateTest();
// repeat might have a chance to update its position depending on where in the frame its hit,
// so some leniency is allowed here instead of checking strict equality
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame);
@@ -133,15 +129,14 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("disable autoplay", () => autoplay = false);
setSnaking(true);
- base.SetUpSteps();
-
+ CreateTest();
addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased);
}
private void retrieveSlider(int index)
{
AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]);
- addSeekStep(() => slider);
+ addSeekStep(() => slider.StartTime);
AddUntilStep("retrieve drawable slider", () =>
(drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
}
@@ -161,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Tests
=> addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex;
- private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd;
+ private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? getSliderStart : getSliderEnd;
private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
private Vector2 getSliderStart() => getSliderCurve().First();
@@ -205,16 +200,10 @@ namespace osu.Game.Rulesets.Osu.Tests
});
}
- private void addSeekStep(Func slider)
+ private void addSeekStep(Func getTime)
{
- AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime));
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
- }
-
- private void addSeekStep(Func time)
- {
- AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time()));
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddStep("seek to time", () => Player.GameplayClockContainer.Seek(getTime()));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(getTime(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() };
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index b24ff54b8a..03540abddb 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -1,12 +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 disable
-
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -104,9 +103,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount);
}
- public override void FromDatabaseAttributes(IReadOnlyDictionary values)
+ public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo)
{
- base.FromDatabaseAttributes(values);
+ base.FromDatabaseAttributes(values, onlineInfo);
AimDifficulty = values[ATTRIB_ID_AIM];
SpeedDifficulty = values[ATTRIB_ID_SPEED];
@@ -117,6 +116,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT];
+
+ DrainRate = onlineInfo.DrainRate;
+ HitCircleCount = onlineInfo.CircleCount;
+ SliderCount = onlineInfo.SliderCount;
+ SpinnerCount = onlineInfo.SpinnerCount;
}
#region Newtonsoft.Json implicit ShouldSerialize() methods
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 066a114f66..60896b17bf 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Edit
});
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
- selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
+ selectedHitObjects.CollectionChanged += (_, _) => updateDistanceSnapGrid();
placementObject = EditorBeatmap.PlacementObject.GetBoundCopy();
placementObject.ValueChanged += _ => updateDistanceSnapGrid();
@@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Edit
switch (BlueprintContainer.CurrentTool)
{
- case SelectTool _:
+ case SelectTool:
if (!EditorBeatmap.SelectedHitObjects.Any())
return;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
index 3d4a26b3ff..e25845f5ab 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
{
- drawable.ApplyCustomUpdateState += (drawableObject, state) =>
+ drawable.ApplyCustomUpdateState += (drawableObject, _) =>
{
if (!(drawableObject is DrawableHitCircle drawableHitCircle)) return;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
index 8a15d730cd..00009f4c3d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs
@@ -30,9 +30,6 @@ namespace osu.Game.Rulesets.Osu.Mods
[SettingSource("Apply classic note lock", "Applies note lock to the full hit window.")]
public Bindable ClassicNoteLock { get; } = new BindableBool(true);
- [SettingSource("Use fixed slider follow circle hit area", "Makes the slider follow circle track its final size at all times.")]
- public Bindable FixedFollowCircleHitArea { get; } = new BindableBool(true);
-
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
public Bindable AlwaysPlayTailSample { get; } = new BindableBool(true);
@@ -62,10 +59,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
switch (obj)
{
- case DrawableSlider slider:
- slider.Ball.InputTracksVisualSize = !FixedFollowCircleHitArea.Value;
- break;
-
case DrawableSliderHead head:
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
break;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index f9422e1ff9..11ceb0f710 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (drawableObject)
{
- case DrawableSliderTail _:
+ case DrawableSliderTail:
using (drawableObject.BeginAbsoluteSequence(fadeStartTime))
drawableObject.FadeOut(fadeDuration);
@@ -165,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (hitObject)
{
- case Slider _:
+ case Slider:
return (fadeOutStartTime, longFadeDuration);
- case SliderTick _:
+ case SliderTick:
double tickFadeOutDuration = Math.Min(hitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
return (hitObject.StartTime - tickFadeOutDuration, tickFadeOutDuration);
- case Spinner _:
+ case Spinner:
return (fadeOutStartTime + longFadeDuration, fadeOutDuration);
default:
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index d96724929f..44942e9e37 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Osu.Mods
// apply grow effect
switch (drawable)
{
- case DrawableSliderHead _:
- case DrawableSliderTail _:
+ case DrawableSliderHead:
+ case DrawableSliderTail:
// special cases we should *not* be scaling.
break;
- case DrawableSlider _:
- case DrawableHitCircle _:
+ case DrawableSlider:
+ case DrawableHitCircle:
{
using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
drawable.ScaleTo(StartScale.Value).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index 4589a8eca1..f03bcffdc8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
@@ -374,7 +374,7 @@ namespace osu.Game.Rulesets.Osu.Mods
int i = 0;
double currentTime = timingPoint.Time;
- while (!definitelyBigger(currentTime, mapEndTime) && controlPointInfo.TimingPointAt(currentTime) == timingPoint)
+ while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint))
{
beats.Add(Math.Floor(currentTime));
i++;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index 51994a3e1a..5a08df3803 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Osu.Mods
{
switch (drawable)
{
- case DrawableSliderHead _:
- case DrawableSliderTail _:
- case DrawableSliderTick _:
- case DrawableSliderRepeat _:
+ case DrawableSliderHead:
+ case DrawableSliderTail:
+ case DrawableSliderTick:
+ case DrawableSliderRepeat:
return;
default:
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index dcb47347ef..a5468ff613 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public Slider()
{
- SamplesBindable.CollectionChanged += (_, __) => UpdateNestedSamples();
+ SamplesBindable.CollectionChanged += (_, _) => UpdateNestedSamples();
Path.Version.ValueChanged += _ => updateNestedPositions();
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 8b9dc71a8b..120ce32612 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -120,19 +120,19 @@ namespace osu.Game.Rulesets.Osu
{
switch (mod)
{
- case OsuModAutopilot _:
+ case OsuModAutopilot:
value |= LegacyMods.Autopilot;
break;
- case OsuModSpunOut _:
+ case OsuModSpunOut:
value |= LegacyMods.SpunOut;
break;
- case OsuModTarget _:
+ case OsuModTarget:
value |= LegacyMods.Target;
break;
- case OsuModTouchDevice _:
+ case OsuModTouchDevice:
value |= LegacyMods.TouchDevice;
break;
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
index 82c4005c5e..0abaf2c924 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponent.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu
protected override string RulesetPrefix => OsuRuleset.SHORT_NAME;
- protected override string ComponentName => Component.ToString().ToLower();
+ protected override string ComponentName => Component.ToString().ToLowerInvariant();
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index 27029afece..b0155c02cf 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Replays
hitWindows = slider.TailCircle.HitWindows;
break;
- case Spinner _:
+ case Spinner:
hitWindows = defaultHitWindows;
break;
}
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 7a71ef6c65..34a1bd40e9 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
switch (hitObject)
{
- case HitCircle _:
+ case HitCircle:
return new OsuHitCircleJudgementResult(hitObject, judgement);
default:
diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs
index d2ea8f1660..389e9343e7 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Default/SliderBall.cs
@@ -34,13 +34,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
set => ball.Colour = value;
}
- ///
- /// Whether to track accurately to the visual size of this .
- /// If false, tracking will be performed at the final scale at all times.
- ///
- public bool InputTracksVisualSize = true;
-
private readonly Drawable followCircle;
+ private readonly Drawable fullSizeFollowCircle;
private readonly DrawableSlider drawableSlider;
private readonly Drawable ball;
@@ -62,6 +57,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Alpha = 0,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()),
},
+ fullSizeFollowCircle = new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true
+ },
ball = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBall), _ => new DefaultSliderBall())
{
Anchor = Anchor.Centre,
@@ -104,14 +106,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
tracking = value;
- if (InputTracksVisualSize)
- followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
- else
- {
- // We need to always be tracking the final size, at both endpoints. For now, this is achieved by removing the scale duration.
- followCircle.ScaleTo(tracking ? 2.4f : 1f);
- }
+ fullSizeFollowCircle.Scale = new Vector2(tracking ? 2.4f : 1f);
+ followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
}
}
@@ -170,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
// in valid time range
Time.Current >= drawableSlider.HitObject.StartTime && Time.Current < drawableSlider.HitObject.EndTime &&
// in valid position range
- lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
+ lastScreenSpaceMousePosition.HasValue && fullSizeFollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
index e4ca0d2ea8..d5cc469ca9 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
break;
- case DrawableSpinnerBonusTick _:
+ case DrawableSpinnerBonusTick:
if (state == ArmedState.Hit)
glow.FlashColour(Color4.White, 200);
diff --git a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs
index fba225d464..6330208d37 100644
--- a/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu/UI/ObjectOrderedHitPolicy.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.UI
switch (obj)
{
- case DrawableSpinner _:
+ case DrawableSpinner:
continue;
case DrawableSlider slider:
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index ad27428010..3179b37d5a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI
// note: `Slider`'s `ProxiedLayer` is added when its nested `DrawableHitCircle` is loaded.
switch (drawable)
{
- case DrawableSpinner _:
+ case DrawableSpinner:
spinnerProxies.Add(drawable.CreateProxy());
break;
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
index c413226e63..3a156d4d25 100644
--- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs
@@ -88,11 +88,11 @@ namespace osu.Game.Rulesets.Osu.Utils
switch (hitObject)
{
- case HitCircle _:
+ case HitCircle:
shift = clampHitCircleToPlayfield(current);
break;
- case Slider _:
+ case Slider:
shift = clampSliderToPlayfield(current);
break;
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
index 1bf6c0560a..ef95358d34 100644
--- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs
@@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
createDrawableRuleset();
assertStateAfterResult(new JudgementResult(new Swell(), new TaikoSwellJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Clear);
- AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLower()}", () => allMascotsIn(expectedStateAfterClear));
+ AddUntilStep($"state reverts to {expectedStateAfterClear.ToString().ToLowerInvariant()}", () => allMascotsIn(expectedStateAfterClear));
}
private void setBeatmap(bool kiai = false)
@@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
TaikoMascotAnimationState[] mascotStates = null;
- AddStep($"{judgementResult.Type.ToString().ToLower()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
+ AddStep($"{judgementResult.Type.ToString().ToLowerInvariant()} result for {judgementResult.Judgement.GetType().Name.Humanize(LetterCasing.LowerCase)}",
() =>
{
applyNewResult(judgementResult);
@@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
Schedule(() => mascotStates = animatedMascots.Select(mascot => mascot.State.Value).ToArray());
});
- AddAssert($"state is {expectedState.ToString().ToLower()}", () => mascotStates.All(state => state == expectedState));
+ AddAssert($"state is {expectedState.ToString().ToLowerInvariant()}", () => mascotStates.All(state => state == expectedState));
}
private void applyNewResult(JudgementResult judgementResult)
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index 51772df4a7..cdfab4a215 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
AddStep("Setup judgements", () =>
{
judged = false;
- Player.ScoreProcessor.NewJudgement += b => judged = true;
+ Player.ScoreProcessor.NewJudgement += _ => judged = true;
});
AddUntilStep("swell judged", () => judged);
AddAssert("failed", () => Player.GameplayState.HasFailed);
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
index 6c617a22a4..380ab4a4fc 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System.Collections.Generic;
using Newtonsoft.Json;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Taiko.Difficulty
@@ -57,9 +56,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
}
- public override void FromDatabaseAttributes(IReadOnlyDictionary values)
+ public override void FromDatabaseAttributes(IReadOnlyDictionary values, IBeatmapOnlineInfo onlineInfo)
{
- base.FromDatabaseAttributes(values);
+ base.FromDatabaseAttributes(values, onlineInfo);
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
StarRating = values[ATTRIB_ID_DIFFICULTY];
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
index 69eace4302..e065bb43fd 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
@@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
switch (hitObject)
{
- case DrawableDrumRollTick _:
- case DrawableHit _:
+ case DrawableDrumRollTick:
+ case DrawableHit:
double preempt = drawableRuleset.TimeRange.Value / drawableRuleset.ControlPointAt(hitObject.HitObject.StartTime).Multiplier;
double start = hitObject.HitObject.StartTime - preempt * fade_out_start_time;
double duration = preempt * fade_out_duration;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
index 8bc0dc6df0..20f3304c30 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
DisplayColour.Value = Type == HitType.Centre ? COLOUR_CENTRE : COLOUR_RIM;
});
- SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
+ SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples());
}
private void updateTypeFromSamples()
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
index b7bdd98d2a..d4d59d5d44 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoStrongableHitObject.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
protected TaikoStrongableHitObject()
{
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
- SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
+ SamplesBindable.BindCollectionChanged((_, _) => updateTypeFromSamples());
}
private void updateTypeFromSamples()
diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
index 676a3d4bc3..63314a6822 100644
--- a/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponent.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Taiko
protected override string RulesetPrefix => TaikoRuleset.SHORT_NAME;
- protected override string ComponentName => Component.ToString().ToLower();
+ protected override string ComponentName => Component.ToString().ToLowerInvariant();
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
index bacd9b41f8..26a37fc464 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs
@@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Taiko.UI
private static Texture getAnimationFrame(ISkin skin, TaikoMascotAnimationState state, int frameIndex)
{
- var texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}{frameIndex}");
+ var texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}{frameIndex}");
if (frameIndex == 0 && texture == null)
- texture = skin.GetTexture($"pippidon{state.ToString().ToLower()}");
+ texture = skin.GetTexture($"pippidon{state.ToString().ToLowerInvariant()}");
return texture;
}
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 4e0c8029fb..4ef7c24464 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -245,7 +245,7 @@ namespace osu.Game.Rulesets.Taiko.UI
barLinePlayfield.Add(barLine);
break;
- case DrawableTaikoHitObject _:
+ case DrawableTaikoHitObject:
base.Add(h);
break;
@@ -261,7 +261,7 @@ namespace osu.Game.Rulesets.Taiko.UI
case DrawableBarLine barLine:
return barLinePlayfield.Remove(barLine);
- case DrawableTaikoHitObject _:
+ case DrawableTaikoHitObject:
return base.Remove(h);
default:
@@ -280,12 +280,12 @@ namespace osu.Game.Rulesets.Taiko.UI
switch (result.Judgement)
{
- case TaikoStrongJudgement _:
+ case TaikoStrongJudgement:
if (result.IsHit)
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).ParentHitObject)?.VisualiseSecondHit(result);
break;
- case TaikoDrumRollTickJudgement _:
+ case TaikoDrumRollTickJudgement:
if (!result.IsHit)
break;
diff --git a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
index f87e5711a6..6eb103316f 100644
--- a/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
+++ b/osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Beatmaps
[TestCase(8.3, DifficultyRating.ExpertPlus)]
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
{
- var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
+ var actualBracket = StarDifficulty.GetDifficultyRating(starRating);
Assert.AreEqual(expectedBracket, actualBracket);
}
diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
index 7f466925a4..c31aafa67f 100644
--- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
+++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Collections.IO
public async Task TestImportMalformedDatabase()
{
bool exceptionThrown = false;
- UnhandledExceptionEventHandler setException = (_, __) => exceptionThrown = true;
+ UnhandledExceptionEventHandler setException = (_, _) => exceptionThrown = true;
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs
index f9cd75d8c3..6f5a79c9da 100644
--- a/osu.Game.Tests/Database/BeatmapImporterTests.cs
+++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs
@@ -15,7 +15,6 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Extensions;
-using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
@@ -39,10 +38,7 @@ namespace osu.Game.Tests.Database
using (var importer = new BeatmapImporter(storage, realm))
using (new RealmRulesetStore(realm, storage))
{
- Live? beatmapSet;
-
- using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
- beatmapSet = await importer.Import(reader);
+ var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
Assert.NotNull(beatmapSet);
Debug.Assert(beatmapSet != null);
@@ -83,10 +79,7 @@ namespace osu.Game.Tests.Database
using (var importer = new BeatmapImporter(storage, realm))
using (new RealmRulesetStore(realm, storage))
{
- Live? beatmapSet;
-
- using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
- beatmapSet = await importer.Import(reader);
+ var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
Assert.NotNull(beatmapSet);
Debug.Assert(beatmapSet != null);
@@ -146,11 +139,8 @@ namespace osu.Game.Tests.Database
{
Task.Run(async () =>
{
- Live? beatmapSet;
-
- using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
- // ReSharper disable once AccessToDisposedClosure
- beatmapSet = await importer.Import(reader);
+ // ReSharper disable once AccessToDisposedClosure
+ var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
Assert.NotNull(beatmapSet);
Debug.Assert(beatmapSet != null);
@@ -173,13 +163,8 @@ namespace osu.Game.Tests.Database
using (var importer = new BeatmapImporter(storage, realm))
using (new RealmRulesetStore(realm, storage))
{
- Live? imported;
-
- using (var reader = new ZipArchiveReader(TestResources.GetTestBeatmapStream()))
- {
- imported = await importer.Import(reader);
- EnsureLoaded(realm.Realm);
- }
+ var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
+ EnsureLoaded(realm.Realm);
Assert.AreEqual(1, realm.Realm.All().Count());
@@ -291,6 +276,42 @@ namespace osu.Game.Tests.Database
});
}
+ [Test]
+ public void TestImportDirectoryWithEmptyOsuFiles()
+ {
+ RunTestWithRealmAsync(async (realm, storage) =>
+ {
+ using var importer = new BeatmapImporter(storage, realm);
+ using var store = new RealmRulesetStore(realm, storage);
+
+ string? temp = TestResources.GetTestBeatmapForImport();
+
+ string extractedFolder = $"{temp}_extracted";
+ Directory.CreateDirectory(extractedFolder);
+
+ try
+ {
+ using (var zip = ZipArchive.Open(temp))
+ zip.WriteToDirectory(extractedFolder);
+
+ foreach (var file in new DirectoryInfo(extractedFolder).GetFiles("*.osu"))
+ {
+ using (file.Open(FileMode.Create))
+ {
+ // empty file.
+ }
+ }
+
+ var imported = await importer.Import(new ImportTask(extractedFolder));
+ Assert.IsNull(imported);
+ }
+ finally
+ {
+ Directory.Delete(extractedFolder, true);
+ }
+ });
+ }
+
[Test]
public void TestImportThenImportWithReZip()
{
@@ -586,6 +607,12 @@ namespace osu.Game.Tests.Database
using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
using (var zip = ZipArchive.Open(brokenOsz))
{
+ foreach (var entry in zip.Entries.ToArray())
+ {
+ if (entry.Key.EndsWith(".osu", StringComparison.InvariantCulture))
+ zip.RemoveEntry(entry);
+ }
+
zip.AddEntry("broken.osu", brokenOsu, false);
zip.SaveTo(outStream, CompressionType.Deflate);
}
@@ -606,7 +633,7 @@ namespace osu.Game.Tests.Database
checkSingleReferencedFileCount(realm.Realm, 18);
- Assert.AreEqual(1, loggedExceptionCount);
+ Assert.AreEqual(0, loggedExceptionCount);
File.Delete(brokenTempFilename);
});
@@ -760,7 +787,7 @@ namespace osu.Game.Tests.Database
}
};
- var imported = importer.Import(toImport);
+ var imported = importer.ImportModel(toImport);
realm.Run(r => r.Refresh());
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
index d10ab2ad2b..65f805bafb 100644
--- a/osu.Game.Tests/Database/GeneralUsageTests.cs
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Database
realm.RegisterCustomSubscription(r =>
{
- var subscription = r.All().QueryAsyncWithNotifications((sender, changes, error) =>
+ var subscription = r.All().QueryAsyncWithNotifications((_, _, _) =>
{
realm.Run(_ =>
{
@@ -79,11 +79,11 @@ namespace osu.Game.Tests.Database
{
hasThreadedUsage.Set();
- stopThreadedUsage.Wait();
+ stopThreadedUsage.Wait(60000);
});
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
- hasThreadedUsage.Wait();
+ hasThreadedUsage.Wait(60000);
Assert.Throws(() =>
{
diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs
index 416216062e..00a667521d 100644
--- a/osu.Game.Tests/Database/RealmLiveTests.cs
+++ b/osu.Game.Tests/Database/RealmLiveTests.cs
@@ -189,7 +189,7 @@ namespace osu.Game.Tests.Database
});
// Can't be used, even from within a valid context.
- realm.Run(threadContext =>
+ realm.Run(_ =>
{
Assert.Throws(() =>
{
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
index f9c4985219..c4e8bbfccf 100644
--- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Tests.Gameplay
AddStep("apply perfect hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
AddAssert("not failed", () => !processor.HasFailed);
- AddStep($"apply {resultApplied.ToString().ToLower()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied }));
+ AddStep($"apply {resultApplied.ToString().ToLowerInvariant()} hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = resultApplied }));
AddAssert("failed", () => processor.HasFailed);
}
diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
index 7d5f0bcd0c..a803974d30 100644
--- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
+++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs
@@ -254,7 +254,7 @@ namespace osu.Game.Tests.NonVisual
Assert.That(File.Exists(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME)));
Directory.CreateDirectory(customPath2);
- File.Copy(Path.Combine(customPath, OsuGameBase.CLIENT_DATABASE_FILENAME), Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME));
+ File.WriteAllText(Path.Combine(customPath2, OsuGameBase.CLIENT_DATABASE_FILENAME), "I am a text");
// Fails because file already exists.
Assert.Throws(() => osu.Migrate(customPath2));
diff --git a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
index a779fae510..14da07bc2d 100644
--- a/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
+++ b/osu.Game.Tests/NonVisual/FirstAvailableHitWindowsTest.cs
@@ -83,14 +83,14 @@ namespace osu.Game.Tests.NonVisual
public override event Action NewResult
{
- add => throw new InvalidOperationException();
- remove => throw new InvalidOperationException();
+ add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
+ remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
}
public override event Action RevertResult
{
- add => throw new InvalidOperationException();
- remove => throw new InvalidOperationException();
+ add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
+ remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
}
public override Playfield Playfield { get; }
diff --git a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
index 4a5bb6de46..541ad1e8bb 100644
--- a/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
+++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
@@ -364,12 +364,12 @@ namespace osu.Game.Tests.NonVisual
private void confirmCurrentFrame(int? frame)
{
- Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.CurrentFrame?.Time, "Unexpected current frame");
+ Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.CurrentFrame?.Time, "Unexpected current frame");
}
private void confirmNextFrame(int? frame)
{
- Assert.AreEqual(frame is int x ? replay.Frames[x].Time : (double?)null, handler.NextFrame?.Time, "Unexpected next frame");
+ Assert.AreEqual(frame is int x ? replay.Frames[x].Time : null, handler.NextFrame?.Time, "Unexpected next frame");
}
private class TestReplayFrame : ReplayFrame
diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
index efab884d37..22aa78838a 100644
--- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
+++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
{
// use an incrementing width to allow assertion matching on correct textures as they turn from uploads into actual textures.
int width = 1;
- Textures = fileNames.ToDictionary(fileName => fileName, fileName => new TextureUpload(new Image(width, width++)));
+ Textures = fileNames.ToDictionary(fileName => fileName, _ => new TextureUpload(new Image(width, width++)));
}
public TextureUpload Get(string name) => Textures.GetValueOrDefault(name);
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index e502a71f34..1d33a895eb 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -227,10 +227,10 @@ namespace osu.Game.Tests.Online
this.testBeatmapManager = testBeatmapManager;
}
- public override Live Import(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
+ public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default)
{
testBeatmapManager.AllowImport.Task.WaitSafely();
- return (testBeatmapManager.CurrentImport = base.Import(item, archive, batchImport, cancellationToken));
+ return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken));
}
}
}
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 4db88cbe82..8b7fcae1a9 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -15,7 +15,6 @@ using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO;
-using osu.Game.IO.Archives;
using osu.Game.Skinning;
using SharpCompress.Archives.Zip;
@@ -28,7 +27,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSingleImportDifferentFilename() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
// When the import filename doesn't match, it should be appended (and update the skin.ini).
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
@@ -37,7 +36,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSingleImportWeirdIniFileCase() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", iniFilename: "Skin.InI"), "skin.osk"));
// When the import filename doesn't match, it should be appended (and update the skin.ini).
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
@@ -46,7 +45,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
// When the import filename doesn't match, it should be appended (and update the skin.ini).
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
@@ -55,7 +54,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "test skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "test skin.osk"));
// When the import filename matches it shouldn't be appended.
assertCorrectMetadata(import1, "test skin", "skinner", osu);
@@ -64,7 +63,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSingleImportNoIniFile() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithNonIniFile(), "test skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithNonIniFile(), "test skin.osk"));
// When the import filename matches it shouldn't be appended.
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
@@ -73,7 +72,7 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestEmptyImportImportsWithFilename() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createEmptyOsk(), "test skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createEmptyOsk(), "test skin.osk"));
// When the import filename matches it shouldn't be appended.
assertCorrectMetadata(import1, "test skin", "Unknown", osu);
@@ -86,8 +85,8 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestImportTwiceWithSameMetadataAndFilename() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
assertImportedOnce(import1, import2);
});
@@ -96,8 +95,8 @@ namespace osu.Game.Tests.Skins.IO
public Task TestImportTwiceWithNoMetadataSameDownloadFilename() => runSkinTest(async osu =>
{
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"));
assertImportedOnce(import1, import2);
});
@@ -105,10 +104,10 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestImportUpperCasedOskArchive() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.OsK"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.OsK"));
assertCorrectMetadata(import1, "name 1", "author 1", osu);
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "name 1.oSK"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "name 1.oSK"));
assertImportedOnce(import1, import2);
});
@@ -118,7 +117,7 @@ namespace osu.Game.Tests.Skins.IO
{
MemoryStream exportStream = new MemoryStream();
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "custom.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "custom.osk"));
assertCorrectMetadata(import1, "name 1 [custom]", "author 1", osu);
import1.PerformRead(s =>
@@ -128,7 +127,7 @@ namespace osu.Game.Tests.Skins.IO
string exportFilename = import1.GetDisplayString();
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(exportStream, $"{exportFilename}.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
assertCorrectMetadata(import2, "name 1 [custom]", "author 1", osu);
assertImportedOnce(import1, import2);
@@ -137,8 +136,8 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSameMetadataNameSameFolderName() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
assertImportedOnce(import1, import2);
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
@@ -151,8 +150,8 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestImportTwiceWithSameMetadataButDifferentFilename() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin.osk"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner"), "skin2.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin", "skinner"), "skin2.osk"));
assertImportedBoth(import1, import2);
});
@@ -161,8 +160,8 @@ namespace osu.Game.Tests.Skins.IO
public Task TestImportTwiceWithNoMetadataDifferentDownloadFilename() => runSkinTest(async osu =>
{
// if a user downloads two skins that do have skin.ini files but don't have any creator metadata in the skin.ini, they should both import separately just for safety.
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download.osk"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni(string.Empty, string.Empty), "download2.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni(string.Empty, string.Empty), "download2.osk"));
assertImportedBoth(import1, import2);
});
@@ -170,8 +169,8 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestImportTwiceWithSameFilenameDifferentMetadata() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2", "skinner"), "skin.osk"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin v2.1", "skinner"), "skin.osk"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2", "skinner"), "skin.osk"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("test skin v2.1", "skinner"), "skin.osk"));
assertImportedBoth(import1, import2);
assertCorrectMetadata(import1, "test skin v2 [skin]", "skinner", osu);
@@ -181,8 +180,8 @@ namespace osu.Game.Tests.Skins.IO
[Test]
public Task TestSameMetadataNameDifferentFolderName() => runSkinTest(async osu =>
{
- var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
- var import2 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("name 1", "author 1"), "my custom skin 2"));
+ var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 1"));
+ var import2 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 1", "author 1"), "my custom skin 2"));
assertImportedBoth(import1, import2);
assertCorrectMetadata(import1, "name 1 [my custom skin 1]", "author 1", osu);
@@ -358,10 +357,10 @@ namespace osu.Game.Tests.Skins.IO
}
}
- private async Task> loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
+ private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import)
{
var skinManager = osu.Dependencies.Get();
- return await skinManager.Import(archive);
+ return await skinManager.Import(import);
}
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
index 77ceef6402..1493c10969 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinLookupDisables.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Skins
{
AddStep($"Set beatmap skin enabled to {allowBeatmapLookups}", () => config.SetValue(OsuSetting.BeatmapSkins, allowBeatmapLookups));
- ISkin expected() => allowBeatmapLookups ? (ISkin)beatmapSource : userSource;
+ ISkin expected() => allowBeatmapLookups ? beatmapSource : userSource;
AddAssert("Check lookup is from correct source", () => requester.FindProvider(s => s.GetDrawableComponent(new TestSkinComponent()) != null) == expected());
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index bd7e1f8ec5..f4cea2c8cc 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -10,7 +10,7 @@ using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
-using osu.Game.IO.Archives;
+using osu.Game.Database;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader]
private void load()
{
- var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely();
+ var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely();
imported?.PerformRead(s =>
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
index 97588f4053..42c1eeb6d1 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
@@ -8,7 +8,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Audio;
-using osu.Game.IO.Archives;
+using osu.Game.Database;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader]
private void load()
{
- var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely();
+ var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely();
skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
index a204fc5686..b711d55e15 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs
@@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Editing
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
- protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true;
-
protected override bool IsolateSavingFromDatabase => false;
[Resolved]
@@ -95,18 +93,23 @@ namespace osu.Game.Tests.Visual.Editing
string extractedFolder = $"{temp}_extracted";
Directory.CreateDirectory(extractedFolder);
- using (var zip = ZipArchive.Open(temp))
- zip.WriteToDirectory(extractedFolder);
+ try
+ {
+ using (var zip = ZipArchive.Open(temp))
+ zip.WriteToDirectory(extractedFolder);
- bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));
+ bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")));
- File.Delete(temp);
- Directory.Delete(extractedFolder, true);
+ // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
+ Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");
- // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename.
- Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3");
-
- return success;
+ return success;
+ }
+ finally
+ {
+ File.Delete(temp);
+ Directory.Delete(extractedFolder, true);
+ }
});
AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000);
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs
new file mode 100644
index 0000000000..a21ef6f897
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorNavigation.cs
@@ -0,0 +1,60 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Extensions;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Extensions.ObjectExtensions;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Database;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Components.Timelines.Summary;
+using osu.Game.Screens.Edit.GameplayTest;
+using osu.Game.Screens.Select;
+using osu.Game.Tests.Resources;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneEditorNavigation : OsuGameTestScene
+ {
+ [Test]
+ public void TestEditorGameplayTestAlwaysUsesOriginalRuleset()
+ {
+ BeatmapSetInfo beatmapSet = null!;
+
+ AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
+ AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
+
+ AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
+ AddUntilStep("wait for song select",
+ () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
+ && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
+ && songSelect.IsLoaded);
+ AddUntilStep("wait for completion notification", () => Game.Notifications.ChildrenOfType().Count() == 1);
+ AddStep("dismiss notifications", () => Game.Notifications.Hide());
+ AddStep("switch ruleset", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
+
+ AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
+ AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
+ AddStep("test gameplay", () =>
+ {
+ var testGameplayButton = this.ChildrenOfType().Single();
+ InputManager.MoveMouseTo(testGameplayButton);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen is EditorPlayer editorPlayer && editorPlayer.IsLoaded);
+ AddAssert("current ruleset is osu!", () => Game.Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
+
+ AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
+ AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
+ AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
index cf1246ef07..e9bbf1e33d 100644
--- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -117,8 +117,8 @@ namespace osu.Game.Tests.Visual.Editing
// After placement these must be non-default as defaults are read-only.
AddAssert("Placed object has non-default control points", () =>
- EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
- EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
+ !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
+ !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
ReloadEditorToSameBeatmap();
@@ -126,8 +126,8 @@ namespace osu.Game.Tests.Visual.Editing
// After placement these must be non-default as defaults are read-only.
AddAssert("Placed object still has non-default control points", () =>
- EditorBeatmap.HitObjects[0].SampleControlPoint != SampleControlPoint.DEFAULT &&
- EditorBeatmap.HitObjects[0].DifficultyControlPoint != DifficultyControlPoint.DEFAULT);
+ !ReferenceEquals(EditorBeatmap.HitObjects[0].SampleControlPoint, SampleControlPoint.DEFAULT) &&
+ !ReferenceEquals(EditorBeatmap.HitObjects[0].DifficultyControlPoint, DifficultyControlPoint.DEFAULT));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index f9a3695d65..a79ba0ae5d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -322,8 +322,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
switch (h)
{
- case TestPooledHitObject _:
- case TestPooledParentHitObject _:
+ case TestPooledHitObject:
+ case TestPooledParentHitObject:
return null;
case TestParentHitObject p:
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index 0b737f5110..ce01bf2fb5 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- HealthProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, _) => true;
}
private double lastFrequency = double.MaxValue;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index 90a4b536bb..5e87eff717 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- HealthProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, _) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
index 70d7f6a28b..707f807e64 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs
@@ -273,14 +273,14 @@ namespace osu.Game.Tests.Visual.Gameplay
public override event Action NewResult
{
- add => throw new InvalidOperationException();
- remove => throw new InvalidOperationException();
+ add => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
+ remove => throw new InvalidOperationException($"{nameof(NewResult)} operations not supported in test context");
}
public override event Action RevertResult
{
- add => throw new InvalidOperationException();
- remove => throw new InvalidOperationException();
+ add => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
+ remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
}
public override Playfield Playfield { get; }
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
index 3bebf2b68b..f319290441 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestToggleEditor()
{
- AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility());
+ AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
index 757a261de6..16593effd6 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = new SkinProvidingContainer(secondarySource)
{
RelativeSizeAxes = Axes.Both,
- Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"))
+ Child = consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation"))
}
};
});
@@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
- AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"))));
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation"))));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
}
@@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
});
- AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"))));
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", _ => new NamedBox("Default Implementation"))));
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
AddStep("disable", () => target.Disable());
AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 1e517efef2..5fad661e9b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -167,11 +167,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("start failing sends", () =>
{
spectatorClient.ShouldFailSendingFrames = true;
- framesReceivedSoFar = replay.Frames.Count;
frameSendAttemptsSoFar = spectatorClient.FrameSendAttempts;
});
- AddUntilStep("wait for send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 5);
+ AddUntilStep("wait for next send attempt", () =>
+ {
+ framesReceivedSoFar = replay.Frames.Count;
+ return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1;
+ });
+
+ AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10);
AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count);
AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
index 079d459beb..f0e184d727 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardSamplePlayback.cs
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void createPlayerTest()
{
- CreateTest(null);
+ CreateTest();
AddAssert("storyboard loaded", () => Player.Beatmap.Value.Storyboard != null);
waitUntilStoryboardSamplesPlay();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
index 68d024e63f..e2b2ad85a3 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboardWithOutro.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
base.SetUpSteps();
AddStep("enable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, true));
AddStep("set dim level to 0", () => LocalConfig.SetValue(OsuSetting.DimLevel, 0));
- AddStep("reset fail conditions", () => currentFailConditions = (_, __) => false);
+ AddStep("reset fail conditions", () => currentFailConditions = (_, _) => false);
AddStep("set storyboard duration to 2s", () => currentStoryboardDuration = 2000);
AddStep("set ShowResults = true", () => showResults = true);
}
@@ -52,17 +52,18 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestStoryboardSkipOutro()
{
- CreateTest(null);
+ AddStep("set storyboard duration to long", () => currentStoryboardDuration = 200000);
+ CreateTest();
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
- AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen());
+ AddUntilStep("player is no longer current screen", () => !Player.IsCurrentScreen());
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
[Test]
public void TestStoryboardNoSkipOutro()
{
- CreateTest(null);
+ CreateTest();
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= currentStoryboardDuration);
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
}
@@ -70,7 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestStoryboardExitDuringOutroStillExits()
{
- CreateTest(null);
+ CreateTest();
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("exit via pause", () => Player.ExitViaPause());
AddAssert("player exited", () => !Player.IsCurrentScreen() && Player.GetChildScreen() == null);
@@ -80,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(true)]
public void TestStoryboardToggle(bool enabledAtBeginning)
{
- CreateTest(null);
+ CreateTest();
AddStep($"{(enabledAtBeginning ? "enable" : "disable")} storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, enabledAtBeginning));
AddStep("toggle storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, !enabledAtBeginning));
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
@@ -91,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
CreateTest(() =>
{
- AddStep("fail on first judgement", () => currentFailConditions = (_, __) => true);
+ AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true);
// Fail occurs at 164ms with the provided beatmap.
// Fail animation runs for 2.5s realtime but the gameplay time change is *variable* due to the frequency transform being applied, so we need a bit of lenience.
@@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
SkipOverlay.FadeContainer fadeContainer() => Player.ChildrenOfType().First();
- CreateTest(null);
+ CreateTest();
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddUntilStep("skip overlay content becomes visible", () => fadeContainer().State == Visibility.Visible);
@@ -143,7 +144,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestPerformExitNoOutro()
{
- CreateTest(null);
+ CreateTest();
AddStep("disable storyboard", () => LocalConfig.SetValue(OsuSetting.ShowStoryboard, false));
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("exit via pause", () => Player.ExitViaPause());
diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
index 43ca47778a..631f2e707a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
// To emulate `MultiplayerClient.CurrentMatchPlayingUserIds` we need a bindable list of *only IDs*.
// This tracks the list of users 1:1.
- MultiplayerUsers.BindCollectionChanged((c, e) =>
+ MultiplayerUsers.BindCollectionChanged((_, e) =>
{
switch (e.Action)
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs
index fc3079cba0..be1f21a7b2 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableLoungeRoom.cs
@@ -42,11 +42,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
var mockLounge = new Mock();
mockLounge
.Setup(l => l.Join(It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>()))
- .Callback, Action>((a, b, c, d) =>
+ .Callback, Action>((_, _, _, d) =>
{
Task.Run(() =>
{
- allowResponseCallback.Wait();
+ allowResponseCallback.Wait(10000);
allowResponseCallback.Reset();
Schedule(() => d?.Invoke("Incorrect password"));
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
index 0cdc144b6a..a800b21bc9 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchStartControl.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
setRoomCountdown(countdownStart.Duration);
break;
- case StopCountdownRequest _:
+ case StopCountdownRequest:
multiplayerRoom.Countdown = null;
raiseRoomUpdated();
break;
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
new file mode 100644
index 0000000000..1c2b1fe37d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.IPC;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays;
+
+namespace osu.Game.Tests.Visual.Navigation
+{
+ [TestFixture]
+ [Ignore("This test cannot be run headless, as it requires the game host running the nested game to have IPC bound.")]
+ public class TestSceneInterProcessCommunication : OsuGameTestScene
+ {
+ private HeadlessGameHost ipcSenderHost = null!;
+
+ private OsuSchemeLinkIPCChannel osuSchemeLinkIPCReceiver = null!;
+ private OsuSchemeLinkIPCChannel osuSchemeLinkIPCSender = null!;
+
+ private const int requested_beatmap_set_id = 1;
+
+ [Resolved]
+ private GameHost gameHost { get; set; } = null!;
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+ AddStep("set up request handling", () =>
+ {
+ ((DummyAPIAccess)API).HandleRequest = request =>
+ {
+ switch (request)
+ {
+ case GetBeatmapSetRequest gbr:
+
+ var apiBeatmapSet = CreateAPIBeatmapSet();
+ apiBeatmapSet.OnlineID = requested_beatmap_set_id;
+ apiBeatmapSet.Beatmaps = apiBeatmapSet.Beatmaps.Append(new APIBeatmap
+ {
+ DifficultyName = "Target difficulty",
+ OnlineID = 75,
+ }).ToArray();
+ gbr.TriggerSuccess(apiBeatmapSet);
+ return true;
+ }
+
+ return false;
+ };
+ });
+ AddStep("create IPC receiver channel", () => osuSchemeLinkIPCReceiver = new OsuSchemeLinkIPCChannel(gameHost, Game));
+ AddStep("create IPC sender channel", () =>
+ {
+ ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { BindIPC = true });
+ osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost);
+ });
+ }
+
+ [Test]
+ public void TestOsuSchemeLinkIPCChannel()
+ {
+ AddStep("open beatmap via IPC", () => osuSchemeLinkIPCSender.HandleLinkAsync($@"osu://s/{requested_beatmap_set_id}").WaitSafely());
+ AddUntilStep("beatmap overlay displayed", () => Game.ChildrenOfType().FirstOrDefault()?.State.Value == Visibility.Visible);
+ AddUntilStep("beatmap overlay showing content", () => Game.ChildrenOfType().FirstOrDefault()?.Header.BeatmapSet.Value.OnlineID == requested_beatmap_set_id);
+ }
+
+ public override void TearDownSteps()
+ {
+ AddStep("dispose IPC receiver", () => osuSchemeLinkIPCReceiver.Dispose());
+ AddStep("dispose IPC sender", () =>
+ {
+ osuSchemeLinkIPCSender.Dispose();
+ ipcSenderHost.Dispose();
+ });
+ base.TearDownSteps();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs
index 7d9c08d3d2..2f0f2f68a5 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs
@@ -8,7 +8,9 @@ using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Screens;
using osu.Framework.Testing;
+using osu.Framework.Threading;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@@ -16,6 +18,7 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osu.Game.Skinning.Editor;
using osu.Game.Tests.Beatmaps.IO;
+using osuTK;
using osuTK.Input;
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
@@ -26,29 +29,6 @@ namespace osu.Game.Tests.Visual.Navigation
private TestPlaySongSelect songSelect;
private SkinEditor skinEditor => Game.ChildrenOfType().FirstOrDefault();
- private void advanceToSongSelect()
- {
- PushAndConfirm(() => songSelect = new TestPlaySongSelect());
- AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
-
- AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
-
- AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
- }
-
- private void openSkinEditor()
- {
- AddStep("open skin editor", () =>
- {
- InputManager.PressKey(Key.ControlLeft);
- InputManager.PressKey(Key.ShiftLeft);
- InputManager.Key(Key.S);
- InputManager.ReleaseKey(Key.ControlLeft);
- InputManager.ReleaseKey(Key.ShiftLeft);
- });
- AddUntilStep("skin editor loaded", () => skinEditor != null);
- }
-
[Test]
public void TestEditComponentDuringGameplay()
{
@@ -88,6 +68,68 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default);
}
+ [Test]
+ public void TestComponentsDeselectedOnSkinEditorHide()
+ {
+ advanceToSongSelect();
+ openSkinEditor();
+ switchToGameplayScene();
+
+ AddUntilStep("wait for components", () => skinEditor.ChildrenOfType().Any());
+
+ AddStep("select all components", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Key(Key.A);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ AddUntilStep("components selected", () => skinEditor.SelectedComponents.Count > 0);
+
+ toggleSkinEditor();
+
+ AddUntilStep("no components selected", () => skinEditor.SelectedComponents.Count == 0);
+ }
+
+ [Test]
+ public void TestSwitchScreenWhileDraggingComponent()
+ {
+ Vector2 firstBlueprintCentre = Vector2.Zero;
+ ScheduledDelegate movementDelegate = null;
+
+ advanceToSongSelect();
+
+ openSkinEditor();
+
+ AddStep("add skinnable component", () =>
+ {
+ skinEditor.ChildrenOfType().First().TriggerClick();
+ });
+
+ AddUntilStep("newly added component selected", () => skinEditor.SelectedComponents.Count == 1);
+
+ AddStep("start drag", () =>
+ {
+ firstBlueprintCentre = skinEditor.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre;
+
+ InputManager.MoveMouseTo(firstBlueprintCentre);
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ AddStep("start movement", () => movementDelegate = Scheduler.AddDelayed(() => { InputManager.MoveMouseTo(firstBlueprintCentre += new Vector2(1)); }, 10, true));
+
+ toggleSkinEditor();
+ AddStep("exit song select", () => songSelect.Exit());
+
+ AddUntilStep("wait for blueprints removed", () => !skinEditor.ChildrenOfType().Any());
+
+ AddStep("stop drag", () =>
+ {
+ InputManager.ReleaseButton(MouseButton.Left);
+ movementDelegate?.Cancel();
+ });
+ }
+
[Test]
public void TestAutoplayCompatibleModsRetainedOnEnteringGameplay()
{
@@ -146,8 +188,35 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
}
+ private void advanceToSongSelect()
+ {
+ PushAndConfirm(() => songSelect = new TestPlaySongSelect());
+ AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
+ }
+
+ private void openSkinEditor()
+ {
+ toggleSkinEditor();
+ AddUntilStep("skin editor loaded", () => skinEditor != null);
+ }
+
+ private void toggleSkinEditor()
+ {
+ AddStep("toggle skin editor", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.Key(Key.S);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ }
+
private void switchToGameplayScene()
{
+ AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
+ AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
+
AddStep("Click gameplay scene button", () =>
{
InputManager.MoveMouseTo(skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay"));
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 1fb0195368..d2d9b9a9e5 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Online
private ChannelManager channelManager;
private APIUser testUser;
- private Channel testPMChannel;
private Channel[] testChannels;
private Channel testChannel1 => testChannels[0];
@@ -53,7 +52,6 @@ namespace osu.Game.Tests.Visual.Online
public void SetUp() => Schedule(() =>
{
testUser = new APIUser { Username = "test user", Id = 5071479 };
- testPMChannel = new Channel(testUser);
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
Child = new DependencyProvidingContainer
@@ -80,6 +78,14 @@ namespace osu.Game.Tests.Visual.Online
{
switch (req)
{
+ case CreateChannelRequest createRequest:
+ createRequest.TriggerSuccess(new APIChatChannel
+ {
+ ChannelID = ((int)createRequest.Channel.Id),
+ RecentMessages = new List()
+ });
+ return true;
+
case GetUpdatesRequest getUpdates:
getUpdates.TriggerFailure(new WebException());
return true;
@@ -181,7 +187,7 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("Show overlay", () => chatOverlay.Show());
AddAssert("Listing is visible", () => listingIsVisible);
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
waitForChannel1Visible();
}
@@ -203,12 +209,11 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestChannelCloseButton()
{
+ var testPMChannel = new Channel(testUser);
+
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join PM and public channels", () =>
- {
- channelManager.JoinChannel(testChannel1);
- channelManager.JoinChannel(testPMChannel);
- });
+ joinTestChannel(0);
+ joinChannel(testPMChannel);
AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
AddStep("Click close button", () =>
{
@@ -229,7 +234,7 @@ namespace osu.Game.Tests.Visual.Online
public void TestChatCommand()
{
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
AddAssert("PM channel is selected", () =>
@@ -248,14 +253,16 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestMultiplayerChannelIsNotShown()
{
- Channel multiplayerChannel = null;
+ Channel multiplayerChannel;
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
+
+ joinChannel(multiplayerChannel = new Channel(new APIUser())
{
Name = "#mp_1",
Type = ChannelType.Multiplayer,
- }));
+ });
+
AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel));
AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType()
.Where(item => item.IsPresent)
@@ -269,7 +276,7 @@ namespace osu.Game.Tests.Visual.Online
Message message = null;
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 1", () =>
{
@@ -291,8 +298,8 @@ namespace osu.Game.Tests.Visual.Online
Message message = null;
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ joinTestChannel(0);
+ joinTestChannel(1);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 2", () =>
{
@@ -314,8 +321,8 @@ namespace osu.Game.Tests.Visual.Online
Message message = null;
AddStep("Show overlay", () => chatOverlay.Show());
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ joinTestChannel(0);
+ joinTestChannel(1);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddStep("Send message in channel 2", () =>
{
@@ -337,7 +344,7 @@ namespace osu.Game.Tests.Visual.Online
{
Message message = null;
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Send message in channel 1", () =>
{
testChannel1.AddNewMessages(message = new Message
@@ -357,7 +364,7 @@ namespace osu.Game.Tests.Visual.Online
{
Message message = null;
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Send message in channel 1", () =>
{
testChannel1.AddNewMessages(message = new Message
@@ -378,7 +385,7 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("Show overlay", () => chatOverlay.Show());
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
waitForChannel1Visible();
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
@@ -404,11 +411,11 @@ namespace osu.Game.Tests.Visual.Online
chatOverlay.Show();
chatOverlay.SlowLoading = true;
});
- AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
+ joinTestChannel(0);
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddUntilStep("Channel 1 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel1).LoadState == LoadState.Loading);
- AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
+ joinTestChannel(1);
AddStep("Select channel 2", () => clickDrawable(getChannelListItem(testChannel2)));
AddUntilStep("Channel 2 loading", () => !channelIsVisible && chatOverlay.GetSlowLoadingChannel(testChannel2).LoadState == LoadState.Loading);
@@ -461,19 +468,17 @@ namespace osu.Game.Tests.Visual.Online
Channel pmChannel1 = createPrivateChannel();
Channel pmChannel2 = createPrivateChannel();
- AddStep("Show overlay with channels", () =>
- {
- channelManager.JoinChannel(testChannel1);
- channelManager.JoinChannel(testChannel2);
- channelManager.JoinChannel(pmChannel1);
- channelManager.JoinChannel(pmChannel2);
- channelManager.JoinChannel(announceChannel);
- chatOverlay.Show();
- });
+ joinTestChannel(0);
+ joinTestChannel(1);
+ joinChannel(pmChannel1);
+ joinChannel(pmChannel2);
+ joinChannel(announceChannel);
+
+ AddStep("Show overlay", () => chatOverlay.Show());
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
-
waitForChannel1Visible();
+
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
waitForChannel2Visible();
@@ -490,6 +495,18 @@ namespace osu.Game.Tests.Visual.Online
waitForChannel1Visible();
}
+ private void joinTestChannel(int i)
+ {
+ AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
+ AddUntilStep("wait for join completed", () => testChannels[i].Joined.Value);
+ }
+
+ private void joinChannel(Channel channel)
+ {
+ AddStep($"Join channel {channel}", () => channelManager.JoinChannel(channel));
+ AddUntilStep("wait for join completed", () => channel.Joined.Value);
+ }
+
private void waitForChannel1Visible() =>
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel1);
@@ -549,7 +566,7 @@ namespace osu.Game.Tests.Visual.Online
private Channel createPrivateChannel()
{
- int id = RNG.Next(0, 10000);
+ int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
return new Channel(new APIUser
{
Id = id,
@@ -559,7 +576,7 @@ namespace osu.Game.Tests.Visual.Online
private Channel createAnnounceChannel()
{
- int id = RNG.Next(0, 10000);
+ int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
return new Channel
{
Name = $"Announce {id}",
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
index f28eaf5ad0..266e98db15 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
@@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Online
{
base.LoadComplete();
- Metadata.BindValueChanged(metadata =>
+ Metadata.BindValueChanged(_ =>
{
foreach (var b in this.ChildrenOfType())
b.Action = () => YearChanged?.Invoke(b.Year);
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
index a46e675370..8a04cd96fe 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs
@@ -187,8 +187,8 @@ namespace osu.Game.Tests.Visual.Playlists
// pre-check for requests we should be handling (as they are scheduled below).
switch (request)
{
- case ShowPlaylistUserScoreRequest _:
- case IndexPlaylistScoresRequest _:
+ case ShowPlaylistUserScoreRequest:
+ case IndexPlaylistScoresRequest:
break;
default:
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
index abf65602f9..9791bb6248 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Settings
public void ToggleVisibility()
{
AddWaitStep("wait some", 5);
- AddToggleStep("toggle visibility", visible => settings.ToggleVisibility());
+ AddToggleStep("toggle visibility", _ => settings.ToggleVisibility());
}
[Test]
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index 77670c38f3..4510fda11d 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo
{
- Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException(),
+ Ruleset = rulesets.GetRuleset(3) ?? throw new InvalidOperationException("osu!mania ruleset not found"),
Difficulty = new BeatmapDifficulty
{
CircleSize = 5,
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index 6490fd822e..f9d18f4236 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -91,19 +91,19 @@ namespace osu.Game.Tests.Visual.SongSelect
switch (instance)
{
- case OsuRuleset _:
+ case OsuRuleset:
testInfoLabels(5);
break;
- case TaikoRuleset _:
+ case TaikoRuleset:
testInfoLabels(5);
break;
- case CatchRuleset _:
+ case CatchRuleset:
testInfoLabels(5);
break;
- case ManiaRuleset _:
+ case ManiaRuleset:
testInfoLabels(4);
break;
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 9fcd470d17..6d881555da 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -872,10 +872,10 @@ namespace osu.Game.Tests.Visual.SongSelect
return set != null;
});
- FilterableGroupedDifficultyIcon groupIcon = null;
+ GroupedDifficultyIcon groupIcon = null;
AddUntilStep("Find group icon for different ruleset", () =>
{
- return (groupIcon = set.ChildrenOfType()
+ return (groupIcon = set.ChildrenOfType()
.FirstOrDefault(icon => icon.Items.First().BeatmapInfo.Ruleset.OnlineID == 3)) != null;
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index 27c107a2db..368babc9b5 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("Set time before zero", () =>
{
- beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) =>
+ beatContainer.NewBeat = (i, timingControlPoint, _, _) =>
{
lastActuationTime = gameplayClockContainer.CurrentTime;
lastTimingPoint = timingControlPoint;
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("Set time before zero", () =>
{
- beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) =>
+ beatContainer.NewBeat = (i, timingControlPoint, _, _) =>
{
lastBeatIndex = i;
lastBpm = timingControlPoint.BPM;
@@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("bind event", () =>
{
- beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) => lastBpm = timingControlPoint.BPM;
+ beatContainer.NewBeat = (_, timingControlPoint, _, _) => lastBpm = timingControlPoint.BPM;
});
AddUntilStep("wait for trigger", () => lastBpm != null);
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
actualEffectPoint = null;
beatContainer.AllowMistimedEventFiring = false;
- beatContainer.NewBeat = (i, timingControlPoint, effectControlPoint, channelAmplitudes) =>
+ beatContainer.NewBeat = (_, _, effectControlPoint, _) =>
{
if (Precision.AlmostEquals(gameplayClockContainer.CurrentTime + earlyActivationMilliseconds, expectedEffectPoint.Time, BeatSyncedContainer.MISTIMED_ALLOWANCE))
actualEffectPoint = effectControlPoint;
@@ -269,7 +269,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
- if (timingPoints[^1] == current)
+ if (ReferenceEquals(timingPoints[^1], current))
return current;
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
@@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
if (timingPoints.Count == 0) return 0;
- if (timingPoints[^1] == current)
+ if (ReferenceEquals(timingPoints[^1], current))
{
Debug.Assert(BeatSyncSource.Clock != null);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
index 1107fad834..44f2da2b95 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
@@ -77,13 +77,13 @@ namespace osu.Game.Tests.Visual.UserInterface
};
control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true);
- control.General.BindCollectionChanged((u, v) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true);
+ control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true);
control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true);
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true);
- control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
- control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true);
+ control.Extra.BindCollectionChanged((_, _) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
+ control.Ranks.BindCollectionChanged((_, _) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true);
control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true);
control.ExplicitContent.BindValueChanged(e => explicitMap.Text = $"Explicit Maps: {e.NewValue}", true);
});
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
index a3ae55670a..b845b85e1f 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.UserInterface
notificationOverlay.Reset();
performer.Setup(g => g.PerformFromScreen(It.IsAny>(), It.IsAny>()))
- .Callback((Action action, IEnumerable types) => action(null));
+ .Callback((Action action, IEnumerable _) => action(null));
notificationOverlay.Setup(n => n.Post(It.IsAny()))
.Callback((Notification n) => lastNotification = n);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs
index f717bb4dee..7ce0fceff9 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Origin = Anchor.Centre,
Width = 500,
AutoSizeAxes = Axes.Y,
- Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(),
+ Child = component = padded ? new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(),
};
component.Label = "a sample component";
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
index 50817bf804..72cddc0ad2 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModColumn.cs
@@ -9,9 +9,11 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
+using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
+using osu.Game.Overlays.Mods.Input;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Utils;
@@ -25,6 +27,9 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
+ [Resolved]
+ private OsuConfigManager configManager { get; set; } = null!;
+
[TestCase(ModType.DifficultyReduction)]
[TestCase(ModType.DifficultyIncrease)]
[TestCase(ModType.Conversion)]
@@ -132,14 +137,16 @@ namespace osu.Game.Tests.Visual.UserInterface
}
[Test]
- public void TestKeyboardSelection()
+ public void TestSequentialKeyboardSelection()
{
+ AddStep("set sequential hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential));
+
ModColumn column = null!;
AddStep("create content", () => Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(30),
- Child = column = new ModColumn(ModType.DifficultyReduction, true, new[] { Key.Q, Key.W, Key.E, Key.R, Key.T, Key.Y, Key.U, Key.I, Key.O, Key.P })
+ Child = column = new ModColumn(ModType.DifficultyReduction, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -158,9 +165,12 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set filter to NF", () => setFilter(mod => mod.Acronym == "NF"));
AddStep("press W", () => InputManager.Key(Key.W));
+ AddAssert("NF panel not selected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
+
+ AddStep("press Q", () => InputManager.Key(Key.Q));
AddAssert("NF panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
- AddStep("press W again", () => InputManager.Key(Key.W));
+ AddStep("press Q again", () => InputManager.Key(Key.Q));
AddAssert("NF panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NF").Active.Value);
AddStep("filter out everything", () => setFilter(_ => false));
@@ -171,6 +181,113 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("clear filter", () => setFilter(null));
}
+ [Test]
+ public void TestClassicKeyboardExclusiveSelection()
+ {
+ AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic));
+
+ ModColumn column = null!;
+ AddStep("create content", () => Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(30),
+ Child = column = new ModColumn(ModType.DifficultyIncrease, false)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
+ }
+ });
+
+ AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded);
+
+ AddStep("press A", () => InputManager.Key(Key.A));
+ AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
+
+ AddStep("press A again", () => InputManager.Key(Key.A));
+ AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
+
+ AddStep("press D", () => InputManager.Key(Key.D));
+ AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+
+ AddStep("press D again", () => InputManager.Key(Key.D));
+ AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press D again", () => InputManager.Key(Key.D));
+ AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press Shift-D", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.Key(Key.D);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press J", () => InputManager.Key(Key.J));
+ AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC");
+
+ AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC"));
+
+ AddStep("press A", () => InputManager.Key(Key.A));
+ AddAssert("no change", () => this.ChildrenOfType().Single(panel => panel.Active.Value).Mod.Acronym == "NC");
+ }
+
+ [Test]
+ public void TestClassicKeyboardIncompatibleSelection()
+ {
+ AddStep("set classic hotkey mode", () => configManager.SetValue(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Classic));
+
+ ModColumn column = null!;
+ AddStep("create content", () => Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(30),
+ Child = column = new ModColumn(ModType.DifficultyIncrease, true)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AvailableMods = getExampleModsFor(ModType.DifficultyIncrease)
+ }
+ });
+
+ AddUntilStep("wait for panel load", () => column.IsLoaded && column.ItemsLoaded);
+
+ AddStep("press A", () => InputManager.Key(Key.A));
+ AddAssert("HR panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
+
+ AddStep("press A again", () => InputManager.Key(Key.A));
+ AddAssert("HR panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "HR").Active.Value);
+
+ AddStep("press D", () => InputManager.Key(Key.D));
+ AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press D again", () => InputManager.Key(Key.D));
+ AddAssert("DT panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel deselected", () => !this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press Shift-D", () =>
+ {
+ InputManager.PressKey(Key.ShiftLeft);
+ InputManager.Key(Key.D);
+ InputManager.ReleaseKey(Key.ShiftLeft);
+ });
+ AddAssert("DT panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "DT").Active.Value);
+ AddAssert("NC panel selected", () => this.ChildrenOfType().Single(panel => panel.Mod.Acronym == "NC").Active.Value);
+
+ AddStep("press J", () => InputManager.Key(Key.J));
+ AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2);
+
+ AddStep("filter everything but NC", () => setFilter(mod => mod.Acronym == "NC"));
+
+ AddStep("press A", () => InputManager.Key(Key.A));
+ AddAssert("no change", () => this.ChildrenOfType().Count(panel => panel.Active.Value) == 2);
+ }
+
private void setFilter(Func? filter)
{
foreach (var modState in this.ChildrenOfType().Single().AvailableMods)
@@ -181,8 +298,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public new bool SelectionAnimationRunning => base.SelectionAnimationRunning;
- public TestModColumn(ModType modType, bool allowBulkSelection)
- : base(modType, allowBulkSelection)
+ public TestModColumn(ModType modType, bool allowIncompatibleSelection)
+ : base(modType, allowIncompatibleSelection)
{
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
index 72e503dc33..181f46a996 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
@@ -13,7 +13,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
-using osu.Game.Overlays.Settings;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osuTK;
@@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep($"Set {name} slider to {value}", () =>
this.ChildrenOfType().First(c => c.LabelText == name)
- .ChildrenOfType>().First().Current.Value = value);
+ .ChildrenOfType>().First().Current.Value = value);
}
private void checkBindableAtValue(string name, float? expectedValue)
@@ -260,7 +260,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddAssert($"Slider {name} at {expectedValue}", () =>
this.ChildrenOfType().First(c => c.LabelText == name)
- .ChildrenOfType>().First().Current.Value == expectedValue);
+ .ChildrenOfType>().First().Current.Value == expectedValue);
}
private void setBeatmapWithDifficultyParameters(float value)
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index 31061dc109..4de70f6f9e 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual.UserInterface
createScreen();
AddStep("select diff adjust", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
- AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8);
+ AddStep("set setting", () => modSelectOverlay.ChildrenOfType>().First().Current.Value = 8);
AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType().Single().CircleSize.Value == 8);
@@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("double time not visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).All(panel => panel.Filtered.Value));
AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value));
- AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = m => true);
+ AddStep("make double time valid again", () => modSelectOverlay.IsValidMod = _ => true);
AddUntilStep("double time visible", () => modSelectOverlay.ChildrenOfType().Where(panel => panel.Mod is OsuModDoubleTime).Any(panel => !panel.Filtered.Value));
AddAssert("nightcore still visible", () => modSelectOverlay.ChildrenOfType().Where(b => b.Mod is OsuModNightcore).Any(panel => !panel.Filtered.Value));
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs
index 28599c740e..bab2121d70 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs
@@ -3,7 +3,6 @@
#nullable disable
-using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
@@ -89,7 +88,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddToggleStep("toggle enabled", toggle =>
{
for (int i = 0; i < 6; i++)
- button.Action = toggle ? () => { } : (Action)null;
+ button.Action = toggle ? () => { } : null;
});
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs
index 0e31a133ac..d4c2bfd422 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs
@@ -3,7 +3,6 @@
#nullable disable
-using System;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
@@ -29,7 +28,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddToggleStep("toggle enabled", toggle =>
{
for (int i = 0; i < 6; i++)
- button.Action = toggle ? () => { } : (Action)null;
+ button.Action = toggle ? () => { } : null;
});
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs
index 4c35ec40b5..ee5ef2f364 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private Live first;
+ private const int item_count = 100;
+
[SetUp]
public void Setup() => Schedule(() =>
{
@@ -46,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface
beatmapSets.Clear();
- for (int i = 0; i < 100; i++)
+ for (int i = 0; i < item_count; i++)
{
beatmapSets.Add(TestResources.CreateTestBeatmapSetInfo().ToLiveUnmanaged());
}
@@ -59,6 +61,13 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestRearrangeItems()
{
+ AddUntilStep("wait for load complete", () =>
+ {
+ return this
+ .ChildrenOfType()
+ .Count(i => i.ChildrenOfType().First().DelayedLoadCompleted) > 6;
+ });
+
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
AddStep("hold 1st item handle", () =>
diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs
index 8eccb9e0e0..76d12a6b03 100644
--- a/osu.Game.Tournament/Components/DateTextBox.cs
+++ b/osu.Game.Tournament/Components/DateTextBox.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Components
{
base.Current = new Bindable(string.Empty);
- ((OsuTextBox)Control).OnCommit += (sender, newText) =>
+ ((OsuTextBox)Control).OnCommit += (sender, _) =>
{
try
{
diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
index 6e9c1120e4..348fd8cd76 100644
--- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs
+++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Components
FillMode = FillMode.Fill
};
- (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true);
+ (flag = team.FlagName.GetBoundCopy()).BindValueChanged(_ => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true);
}
}
}
diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs
index b7e936026c..e64e08a921 100644
--- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs
+++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Components
{
if (team == null) return;
- (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(acronym => Text.Text = team?.FullName.Value ?? string.Empty, true);
+ (acronym = team.Acronym.GetBoundCopy()).BindValueChanged(_ => Text.Text = team?.FullName.Value ?? string.Empty, true);
}
}
}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
index 84cae92a39..eb1dde21e7 100644
--- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
+++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Components
{
if (Team == null) return;
- (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true);
+ (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(_ => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true);
}
}
}
diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs
index 9dbe23b4b3..ac57f748da 100644
--- a/osu.Game.Tournament/Models/TournamentTeam.cs
+++ b/osu.Game.Tournament/Models/TournamentTeam.cs
@@ -66,14 +66,14 @@ namespace osu.Game.Tournament.Models
{
// use a sane default flag name based on acronym.
if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase))
- FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpper() : string.Empty;
+ FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpperInvariant() : string.Empty;
};
FullName.ValueChanged += val =>
{
// use a sane acronym based on full name.
if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase))
- Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpper() : string.Empty;
+ Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpperInvariant() : string.Empty;
};
}
diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
index 58ba3c1e8b..32da4d1b36 100644
--- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
+++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs
@@ -219,7 +219,7 @@ namespace osu.Game.Tournament.Screens.Drawings
}
}
- writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run(writeAction);
+ writeOp = writeOp?.ContinueWith(_ => { writeAction(); }) ?? Task.Run(writeAction);
}
private void reloadTeams()
diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
index 6052bcdeb7..4261828df2 100644
--- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Tournament.Screens.Editors
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
- LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage();
+ LadderInfo.Matches.CollectionChanged += (_, _) => updateMessage();
updateMessage();
}
diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs
index ac196130d6..466b9ed482 100644
--- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs
+++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs
@@ -49,10 +49,10 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
};
name = round.Name.GetBoundCopy();
- name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true);
+ name.BindValueChanged(_ => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true);
description = round.Description.GetBoundCopy();
- description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true);
+ description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true);
}
}
}
diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
index f274503894..23bfa84afc 100644
--- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
+++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs
@@ -75,7 +75,7 @@ namespace osu.Game.Tournament.Screens.Ladder
foreach (var match in LadderInfo.Matches)
addMatch(match);
- LadderInfo.Rounds.CollectionChanged += (_, __) => layout.Invalidate();
+ LadderInfo.Rounds.CollectionChanged += (_, _) => layout.Invalidate();
LadderInfo.Matches.CollectionChanged += (_, args) =>
{
switch (args.Action)
diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
index df6e8e816e..925c697346 100644
--- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
+++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
@@ -198,7 +198,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
{
row.Add(new Sprite
{
- Texture = textures.Get($"Mods/{mods.ToLower()}"),
+ Texture = textures.Get($"Mods/{mods.ToLowerInvariant()}"),
Scale = new Vector2(0.5f)
});
}
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index c2c6c271cb..537fbfc038 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -137,7 +137,7 @@ namespace osu.Game.Tournament
heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0;
}), true);
- windowMode.BindValueChanged(mode => ScheduleAfterChildren(() =>
+ windowMode.BindValueChanged(_ => ScheduleAfterChildren(() =>
{
windowMode.Value = WindowMode.Windowed;
}), true);
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 95d2adc4fa..75c9f17d4c 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -62,7 +62,7 @@ namespace osu.Game.Tournament
dependencies.Cache(new TournamentVideoResourceStore(storage));
- Textures.AddStore(new TextureLoaderStore(new StorageBackedResourceStore(storage)));
+ Textures.AddTextureSource(new TextureLoaderStore(new StorageBackedResourceStore(storage)));
dependencies.CacheAs(new StableInfo(storage));
}
@@ -267,7 +267,7 @@ namespace osu.Game.Tournament
}
else
{
- req.Success += res => { populate(); };
+ req.Success += _ => { populate(); };
req.Failure += _ =>
{
user.OnlineID = 1;
diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs
index afbfc2d368..296b259d72 100644
--- a/osu.Game.Tournament/TournamentSceneManager.cs
+++ b/osu.Game.Tournament/TournamentSceneManager.cs
@@ -204,12 +204,12 @@ namespace osu.Game.Tournament
switch (currentScreen)
{
- case MapPoolScreen _:
+ case MapPoolScreen:
chatContainer.FadeIn(TournamentScreen.FADE_DELAY);
chatContainer.ResizeWidthTo(1, 500, Easing.OutQuint);
break;
- case GameplayScreen _:
+ case GameplayScreen:
chatContainer.FadeIn(TournamentScreen.FADE_DELAY);
chatContainer.ResizeWidthTo(0.5f, 500, Easing.OutQuint);
break;
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
index 69488277f1..ef0fa36b16 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
@@ -1,22 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Lists;
using osu.Framework.Logging;
using osu.Framework.Threading;
-using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
@@ -50,31 +46,31 @@ namespace osu.Game.Beatmaps
///
private readonly object bindableUpdateLock = new object();
- private CancellationTokenSource trackedUpdateCancellationSource;
+ private CancellationTokenSource trackedUpdateCancellationSource = new CancellationTokenSource();
[Resolved]
- private BeatmapManager beatmapManager { get; set; }
+ private BeatmapManager beatmapManager { get; set; } = null!;
[Resolved]
- private Bindable currentRuleset { get; set; }
+ private Bindable currentRuleset { get; set; } = null!;
[Resolved]
- private Bindable> currentMods { get; set; }
+ private Bindable> currentMods { get; set; } = null!;
- private ModSettingChangeTracker modSettingChangeTracker;
- private ScheduledDelegate debouncedModSettingsChange;
+ private ModSettingChangeTracker? modSettingChangeTracker;
+ private ScheduledDelegate? debouncedModSettingsChange;
protected override void LoadComplete()
{
base.LoadComplete();
- currentRuleset.BindValueChanged(_ => updateTrackedBindables());
+ currentRuleset.BindValueChanged(_ => Scheduler.AddOnce(updateTrackedBindables));
currentMods.BindValueChanged(mods =>
{
modSettingChangeTracker?.Dispose();
- updateTrackedBindables();
+ Scheduler.AddOnce(updateTrackedBindables);
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += _ =>
@@ -91,9 +87,11 @@ namespace osu.Game.Beatmaps
/// The to get the difficulty of.
/// An optional which stops updating the star difficulty for the given .
/// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state (but not during updates to ruleset and mods if a stale value is already propagated).
- public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
+ public IBindable GetBindableDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
{
- var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
+ var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken);
+
+ updateBindable(bindable, currentRuleset.Value, currentMods.Value, cancellationToken);
lock (bindableUpdateLock)
trackedBindables.Add(bindable);
@@ -101,21 +99,6 @@ namespace osu.Game.Beatmaps
return bindable;
}
- ///
- /// Retrieves a bindable containing the star difficulty of a with a given and combination.
- ///
- ///
- /// The bindable will not update to follow the currently-selected ruleset and mods or its settings.
- ///
- /// The to get the difficulty of.
- /// The to get the difficulty with. If null, the 's ruleset is used.
- /// The s to get the difficulty with. If null, no mods will be assumed.
- /// An optional which stops updating the star difficulty for the given .
- /// A bindable that is updated to contain the star difficulty when it becomes available. Will be null while in an initial calculating state.
- public IBindable GetBindableDifficulty([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods,
- CancellationToken cancellationToken = default)
- => createBindable(beatmapInfo, rulesetInfo, mods, cancellationToken);
-
///
/// Retrieves the difficulty of a .
///
@@ -128,8 +111,8 @@ namespace osu.Game.Beatmaps
/// A return value indicates that the difficulty process failed or was interrupted early,
/// and as such there is no usable star difficulty value to be returned.
///
- public virtual Task GetDifficultyAsync([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo rulesetInfo = null,
- [CanBeNull] IEnumerable mods = null, CancellationToken cancellationToken = default)
+ public virtual Task GetDifficultyAsync(IBeatmapInfo beatmapInfo, IRulesetInfo? rulesetInfo = null,
+ IEnumerable? mods = null, CancellationToken cancellationToken = default)
{
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
rulesetInfo ??= beatmapInfo.Ruleset;
@@ -168,34 +151,6 @@ namespace osu.Game.Beatmaps
updateScheduler);
}
- ///
- /// Retrieves the that describes a star rating.
- ///
- ///
- /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties
- ///
- /// The star rating.
- /// The that best describes .
- public static DifficultyRating GetDifficultyRating(double starRating)
- {
- if (Precision.AlmostBigger(starRating, 6.5, 0.005))
- return DifficultyRating.ExpertPlus;
-
- if (Precision.AlmostBigger(starRating, 5.3, 0.005))
- return DifficultyRating.Expert;
-
- if (Precision.AlmostBigger(starRating, 4.0, 0.005))
- return DifficultyRating.Insane;
-
- if (Precision.AlmostBigger(starRating, 2.7, 0.005))
- return DifficultyRating.Hard;
-
- if (Precision.AlmostBigger(starRating, 2.0, 0.005))
- return DifficultyRating.Normal;
-
- return DifficultyRating.Easy;
- }
-
///
/// Updates all tracked using the current ruleset and mods.
///
@@ -204,7 +159,6 @@ namespace osu.Game.Beatmaps
lock (bindableUpdateLock)
{
cancelTrackedBindableUpdate();
- trackedUpdateCancellationSource = new CancellationTokenSource();
foreach (var b in trackedBindables)
{
@@ -223,35 +177,16 @@ namespace osu.Game.Beatmaps
{
lock (bindableUpdateLock)
{
- trackedUpdateCancellationSource?.Cancel();
- trackedUpdateCancellationSource = null;
+ trackedUpdateCancellationSource.Cancel();
+ trackedUpdateCancellationSource = new CancellationTokenSource();
- if (linkedCancellationSources != null)
- {
- foreach (var c in linkedCancellationSources)
- c.Dispose();
+ foreach (var c in linkedCancellationSources)
+ c.Dispose();
- linkedCancellationSources.Clear();
- }
+ linkedCancellationSources.Clear();
}
}
- ///
- /// Creates a new and triggers an initial value update.
- ///
- /// The that star difficulty should correspond to.
- /// The initial to get the difficulty with.
- /// The initial s to get the difficulty with.
- /// An optional which stops updating the star difficulty for the given .
- /// The .
- private BindableStarDifficulty createBindable([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo initialRulesetInfo, [CanBeNull] IEnumerable initialMods,
- CancellationToken cancellationToken)
- {
- var bindable = new BindableStarDifficulty(beatmapInfo, cancellationToken);
- updateBindable(bindable, initialRulesetInfo, initialMods, cancellationToken);
- return bindable;
- }
-
///
/// Updates the value of a with a given ruleset + mods.
///
@@ -259,7 +194,7 @@ namespace osu.Game.Beatmaps
/// The to update with.
/// The s to update with.
/// A token that may be used to cancel this update.
- private void updateBindable([NotNull] BindableStarDifficulty bindable, [CanBeNull] IRulesetInfo rulesetInfo, [CanBeNull] IEnumerable mods, CancellationToken cancellationToken = default)
+ private void updateBindable(BindableStarDifficulty bindable, IRulesetInfo? rulesetInfo, IEnumerable? mods, CancellationToken cancellationToken = default)
{
// GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available
// (contrary to GetAsync)
@@ -329,7 +264,7 @@ namespace osu.Game.Beatmaps
modSettingChangeTracker?.Dispose();
cancelTrackedBindableUpdate();
- updateScheduler?.Dispose();
+ updateScheduler.Dispose();
}
public readonly struct DifficultyCacheLookup : IEquatable
@@ -339,7 +274,7 @@ namespace osu.Game.Beatmaps
public readonly Mod[] OrderedMods;
- public DifficultyCacheLookup([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, IEnumerable mods)
+ public DifficultyCacheLookup(BeatmapInfo beatmapInfo, RulesetInfo? ruleset, IEnumerable? mods)
{
BeatmapInfo = beatmapInfo;
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs
index e463492e2b..d89541844b 100644
--- a/osu.Game/Beatmaps/BeatmapImporter.cs
+++ b/osu.Game/Beatmaps/BeatmapImporter.cs
@@ -18,7 +18,6 @@ using osu.Game.Database;
using osu.Game.Extensions;
using osu.Game.IO;
using osu.Game.IO.Archives;
-using osu.Game.Models;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Skinning;
@@ -49,7 +48,7 @@ namespace osu.Game.Beatmaps
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default)
{
if (archive != null)
- beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet.Files, realm));
+ beatmapSet.Beatmaps.AddRange(createBeatmapDifficulties(beatmapSet, realm));
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
{
@@ -177,8 +176,17 @@ namespace osu.Game.Beatmaps
}
Beatmap beatmap;
+
using (var stream = new LineBufferedReader(reader.GetStream(mapName)))
+ {
+ if (stream.PeekLine() == null)
+ {
+ Logger.Log($"No content found in first .osu file of beatmap archive ({reader.Name} / {mapName})", LoggingTarget.Database);
+ return null;
+ }
+
beatmap = Decoder.GetDecoder(stream).Decode(stream);
+ }
return new BeatmapSetInfo
{
@@ -191,23 +199,32 @@ namespace osu.Game.Beatmaps
///
/// Create all required s for the provided archive.
///
- private List createBeatmapDifficulties(IList files, Realm realm)
+ private List createBeatmapDifficulties(BeatmapSetInfo beatmapSet, Realm realm)
{
var beatmaps = new List();
- foreach (var file in files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)))
+ foreach (var file in beatmapSet.Files.Where(f => f.Filename.EndsWith(".osu", StringComparison.OrdinalIgnoreCase)))
{
using (var memoryStream = new MemoryStream(Files.Store.Get(file.File.GetStoragePath()))) // we need a memory stream so we can seek
{
IBeatmap decoded;
+
using (var lineReader = new LineBufferedReader(memoryStream, true))
+ {
+ if (lineReader.PeekLine() == null)
+ {
+ LogForModel(beatmapSet, $"No content found in beatmap file {file.Filename}.");
+ continue;
+ }
+
decoded = Decoder.GetDecoder(lineReader).Decode(lineReader);
+ }
string hash = memoryStream.ComputeSHA2Hash();
if (beatmaps.Any(b => b.Hash == hash))
{
- Logger.Log($"Skipping import of {file.Filename} due to duplicate file content.", LoggingTarget.Database);
+ LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to duplicate file content.");
continue;
}
@@ -218,7 +235,7 @@ namespace osu.Game.Beatmaps
if (ruleset?.Available != true)
{
- Logger.Log($"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.", LoggingTarget.Database);
+ LogForModel(beatmapSet, $"Skipping import of {file.Filename} due to missing local ruleset {decodedInfo.Ruleset.OnlineID}.");
continue;
}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 1dd3f82426..670dba14ec 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps
foreach (BeatmapInfo b in beatmapSet.Beatmaps)
b.BeatmapSet = beatmapSet;
- var imported = beatmapImporter.Import(beatmapSet);
+ var imported = beatmapImporter.ImportModel(beatmapSet);
if (imported == null)
throw new InvalidOperationException("Failed to import new beatmap");
@@ -409,11 +409,8 @@ namespace osu.Game.Beatmaps
public Task?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) =>
beatmapImporter.Import(task, batchImport, cancellationToken);
- public Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) =>
- beatmapImporter.Import(archive, batchImport, cancellationToken);
-
public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) =>
- beatmapImporter.Import(item, archive, false, cancellationToken);
+ beatmapImporter.ImportModel(item, archive, false, cancellationToken);
public IEnumerable HandledExtensions => beatmapImporter.HandledExtensions;
@@ -421,22 +418,24 @@ namespace osu.Game.Beatmaps
#region Implementation of IWorkingBeatmapCache
- public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? importedBeatmap)
+ public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo? beatmapInfo)
{
// Detached sets don't come with files.
// If we seem to be missing files, now is a good time to re-fetch.
- if (importedBeatmap?.BeatmapSet?.Files.Count == 0)
+ if (beatmapInfo?.IsManaged == true || beatmapInfo?.BeatmapSet?.Files.Count == 0)
{
Realm.Run(r =>
{
- var refetch = r.Find(importedBeatmap.ID)?.Detach();
+ var refetch = r.Find(beatmapInfo.ID)?.Detach();
if (refetch != null)
- importedBeatmap = refetch;
+ beatmapInfo = refetch;
});
}
- return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap);
+ Debug.Assert(beatmapInfo?.IsManaged != true);
+
+ return workingBeatmapCache.GetWorkingBeatmap(beatmapInfo);
}
void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo);
@@ -457,9 +456,9 @@ namespace osu.Game.Beatmaps
#region Implementation of IPostImports
- public Action>>? PostImport
+ public Action>>? PresentImport
{
- set => beatmapImporter.PostImport = value;
+ set => beatmapImporter.PresentImport = value;
}
#endregion
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 5f06e03509..56a432aec4 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using Newtonsoft.Json;
using osu.Game.Graphics;
@@ -11,7 +9,7 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
- public abstract class ControlPoint : IComparable, IDeepCloneable
+ public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable
{
///
/// The time at which the control point takes effect.
@@ -30,7 +28,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// An existing control point to compare with.
/// Whether this is redundant when placed alongside .
- public abstract bool IsRedundant(ControlPoint existing);
+ public abstract bool IsRedundant(ControlPoint? existing);
///
/// Create an unbound copy of this control point.
@@ -48,5 +46,20 @@ namespace osu.Game.Beatmaps.ControlPoints
{
Time = other.Time;
}
+
+ public sealed override bool Equals(object? obj)
+ => obj is ControlPoint otherControlPoint
+ && Equals(otherControlPoint);
+
+ public virtual bool Equals(ControlPoint? other)
+ {
+ if (ReferenceEquals(other, null)) return false;
+ if (ReferenceEquals(other, this)) return true;
+
+ return Time == other.Time;
+ }
+
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ public override int GetHashCode() => Time.GetHashCode();
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
index 9df38b01ee..db479f0e5b 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs
@@ -1,18 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Linq;
using osu.Framework.Bindables;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class ControlPointGroup : IComparable
+ public class ControlPointGroup : IComparable, IEquatable
{
- public event Action ItemAdded;
- public event Action ItemRemoved;
+ public event Action? ItemAdded;
+ public event Action? ItemRemoved;
///
/// The time at which the control point takes effect.
@@ -48,5 +46,23 @@ namespace osu.Game.Beatmaps.ControlPoints
controlPoints.Remove(point);
ItemRemoved?.Invoke(point);
}
+
+ public sealed override bool Equals(object? obj)
+ => obj is ControlPointGroup otherGroup
+ && Equals(otherGroup);
+
+ public virtual bool Equals(ControlPointGroup? other)
+ => other != null
+ && Time == other.Time
+ && ControlPoints.SequenceEqual(other.ControlPoints);
+
+ public override int GetHashCode()
+ {
+ HashCode hashCode = new HashCode();
+ hashCode.Add(Time);
+ foreach (var point in controlPoints)
+ hashCode.Add(point);
+ return hashCode.ToHashCode();
+ }
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index eda7ef0bcc..4be6b5eede 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -254,12 +254,12 @@ namespace osu.Game.Beatmaps.ControlPoints
switch (newPoint)
{
- case TimingControlPoint _:
+ case TimingControlPoint:
// Timing points are a special case and need to be added regardless of fallback availability.
existing = BinarySearch(TimingPoints, time);
break;
- case EffectControlPoint _:
+ case EffectControlPoint:
existing = EffectPointAt(time);
break;
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 8b3d755fb6..c199d1da59 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System;
using osu.Framework.Bindables;
using osu.Game.Graphics;
using osuTK.Graphics;
@@ -12,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
///
- public class DifficultyControlPoint : ControlPoint
+ public class DifficultyControlPoint : ControlPoint, IEquatable
{
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
{
@@ -41,7 +40,7 @@ namespace osu.Game.Beatmaps.ControlPoints
set => SliderVelocityBindable.Value = value;
}
- public override bool IsRedundant(ControlPoint existing)
+ public override bool IsRedundant(ControlPoint? existing)
=> existing is DifficultyControlPoint existingDifficulty
&& SliderVelocity == existingDifficulty.SliderVelocity;
@@ -51,5 +50,15 @@ namespace osu.Game.Beatmaps.ControlPoints
base.CopyFrom(other);
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is DifficultyControlPoint otherDifficultyControlPoint
+ && Equals(otherDifficultyControlPoint);
+
+ public bool Equals(DifficultyControlPoint? other)
+ => base.Equals(other)
+ && SliderVelocity == other.SliderVelocity;
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SliderVelocity);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
index 35425972da..ead07b4eaa 100644
--- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs
@@ -1,15 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System;
using osu.Framework.Bindables;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class EffectControlPoint : ControlPoint
+ public class EffectControlPoint : ControlPoint, IEquatable
{
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
{
@@ -68,7 +67,7 @@ namespace osu.Game.Beatmaps.ControlPoints
set => KiaiModeBindable.Value = value;
}
- public override bool IsRedundant(ControlPoint existing)
+ public override bool IsRedundant(ControlPoint? existing)
=> !OmitFirstBarLine
&& existing is EffectControlPoint existingEffect
&& KiaiMode == existingEffect.KiaiMode
@@ -83,5 +82,17 @@ namespace osu.Game.Beatmaps.ControlPoints
base.CopyFrom(other);
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is EffectControlPoint otherEffectControlPoint
+ && Equals(otherEffectControlPoint);
+
+ public bool Equals(EffectControlPoint? other)
+ => base.Equals(other)
+ && OmitFirstBarLine == other.OmitFirstBarLine
+ && ScrollSpeed == other.ScrollSpeed
+ && KiaiMode == other.KiaiMode;
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), OmitFirstBarLine, ScrollSpeed, KiaiMode);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
index bed499ef3f..78dec67937 100644
--- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs
@@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
+using System;
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Graphics;
@@ -13,7 +12,7 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// Note that going forward, this control point type should always be assigned directly to HitObjects.
///
- public class SampleControlPoint : ControlPoint
+ public class SampleControlPoint : ControlPoint, IEquatable
{
public const string DEFAULT_BANK = "normal";
@@ -73,7 +72,7 @@ namespace osu.Game.Beatmaps.ControlPoints
public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo)
=> hitSampleInfo.With(newBank: hitSampleInfo.Bank ?? SampleBank, newVolume: hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume);
- public override bool IsRedundant(ControlPoint existing)
+ public override bool IsRedundant(ControlPoint? existing)
=> existing is SampleControlPoint existingSample
&& SampleBank == existingSample.SampleBank
&& SampleVolume == existingSample.SampleVolume;
@@ -85,5 +84,16 @@ namespace osu.Game.Beatmaps.ControlPoints
base.CopyFrom(other);
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is SampleControlPoint otherSampleControlPoint
+ && Equals(otherSampleControlPoint);
+
+ public bool Equals(SampleControlPoint? other)
+ => base.Equals(other)
+ && SampleBank == other.SampleBank
+ && SampleVolume == other.SampleVolume;
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), SampleBank, SampleVolume);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 922439fcb8..23d4d10fd8 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Bindables;
using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics;
@@ -8,7 +9,7 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
- public class TimingControlPoint : ControlPoint
+ public class TimingControlPoint : ControlPoint, IEquatable
{
///
/// The time signature at this control point.
@@ -68,7 +69,7 @@ namespace osu.Game.Beatmaps.ControlPoints
public double BPM => 60000 / BeatLength;
// Timing points are never redundant as they can change the time signature.
- public override bool IsRedundant(ControlPoint existing) => false;
+ public override bool IsRedundant(ControlPoint? existing) => false;
public override void CopyFrom(ControlPoint other)
{
@@ -77,5 +78,16 @@ namespace osu.Game.Beatmaps.ControlPoints
base.CopyFrom(other);
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is TimingControlPoint otherTimingControlPoint
+ && Equals(otherTimingControlPoint);
+
+ public bool Equals(TimingControlPoint? other)
+ => base.Equals(other)
+ && TimeSignature.Equals(other.TimeSignature)
+ && BeatLength.Equals(other.BeatLength);
+
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength);
}
}
diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
index 9b97df906b..80af4108c7 100644
--- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
+++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps.Drawables
// Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps.
var random = new LegacyRandom(DateTime.UtcNow.Year * 1000 + (DateTime.UtcNow.DayOfYear / 7));
- downloadableFilenames.AddRange(sourceFilenames.OrderBy(x => random.NextDouble()).Take(limit ?? int.MaxValue));
+ downloadableFilenames.AddRange(sourceFilenames.OrderBy(_ => random.NextDouble()).Take(limit ?? int.MaxValue));
}
catch { }
}
diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs
index 78481ac27a..bc0fcb92bb 100644
--- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs
+++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/FavouriteButton.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
};
favouriteRequest.Failure += e =>
{
- Logger.Error(e, $"Failed to {actionType.ToString().ToLower()} beatmap: {e.Message}");
+ Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}");
Enabled.Value = true;
};
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index a1b0f04aae..679e9c3665 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -1,12 +1,6 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
@@ -16,19 +10,17 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
-using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
- public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip
+ public class DifficultyIcon : CompositeDrawable, IHasCustomTooltip, IHasCurrentValue
{
- private readonly Container iconContainer;
-
///
/// Size of this difficulty icon.
///
@@ -38,57 +30,53 @@ namespace osu.Game.Beatmaps.Drawables
set => iconContainer.Size = value;
}
- [NotNull]
- private readonly IBeatmapInfo beatmapInfo;
+ ///
+ /// Whether to display a tooltip on hover. Only works if a beatmap was provided at construction time.
+ ///
+ public bool ShowTooltip { get; set; } = true;
+
+ private readonly IBeatmapInfo? beatmap;
- [CanBeNull]
private readonly IRulesetInfo ruleset;
- [CanBeNull]
- private readonly IReadOnlyList mods;
+ private Drawable background = null!;
- private readonly bool shouldShowTooltip;
+ private readonly Container iconContainer;
- private readonly bool performBackgroundDifficultyLookup;
+ private readonly BindableWithCurrent difficulty = new BindableWithCurrent();
- private readonly Bindable difficultyBindable = new Bindable();
-
- private Drawable background;
-
- ///
- /// Creates a new with a given and combination.
- ///
- /// The beatmap to show the difficulty of.
- /// The ruleset to show the difficulty with.
- /// The mods to show the difficulty with.
- /// Whether to display a tooltip when hovered.
- /// Whether to perform difficulty lookup (including calculation if necessary).
- public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
- : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup)
+ public virtual Bindable Current
{
- this.ruleset = ruleset ?? beatmapInfo.Ruleset;
- this.mods = mods ?? Array.Empty();
- }
-
- ///
- /// Creates a new that follows the currently-selected ruleset and mods.
- ///
- /// The beatmap to show the difficulty of.
- /// Whether to display a tooltip when hovered.
- /// Whether to perform difficulty lookup (including calculation if necessary).
- public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
- {
- this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo));
- this.shouldShowTooltip = shouldShowTooltip;
- this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup;
-
- AutoSizeAxes = Axes.Both;
-
- InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
+ get => difficulty.Current;
+ set => difficulty.Current = value;
}
[Resolved]
- private IRulesetStore rulesets { get; set; }
+ private IRulesetStore rulesets { get; set; } = null!;
+
+ ///
+ /// Creates a new . Will use provided beatmap's for initial value.
+ ///
+ /// The beatmap to be displayed in the tooltip, and to be used for the initial star rating value.
+ /// An optional ruleset to be used for the icon display, in place of the beatmap's ruleset.
+ public DifficultyIcon(IBeatmapInfo beatmap, IRulesetInfo? ruleset = null)
+ : this(ruleset ?? beatmap.Ruleset)
+ {
+ this.beatmap = beatmap;
+ Current.Value = new StarDifficulty(beatmap.StarRating, 0);
+ }
+
+ ///
+ /// Creates a new without an associated beatmap.
+ ///
+ /// The ruleset to be used for the icon display.
+ public DifficultyIcon(IRulesetInfo ruleset)
+ {
+ this.ruleset = ruleset;
+
+ AutoSizeAxes = Axes.Both;
+ InternalChild = iconContainer = new Container { Size = new Vector2(20f) };
+ }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
@@ -111,7 +99,6 @@ namespace osu.Game.Beatmaps.Drawables
Child = background = new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = colours.ForStarDifficulty(beatmapInfo.StarRating) // Default value that will be re-populated once difficulty calculation completes
},
},
new ConstrainedIconContainer
@@ -124,17 +111,12 @@ namespace osu.Game.Beatmaps.Drawables
},
};
- if (performBackgroundDifficultyLookup)
- iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
- else
- difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarRating, 0);
-
- difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars));
+ Current.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars), true);
}
private Drawable getRulesetIcon()
{
- int? onlineID = (ruleset ?? beatmapInfo.Ruleset).OnlineID;
+ int? onlineID = ruleset.OnlineID;
if (onlineID >= 0 && rulesets.GetRuleset(onlineID.Value)?.CreateInstance() is Ruleset rulesetInstance)
return rulesetInstance.CreateIcon();
@@ -142,51 +124,10 @@ namespace osu.Game.Beatmaps.Drawables
return new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle };
}
- ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip();
+ ITooltip IHasCustomTooltip.
+ GetCustomTooltip() => new DifficultyIconTooltip();
- DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null;
-
- private class DifficultyRetriever : Component
- {
- public readonly Bindable StarDifficulty = new Bindable();
-
- private readonly IBeatmapInfo beatmapInfo;
- private readonly IRulesetInfo ruleset;
- private readonly IReadOnlyList mods;
-
- private CancellationTokenSource difficultyCancellation;
-
- [Resolved]
- private BeatmapDifficultyCache difficultyCache { get; set; }
-
- public DifficultyRetriever(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, IReadOnlyList mods)
- {
- this.beatmapInfo = beatmapInfo;
- this.ruleset = ruleset;
- this.mods = mods;
- }
-
- private IBindable localStarDifficulty;
-
- [BackgroundDependencyLoader]
- private void load()
- {
- difficultyCancellation = new CancellationTokenSource();
- localStarDifficulty = ruleset != null
- ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token)
- : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token);
- localStarDifficulty.BindValueChanged(d =>
- {
- if (d.NewValue is StarDifficulty diff)
- StarDifficulty.Value = diff;
- });
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- difficultyCancellation?.Cancel();
- }
- }
+ DifficultyIconTooltipContent IHasCustomTooltip.
+ TooltipContent => (ShowTooltip && beatmap != null ? new DifficultyIconTooltipContent(beatmap, Current) : null)!;
}
}
diff --git a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
deleted file mode 100644
index 15ca4c60d4..0000000000
--- a/osu.Game/Beatmaps/Drawables/GroupedDifficultyIcon.cs
+++ /dev/null
@@ -1,39 +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 disable
-
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Graphics;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets;
-using osuTK.Graphics;
-
-namespace osu.Game.Beatmaps.Drawables
-{
- ///
- /// A difficulty icon that contains a counter on the right-side of it.
- ///
- ///
- /// Used in cases when there are too many difficulty icons to show.
- ///
- public class GroupedDifficultyIcon : DifficultyIcon
- {
- public GroupedDifficultyIcon(IEnumerable beatmaps, IRulesetInfo ruleset, Color4 counterColour)
- : base(beatmaps.OrderBy(b => b.StarRating).Last(), ruleset, null, false)
- {
- AddInternal(new OsuSpriteText
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- Padding = new MarginPadding { Left = Size.X },
- Margin = new MarginPadding { Left = 2, Right = 5 },
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
- Text = beatmaps.Count().ToString(),
- Colour = counterColour,
- });
- }
- }
-}
diff --git a/osu.Game/Beatmaps/EFBeatmapInfo.cs b/osu.Game/Beatmaps/EFBeatmapInfo.cs
index 34311448eb..20abdc686a 100644
--- a/osu.Game/Beatmaps/EFBeatmapInfo.cs
+++ b/osu.Game/Beatmaps/EFBeatmapInfo.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps
public List Scores { get; set; }
[JsonIgnore]
- public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarRating);
+ public DifficultyRating DifficultyRating => StarDifficulty.GetDifficultyRating(StarRating);
public override string ToString() => this.GetDisplayTitle();
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
index aad29f46fb..ca1bcc97fd 100644
--- a/osu.Game/Beatmaps/Formats/Decoder.cs
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Beatmaps.Formats
}
if (line == null)
- throw new IOException("Unknown file format (null)");
+ throw new IOException("Unknown file format (no content)");
var decoder = typedDecoders.Where(d => line.StartsWith(d.Key, StringComparison.InvariantCulture)).Select(d => d.Value).FirstOrDefault();
diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
index 2f11c18993..4f292a9a1f 100644
--- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps.Formats
{
public static void Register()
{
- AddDecoder("{", m => new JsonBeatmapDecoder());
+ AddDecoder("{", _ => new JsonBeatmapDecoder());
}
protected override void ParseStreamInto(LineBufferedReader stream, Beatmap output)
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 984e88d3a9..03c63ff4f2 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -373,11 +373,11 @@ namespace osu.Game.Beatmaps.Formats
switch (hitObject)
{
- case IHasPath _:
+ case IHasPath:
type |= LegacyHitObjectType.Slider;
break;
- case IHasDuration _:
+ case IHasDuration:
if (onlineRulesetID == 3)
type |= LegacyHitObjectType.Hold;
else
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index f2c5b494de..a5e6ac0a1c 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using osu.Framework.Extensions;
@@ -145,7 +143,7 @@ namespace osu.Game.Beatmaps.Formats
protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath();
- protected enum Section
+ public enum Section
{
General,
Editor,
@@ -162,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats
}
[Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")]
- public class LegacyDifficultyControlPoint : DifficultyControlPoint
+ public class LegacyDifficultyControlPoint : DifficultyControlPoint, IEquatable
{
///
/// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it.
@@ -188,9 +186,20 @@ namespace osu.Game.Beatmaps.Formats
BpmMultiplier = ((LegacyDifficultyControlPoint)other).BpmMultiplier;
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is LegacyDifficultyControlPoint otherLegacyDifficultyControlPoint
+ && Equals(otherLegacyDifficultyControlPoint);
+
+ public bool Equals(LegacyDifficultyControlPoint? other)
+ => base.Equals(other)
+ && BpmMultiplier == other.BpmMultiplier;
+
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), BpmMultiplier);
}
- internal class LegacySampleControlPoint : SampleControlPoint
+ internal class LegacySampleControlPoint : SampleControlPoint, IEquatable
{
public int CustomSampleBank;
@@ -204,7 +213,7 @@ namespace osu.Game.Beatmaps.Formats
return baseInfo;
}
- public override bool IsRedundant(ControlPoint existing)
+ public override bool IsRedundant(ControlPoint? existing)
=> base.IsRedundant(existing)
&& existing is LegacySampleControlPoint existingSample
&& CustomSampleBank == existingSample.CustomSampleBank;
@@ -215,6 +224,17 @@ namespace osu.Game.Beatmaps.Formats
CustomSampleBank = ((LegacySampleControlPoint)other).CustomSampleBank;
}
+
+ public override bool Equals(ControlPoint? other)
+ => other is LegacySampleControlPoint otherLegacySampleControlPoint
+ && Equals(otherLegacySampleControlPoint);
+
+ public bool Equals(LegacySampleControlPoint? other)
+ => base.Equals(other)
+ && CustomSampleBank == other.CustomSampleBank;
+
+ // ReSharper disable once NonReadonlyMemberInGetHashCode
+ public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), CustomSampleBank);
}
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 3021688aaf..b8f60f0bc6 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Formats
{
// note that this isn't completely correct
AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder(Parsing.ParseInt(m.Split('v').Last())));
- AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder());
+ AddDecoder(@"[Events]", _ => new LegacyStoryboardDecoder());
SetFallbackDecoder(() => new LegacyStoryboardDecoder());
}
diff --git a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
index 160b7cf0ae..e1634e7d24 100644
--- a/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
+++ b/osu.Game/Beatmaps/IBeatmapOnlineInfo.cs
@@ -13,25 +13,50 @@ namespace osu.Game.Beatmaps
///
int? MaxCombo { get; }
+ ///
+ /// The approach rate.
+ ///
+ float ApproachRate { get; }
+
+ ///
+ /// The circle size.
+ ///
+ float CircleSize { get; }
+
+ ///
+ /// The drain rate.
+ ///
+ float DrainRate { get; }
+
+ ///
+ /// The overall difficulty.
+ ///
+ float OverallDifficulty { get; }
+
///
/// The amount of circles in this beatmap.
///
- public int CircleCount { get; }
+ int CircleCount { get; }
///
/// The amount of sliders in this beatmap.
///
- public int SliderCount { get; }
+ int SliderCount { get; }
+
+ ///
+ /// The amount of spinners in tihs beatmap.
+ ///
+ int SpinnerCount { get; }
///
/// The amount of plays this beatmap has.
///
- public int PlayCount { get; }
+ int PlayCount { get; }
///
/// The amount of passes this beatmap has.
///
- public int PassCount { get; }
+ int PassCount { get; }
APIFailTimes? FailTimes { get; }
}
diff --git a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs
index 19432f0c58..5c3c72c9e4 100644
--- a/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs
+++ b/osu.Game/Beatmaps/Legacy/LegacyControlPointInfo.cs
@@ -56,12 +56,12 @@ namespace osu.Game.Beatmaps.Legacy
{
switch (newPoint)
{
- case SampleControlPoint _:
+ case SampleControlPoint:
// intentionally don't use SamplePointAt (we always need to consider the first sample point).
var existing = BinarySearch(SamplePoints, time);
return newPoint.IsRedundant(existing);
- case DifficultyControlPoint _:
+ case DifficultyControlPoint:
return newPoint.IsRedundant(DifficultyPointAt(time));
default:
diff --git a/osu.Game/Beatmaps/StarDifficulty.cs b/osu.Game/Beatmaps/StarDifficulty.cs
index 91bc3aacf6..e042f1c698 100644
--- a/osu.Game/Beatmaps/StarDifficulty.cs
+++ b/osu.Game/Beatmaps/StarDifficulty.cs
@@ -4,6 +4,7 @@
#nullable disable
using JetBrains.Annotations;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Beatmaps
@@ -50,6 +51,34 @@ namespace osu.Game.Beatmaps
Attributes = null;
}
- public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(Stars);
+ public DifficultyRating DifficultyRating => GetDifficultyRating(Stars);
+
+ ///
+ /// Retrieves the that describes a star rating.
+ ///
+ ///
+ /// For more information, see: https://osu.ppy.sh/help/wiki/Difficulties
+ ///
+ /// The star rating.
+ /// The that best describes .
+ public static DifficultyRating GetDifficultyRating(double starRating)
+ {
+ if (Precision.AlmostBigger(starRating, 6.5, 0.005))
+ return DifficultyRating.ExpertPlus;
+
+ if (Precision.AlmostBigger(starRating, 5.3, 0.005))
+ return DifficultyRating.Expert;
+
+ if (Precision.AlmostBigger(starRating, 4.0, 0.005))
+ return DifficultyRating.Insane;
+
+ if (Precision.AlmostBigger(starRating, 2.7, 0.005))
+ return DifficultyRating.Hard;
+
+ if (Precision.AlmostBigger(starRating, 2.0, 0.005))
+ return DifficultyRating.Normal;
+
+ return DifficultyRating.Easy;
+ }
}
}
diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs
index 23b2ef60dd..742d757bec 100644
--- a/osu.Game/Collections/BeatmapCollection.cs
+++ b/osu.Game/Collections/BeatmapCollection.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Collections
public BeatmapCollection()
{
- BeatmapHashes.CollectionChanged += (_, __) => onChange();
+ BeatmapHashes.CollectionChanged += (_, _) => onChange();
Name.ValueChanged += _ => onChange();
}
diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs
index 46d90b930c..d099eb6e1b 100644
--- a/osu.Game/Collections/CollectionFilterDropdown.cs
+++ b/osu.Game/Collections/CollectionFilterDropdown.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Collections
// An extra bindable is enough to subvert this behaviour.
base.Current = Current;
- collections.BindCollectionChanged((_, __) => collectionsChanged(), true);
+ collections.BindCollectionChanged((_, _) => collectionsChanged(), true);
Current.BindValueChanged(filterChanged, true);
}
@@ -233,7 +233,7 @@ namespace osu.Game.Collections
if (collectionBeatmaps != null)
{
- collectionBeatmaps.CollectionChanged += (_, __) => collectionChanged();
+ collectionBeatmaps.CollectionChanged += (_, _) => collectionChanged();
beatmap.BindValueChanged(_ => collectionChanged(), true);
}
diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs
index c6e52a1b75..796b3c426c 100644
--- a/osu.Game/Collections/CollectionManager.cs
+++ b/osu.Game/Collections/CollectionManager.cs
@@ -261,7 +261,7 @@ namespace osu.Game.Collections
private void backgroundSave()
{
int current = Interlocked.Increment(ref lastSave);
- Task.Delay(100).ContinueWith(task =>
+ Task.Delay(100).ContinueWith(_ =>
{
if (current != lastSave)
return;
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index a0e1d9ddc4..713166a9a0 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -20,6 +20,7 @@ using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays;
+using osu.Game.Overlays.Mods.Input;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Filter;
@@ -47,6 +48,7 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title);
SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
+ SetDefault(OsuSetting.ModSelectHotkeyStyle, ModSelectHotkeyStyle.Sequential);
SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f);
@@ -324,6 +326,7 @@ namespace osu.Game.Configuration
SongSelectGroupingMode,
SongSelectSortingMode,
RandomSelectAlgorithm,
+ ModSelectHotkeyStyle,
ShowFpsDisplay,
ChatDisplayHeight,
BeatmapListingCardSize,
diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs
index 8af649fbfa..af91fb4971 100644
--- a/osu.Game/Database/DatabaseContextFactory.cs
+++ b/osu.Game/Database/DatabaseContextFactory.cs
@@ -209,6 +209,10 @@ namespace osu.Game.Database
public void SetMigrationCompletion() => migrationComplete.Set();
- public void WaitForMigrationCompletion() => migrationComplete.Wait();
+ public void WaitForMigrationCompletion()
+ {
+ if (!migrationComplete.Wait(300000))
+ throw new TimeoutException("Migration took too long (likely stuck).");
+ }
}
}
diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs
index bf9f2f9c75..ebb8be39ef 100644
--- a/osu.Game/Database/IModelImporter.cs
+++ b/osu.Game/Database/IModelImporter.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using osu.Game.Overlays.Notifications;
@@ -11,7 +12,7 @@ namespace osu.Game.Database
/// A class which handles importing of associated models to the game store.
///
/// The model type.
- public interface IModelImporter : IPostNotifications, IPostImports, ICanAcceptFiles
+ public interface IModelImporter : IPostNotifications, ICanAcceptFiles
where TModel : class, IHasGuidPrimaryKey
{
///
@@ -25,6 +26,11 @@ namespace osu.Game.Database
///
/// A user displayable name for the model type associated with this manager.
///
- string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
+ string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}";
+
+ ///
+ /// Fired when the user requests to view the resulting import.
+ ///
+ public Action>>? PresentImport { set; }
}
}
diff --git a/osu.Game/Database/IPostImports.cs b/osu.Game/Database/IPostImports.cs
deleted file mode 100644
index 83a211f6e6..0000000000
--- a/osu.Game/Database/IPostImports.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-
-namespace osu.Game.Database
-{
- public interface IPostImports
- where TModel : class, IHasGuidPrimaryKey
- {
- ///
- /// Fired when the user requests to view the resulting import.
- ///
- public Action>>? PostImport { set; }
- }
-}
diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs
index 0643300fc5..e7f599d85f 100644
--- a/osu.Game/Database/ImportTask.cs
+++ b/osu.Game/Database/ImportTask.cs
@@ -33,7 +33,7 @@ namespace osu.Game.Database
}
///
- /// Construct a new import task from a stream.
+ /// Construct a new import task from a stream. The provided stream will be disposed after reading.
///
public ImportTask(Stream stream, string filename)
{
@@ -62,6 +62,7 @@ namespace osu.Game.Database
{
// 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).
memoryStream = new MemoryStream(stream.ReadAllBytesToArray());
+ stream.Dispose();
}
if (ZipUtils.IsZipArchive(memoryStream))
diff --git a/osu.Game/Database/ModelManager.cs b/osu.Game/Database/ModelManager.cs
index e0c3dccf57..4224c92f2c 100644
--- a/osu.Game/Database/ModelManager.cs
+++ b/osu.Game/Database/ModelManager.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using osu.Framework.Platform;
@@ -46,6 +47,7 @@ namespace osu.Game.Database
Realm.Realm.Write(realm =>
{
var managed = realm.Find(item.ID);
+ Debug.Assert(managed != null);
operation(managed);
item.Files.Clear();
@@ -201,6 +203,6 @@ namespace osu.Game.Database
public Action? PostNotification { get; set; }
- public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
+ public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}";
}
}
diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs
index 865a8b5021..cb34a92702 100644
--- a/osu.Game/Database/RealmAccess.cs
+++ b/osu.Game/Database/RealmAccess.cs
@@ -102,6 +102,12 @@ namespace osu.Game.Database
private Realm? updateRealm;
+ ///
+ /// Tracks whether a realm was ever fetched from this instance.
+ /// After a fetch occurs, blocking operations will be guaranteed to restore any subscriptions.
+ ///
+ private bool hasInitialisedOnce;
+
private bool isSendingNotificationResetEvents;
public Realm Realm => ensureUpdateRealm();
@@ -121,11 +127,12 @@ namespace osu.Game.Database
if (updateRealm == null)
{
updateRealm = getRealmInstance();
+ hasInitialisedOnce = true;
Logger.Log(@$"Opened realm ""{updateRealm.Config.DatabasePath}"" at version {updateRealm.Config.SchemaVersion}");
// Resubscribe any subscriptions
- foreach (var action in customSubscriptionsResetMap.Keys)
+ foreach (var action in customSubscriptionsResetMap.Keys.ToArray())
registerSubscription(action);
}
@@ -385,11 +392,22 @@ namespace osu.Game.Database
/// Write changes to realm asynchronously, guaranteeing order of execution.
///
/// The work to run.
- public async Task WriteAsync(Action action)
+ public Task WriteAsync(Action action)
{
- total_writes_async.Value++;
- using (var realm = getRealmInstance())
- await realm.WriteAsync(() => action(realm));
+ // Regardless of calling Realm.GetInstance or Realm.GetInstanceAsync, there is a blocking overhead on retrieval.
+ // Adding a forced Task.Run resolves this.
+
+ return Task.Run(async () =>
+ {
+ total_writes_async.Value++;
+
+ // Not attempting to use Realm.GetInstanceAsync as there's seemingly no benefit to us (for now) and it adds complexity due to locking
+ // concerns in getRealmInstance(). On a quick check, it looks to be more suited to cases where realm is connecting to an online sync
+ // server, which we don't use. May want to report upstream or revisit in the future.
+ using (var realm = getRealmInstance())
+ // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]).
+ await realm.WriteAsync(() => action(realm));
+ });
}
///
@@ -443,7 +461,7 @@ namespace osu.Game.Database
public IDisposable SubscribeToPropertyChanged(Func modelAccessor, Expression> propertyLookup, Action onChanged)
where TModel : RealmObjectBase
{
- return RegisterCustomSubscription(r =>
+ return RegisterCustomSubscription(_ =>
{
string propertyName = getMemberName(propertyLookup);
@@ -795,13 +813,7 @@ namespace osu.Game.Database
lock (realmLock)
{
- if (updateRealm == null)
- {
- // null realm means the update thread has not yet retrieved its instance.
- // we don't need to worry about reviving the update instance in this case, so don't bother with the SynchronizationContext.
- Debug.Assert(!ThreadSafety.IsUpdateThread);
- }
- else
+ if (hasInitialisedOnce)
{
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException(@$"{nameof(BlockAllOperations)} must be called from the update thread.");
@@ -811,17 +823,17 @@ namespace osu.Game.Database
// Before disposing the update context, clean up all subscriptions.
// Note that in the case of realm notification subscriptions, this is not really required (they will be cleaned up by disposal).
// In the case of custom subscriptions, we want them to fire before the update realm is disposed in case they do any follow-up work.
- foreach (var action in customSubscriptionsResetMap)
+ foreach (var action in customSubscriptionsResetMap.ToArray())
{
action.Value?.Dispose();
customSubscriptionsResetMap[action.Key] = null;
}
+
+ updateRealm?.Dispose();
+ updateRealm = null;
}
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
-
- updateRealm?.Dispose();
- updateRealm = null;
}
const int sleep_length = 200;
diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs
index c5e04a8a0f..76f6db1384 100644
--- a/osu.Game/Database/RealmArchiveModelImporter.cs
+++ b/osu.Game/Database/RealmArchiveModelImporter.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Database
///
private static readonly ThreadedTaskScheduler import_scheduler_batch = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(RealmArchiveModelImporter));
- public virtual IEnumerable HandledExtensions => new[] { @".zip" };
+ public abstract IEnumerable HandledExtensions { get; }
protected readonly RealmFileStore Files;
@@ -65,7 +65,7 @@ namespace osu.Game.Database
///
/// Fired when the user requests to view the resulting import.
///
- public Action>>? PostImport { get; set; }
+ public Action>>? PresentImport { get; set; }
///
/// Set an endpoint for notifications to be posted to.
@@ -158,12 +158,12 @@ namespace osu.Game.Database
? $"Imported {imported.First().GetDisplayString()}!"
: $"Imported {imported.Count} {HumanisedModelName}s!";
- if (imported.Count > 0 && PostImport != null)
+ if (imported.Count > 0 && PresentImport != null)
{
notification.CompletionText += " Click to view.";
notification.CompletionClickAction = () =>
{
- PostImport?.Invoke(imported);
+ PresentImport?.Invoke(imported);
return true;
};
}
@@ -188,7 +188,7 @@ namespace osu.Game.Database
Live? import;
using (ArchiveReader reader = task.GetReader())
- import = await Import(reader, batchImport, cancellationToken).ConfigureAwait(false);
+ import = await importFromArchive(reader, batchImport, cancellationToken).ConfigureAwait(false);
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with items from default storage.
@@ -208,12 +208,15 @@ namespace osu.Game.Database
}
///
- /// Silently import an item from an .
+ /// Create and import a model based off the provided .
///
+ ///
+ /// This method also handled queueing the import task on a relevant import thread pool.
+ ///
/// The archive to be imported.
/// Whether this import is part of a larger batch.
/// An optional cancellation token.
- public async Task?> Import(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default)
+ private async Task?> importFromArchive(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -236,7 +239,7 @@ namespace osu.Game.Database
return null;
}
- var scheduledImport = Task.Factory.StartNew(() => Import(model, archive, batchImport, cancellationToken),
+ var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken),
cancellationToken,
TaskCreationOptions.HideScheduler,
batchImport ? import_scheduler_batch : import_scheduler);
@@ -251,7 +254,7 @@ namespace osu.Game.Database
/// An optional archive to use for model population.
/// If true, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model.
/// An optional cancellation token.
- public virtual Live? Import(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm =>
+ public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm =>
{
cancellationToken.ThrowIfCancellationRequested();
@@ -293,7 +296,8 @@ namespace osu.Game.Database
try
{
- LogForModel(item, @"Beginning import...");
+ // Log output here will be missing a valid hash in non-batch imports.
+ LogForModel(item, $@"Beginning import from {archive?.Name ?? "unknown"}...");
// TODO: do we want to make the transaction this local? not 100% sure, will need further investigation.
using (var transaction = realm.BeginWrite())
@@ -335,6 +339,8 @@ namespace osu.Game.Database
transaction.Commit();
}
+ PostImport(item, realm);
+
LogForModel(item, @"Import successfully completed!");
}
catch (Exception e)
@@ -470,6 +476,15 @@ namespace osu.Game.Database
{
}
+ ///
+ /// Perform any final actions after the import has been committed to the database.
+ ///
+ /// The model prepared for import.
+ /// The current realm context.
+ protected virtual void PostImport(TModel model, Realm realm)
+ {
+ }
+
///
/// Check whether an existing model already exists for a new import item.
///
@@ -535,6 +550,6 @@ namespace osu.Game.Database
yield return f.Filename;
}
- public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
+ public virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLowerInvariant()}";
}
}
diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs
index 25416c7fa2..a771aa04df 100644
--- a/osu.Game/Database/RealmObjectExtensions.cs
+++ b/osu.Game/Database/RealmObjectExtensions.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Database
{
private static readonly IMapper write_mapper = new MapperConfiguration(c =>
{
- c.ShouldMapField = fi => false;
+ c.ShouldMapField = _ => false;
c.ShouldMapProperty = pi => pi.SetMethod?.IsPublic == true;
c.CreateMap()
@@ -70,7 +70,7 @@ namespace osu.Game.Database
}
});
- c.Internal().ForAllMaps((typeMap, expression) =>
+ c.Internal().ForAllMaps((_, expression) =>
{
expression.ForAllMembers(m =>
{
@@ -87,7 +87,7 @@ namespace osu.Game.Database
c.CreateMap()
.ConstructUsing(_ => new BeatmapSetInfo(null))
.MaxDepth(2)
- .AfterMap((s, d) =>
+ .AfterMap((_, d) =>
{
foreach (var beatmap in d.Beatmaps)
beatmap.BeatmapSet = d;
@@ -97,7 +97,7 @@ namespace osu.Game.Database
// Only hasn't been done yet as we detach at the point of BeatmapInfo less often.
c.CreateMap()
.MaxDepth(2)
- .AfterMap((s, d) =>
+ .AfterMap((_, d) =>
{
for (int i = 0; i < d.BeatmapSet?.Beatmaps.Count; i++)
{
@@ -121,7 +121,7 @@ namespace osu.Game.Database
.ConstructUsing(_ => new BeatmapSetInfo(null))
.MaxDepth(2)
.ForMember(b => b.Files, cc => cc.Ignore())
- .AfterMap((s, d) =>
+ .AfterMap((_, d) =>
{
foreach (var beatmap in d.Beatmaps)
beatmap.BeatmapSet = d;
@@ -135,14 +135,14 @@ namespace osu.Game.Database
private static void applyCommonConfiguration(IMapperConfigurationExpression c)
{
- c.ShouldMapField = fi => false;
+ c.ShouldMapField = _ => false;
// This is specifically to avoid mapping explicit interface implementations.
// If we want to limit this further, we can avoid mapping properties with no setter that are not IList<>.
// Takes a bit of effort to determine whether this is the case though, see https://stackoverflow.com/questions/951536/how-do-i-tell-whether-a-type-implements-ilist
c.ShouldMapProperty = pi => pi.GetMethod?.IsPublic == true;
- c.Internal().ForAllMaps((typeMap, expression) =>
+ c.Internal().ForAllMaps((_, expression) =>
{
expression.ForAllMembers(m =>
{
diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs
index 5165ae59b5..b4a0c02e35 100644
--- a/osu.Game/Extensions/TaskExtensions.cs
+++ b/osu.Game/Extensions/TaskExtensions.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Extensions
{
var tcs = new TaskCompletionSource();
- task.ContinueWith(t =>
+ task.ContinueWith(_ =>
{
// the previous task has finished execution or been cancelled, so we can run the provided continuation.
diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
index 4146bbcc5e..45a935d165 100644
--- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
+++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs
@@ -127,7 +127,7 @@ namespace osu.Game.Graphics.Containers
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
- if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
+ if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat)
return;
// as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat.
diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
index a67e5ae66f..bf96695fd3 100644
--- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs
+++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Online;
using osu.Game.Users;
namespace osu.Game.Graphics.Containers
@@ -25,7 +26,7 @@ namespace osu.Game.Graphics.Containers
}
[Resolved(CanBeNull = true)]
- private OsuGame game { get; set; }
+ private ILinkHandler linkHandler { get; set; }
[Resolved]
private GameHost host { get; set; }
@@ -81,8 +82,8 @@ namespace osu.Game.Graphics.Containers
{
if (action != null)
action();
- else if (game != null)
- game.HandleLink(link);
+ else if (linkHandler != null)
+ linkHandler.HandleLink(link);
// fallback to handle cases where OsuGame is not available, ie. tournament client.
else if (link.Action == LinkAction.External)
host.OpenUrlExternally(link.Argument.ToString());
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
index aa156efee9..a2370a76cb 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers.Markdown
{
switch (markdownObject)
{
- case YamlFrontMatterBlock _:
+ case YamlFrontMatterBlock:
// Don't parse YAML Frontmatter
break;
diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
index dca9e3de6e..3a16eb0a0f 100644
--- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
+++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownLinkText.cs
@@ -8,6 +8,7 @@ using Markdig.Syntax.Inlines;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers.Markdown;
+using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
@@ -16,7 +17,7 @@ namespace osu.Game.Graphics.Containers.Markdown
public class OsuMarkdownLinkText : MarkdownLinkText
{
[Resolved(canBeNull: true)]
- private OsuGame game { get; set; }
+ private ILinkHandler linkHandler { get; set; }
private readonly string text;
private readonly string title;
@@ -51,7 +52,7 @@ namespace osu.Game.Graphics.Containers.Markdown
};
}
- protected override void OnLinkPressed() => game?.HandleLink(Url);
+ protected override void OnLinkPressed() => linkHandler?.HandleLink(Url);
private class OsuMarkdownLinkCompiler : DrawableLinkCompiler
{
diff --git a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs
index 84d0d1eb32..b604ae73eb 100644
--- a/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuRearrangeableListContainer.cs
@@ -3,9 +3,14 @@
#nullable disable
+using System.Collections.Specialized;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
namespace osu.Game.Graphics.Containers
{
@@ -18,11 +23,49 @@ namespace osu.Game.Graphics.Containers
protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer();
+ private Sample sampleSwap;
+ private double sampleLastPlaybackTime;
+
protected sealed override RearrangeableListItem CreateDrawable(TModel item) => CreateOsuDrawable(item).With(d =>
{
d.DragActive.BindTo(DragActive);
});
protected abstract OsuRearrangeableListItem CreateOsuDrawable(TModel item);
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Items.CollectionChanged += (_, args) =>
+ {
+ if (args.Action == NotifyCollectionChangedAction.Move)
+ playSwapSample();
+ };
+ }
+
+ private void playSwapSample()
+ {
+ if (!DragActive.Value)
+ return;
+
+ if (Time.Current - sampleLastPlaybackTime <= 35)
+ return;
+
+ var channel = sampleSwap?.GetChannel();
+ if (channel == null)
+ return;
+
+ channel.Frequency.Value = 0.96 + RNG.NextDouble(0.08);
+ channel.Play();
+ sampleLastPlaybackTime = Time.Current;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ sampleSwap = audio.Samples.Get(@"UI/item-swap");
+ sampleLastPlaybackTime = Time.Current;
+ }
}
}
diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs
index 5c8579a144..17c51129a7 100644
--- a/osu.Game/Graphics/Containers/ScalingContainer.cs
+++ b/osu.Game/Graphics/Containers/ScalingContainer.cs
@@ -260,7 +260,7 @@ namespace osu.Game.Graphics.Containers
if (host.Window == null) return;
bool coversWholeScreen = Size == Vector2.One && safeArea.SafeAreaPadding.Value.Total == Vector2.Zero;
- host.Window.CursorConfineRect = coversWholeScreen ? (RectangleF?)null : ToScreenSpace(DrawRectangle).AABBFloat;
+ host.Window.CursorConfineRect = coversWholeScreen ? null : ToScreenSpace(DrawRectangle).AABBFloat;
}
}
}
diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs
index 4544633318..10ed76ebdd 100644
--- a/osu.Game/Graphics/Cursor/MenuCursor.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursor.cs
@@ -51,12 +51,16 @@ namespace osu.Game.Graphics.Cursor
{
if (dragRotationState != DragRotationState.NotDragging)
{
+ // make the rotation centre point floating.
+ if (Vector2.Distance(positionMouseDown, e.MousePosition) > 60)
+ positionMouseDown = Interpolation.ValueAt(0.005f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime);
+
var position = e.MousePosition;
float distance = Vector2Extensions.Distance(position, positionMouseDown);
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
- if (dragRotationState == DragRotationState.DragStarted && distance > 30)
+ if (dragRotationState == DragRotationState.DragStarted && distance > 80)
dragRotationState = DragRotationState.Rotating;
// don't rotate when distance is zero to avoid NaN
@@ -71,7 +75,7 @@ namespace osu.Game.Graphics.Cursor
if (diff > 180) diff -= 360;
degrees = activeCursor.Rotation + diff;
- activeCursor.RotateTo(degrees, 600, Easing.OutQuint);
+ activeCursor.RotateTo(degrees, 120, Easing.OutQuint);
}
}
@@ -111,7 +115,7 @@ namespace osu.Game.Graphics.Cursor
if (dragRotationState != DragRotationState.NotDragging)
{
- activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf);
+ activeCursor.RotateTo(0, 400 * (0.5f + Math.Abs(activeCursor.Rotation / 960)), Easing.OutElasticQuarter);
dragRotationState = DragRotationState.NotDragging;
}
diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs
index 7147f89dd0..2cc9e63c87 100644
--- a/osu.Game/Graphics/ScreenshotManager.cs
+++ b/osu.Game/Graphics/ScreenshotManager.cs
@@ -103,7 +103,9 @@ namespace osu.Game.Graphics
framesWaitedEvent.Set();
}, 10, true);
- framesWaitedEvent.Wait();
+ if (!framesWaitedEvent.Wait(1000))
+ throw new TimeoutException("Screenshot data did not arrive in a timely fashion");
+
waitDelegate.Cancel();
}
}
diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs
index 00ed6c1c92..f55875ac58 100644
--- a/osu.Game/Graphics/UserInterface/BarGraph.cs
+++ b/osu.Game/Graphics/UserInterface/BarGraph.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Graphics.UserInterface
}
//I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards
- RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList());
+ RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList());
}
}
}
diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
index 407823fc2a..b3655eaab4 100644
--- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
@@ -55,12 +55,12 @@ namespace osu.Game.Graphics.UserInterface
switch (e)
{
// blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer.
- case ScrollEvent _:
+ case ScrollEvent:
return false;
// blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these.
// note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future).
- case TouchEvent _:
+ case TouchEvent:
return false;
}
diff --git a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
index 50682ddfe0..0bbcb2b976 100644
--- a/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
+++ b/osu.Game/Graphics/UserInterface/ShearedToggleButton.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -47,7 +46,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void LoadComplete()
{
- Active.BindDisabledChanged(disabled => Action = disabled ? (Action?)null : Active.Toggle, true);
+ Active.BindDisabledChanged(disabled => Action = disabled ? null : Active.Toggle, true);
Active.BindValueChanged(_ =>
{
updateActiveState();
diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs
index a8051a94d9..d9274bf2cb 100644
--- a/osu.Game/Graphics/UserInterface/StarCounter.cs
+++ b/osu.Game/Graphics/UserInterface/StarCounter.cs
@@ -68,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(star_spacing),
- ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => CreateStar())
+ ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(_ => CreateStar())
}
};
}
diff --git a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs
index 9d1650c970..6787e17113 100644
--- a/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs
+++ b/osu.Game/Graphics/UserInterface/ToggleMenuItem.cs
@@ -34,6 +34,6 @@ namespace osu.Game.Graphics.UserInterface
{
}
- public override IconUsage? GetIconForState(bool state) => state ? (IconUsage?)FontAwesome.Solid.Check : null;
+ public override IconUsage? GetIconForState(bool state) => state ? FontAwesome.Solid.Check : null;
}
}
diff --git a/osu.Game/IPC/IPCTimeoutException.cs b/osu.Game/IPC/IPCTimeoutException.cs
new file mode 100644
index 0000000000..d820184468
--- /dev/null
+++ b/osu.Game/IPC/IPCTimeoutException.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.IPC
+{
+ public class IPCTimeoutException : TimeoutException
+ {
+ public IPCTimeoutException(Type channelType)
+ : base($@"IPC took too long to send message via channel {channelType}")
+ {
+ }
+ }
+}
diff --git a/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs b/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs
new file mode 100644
index 0000000000..33318e329c
--- /dev/null
+++ b/osu.Game/IPC/OsuSchemeLinkIPCChannel.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using System.Threading.Tasks;
+using osu.Framework.Platform;
+using osu.Game.Online;
+
+namespace osu.Game.IPC
+{
+ public class OsuSchemeLinkIPCChannel : IpcChannel
+ {
+ private readonly ILinkHandler? linkHandler;
+
+ public OsuSchemeLinkIPCChannel(IIpcHost host, ILinkHandler? linkHandler = null)
+ : base(host)
+ {
+ this.linkHandler = linkHandler;
+
+ MessageReceived += msg =>
+ {
+ Debug.Assert(linkHandler != null);
+ linkHandler.HandleLink(msg.Link);
+ return null;
+ };
+ }
+
+ public async Task HandleLinkAsync(string url)
+ {
+ if (linkHandler == null)
+ {
+ await SendMessageAsync(new OsuSchemeLinkMessage(url)).ConfigureAwait(false);
+ return;
+ }
+
+ linkHandler.HandleLink(url);
+ }
+ }
+
+ public class OsuSchemeLinkMessage
+ {
+ public string Link { get; }
+
+ public OsuSchemeLinkMessage(string link)
+ {
+ Link = link;
+ }
+ }
+}
diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
index 974abc4036..14a041b459 100644
--- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
+++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Input.Bindings
protected override void LoadComplete()
{
- realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, changes, error) =>
+ realmSubscription = realm.RegisterForNotifications(queryRealmKeyBindings, (sender, _, _) =>
{
// The first fire of this is a bit redundant as this is being called in base.LoadComplete,
// but this is safest in case the subscription is restored after a context recycle.
diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs
index 279cfb5a4e..45036b3e41 100644
--- a/osu.Game/Input/IdleTracker.cs
+++ b/osu.Game/Input/IdleTracker.cs
@@ -80,11 +80,11 @@ namespace osu.Game.Input
switch (e)
{
- case KeyDownEvent _:
- case KeyUpEvent _:
- case MouseDownEvent _:
- case MouseUpEvent _:
- case MouseMoveEvent _:
+ case KeyDownEvent:
+ case KeyUpEvent:
+ case MouseDownEvent:
+ case MouseUpEvent:
+ case MouseMoveEvent:
return updateLastInteractionTime();
default:
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
index 0dc95da2f4..0f0f560df9 100644
--- a/osu.Game/Localisation/AudioSettingsStrings.cs
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
index 417cf335e0..632a1ad0ea 100644
--- a/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
+++ b/osu.Game/Localisation/BeatmapOffsetControlStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/BindingSettingsStrings.cs b/osu.Game/Localisation/BindingSettingsStrings.cs
index 39b5ac0d21..ad4a650a1f 100644
--- a/osu.Game/Localisation/BindingSettingsStrings.cs
+++ b/osu.Game/Localisation/BindingSettingsStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/ButtonSystemStrings.cs b/osu.Game/Localisation/ButtonSystemStrings.cs
index c71a99711b..ba4abf63a6 100644
--- a/osu.Game/Localisation/ButtonSystemStrings.cs
+++ b/osu.Game/Localisation/ButtonSystemStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/ChatStrings.cs b/osu.Game/Localisation/ChatStrings.cs
index b07ed18f7b..7bd284a94e 100644
--- a/osu.Game/Localisation/ChatStrings.cs
+++ b/osu.Game/Localisation/ChatStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
index 39dc7cf518..1ee562e122 100644
--- a/osu.Game/Localisation/CommonStrings.cs
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/DebugLocalisationStore.cs b/osu.Game/Localisation/DebugLocalisationStore.cs
index 83ae4581a8..2b114b1bd8 100644
--- a/osu.Game/Localisation/DebugLocalisationStore.cs
+++ b/osu.Game/Localisation/DebugLocalisationStore.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using System;
using System.Collections.Generic;
using System.Globalization;
diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs
index e6de4ddee9..74b2c8d892 100644
--- a/osu.Game/Localisation/DebugSettingsStrings.cs
+++ b/osu.Game/Localisation/DebugSettingsStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
index 4bcbdcff7b..952ca22678 100644
--- a/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
+++ b/osu.Game/Localisation/DifficultyMultiplierDisplayStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs
index 38f5860cc5..deac7d8628 100644
--- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs
+++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs
index 331a8c6764..3a7fe4bb12 100644
--- a/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs
+++ b/osu.Game/Localisation/FirstRunSetupBeatmapScreenStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-#nullable disable
-
using osu.Framework.Localisation;
namespace osu.Game.Localisation
diff --git a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs
index 0c73d7a85b..91b427e2ca 100644
--- a/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs
+++ b/osu.Game/Localisation/FirstRunSetupOverlayStrings.cs
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd