diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 39fa0f9f6c..8be6479043 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1,4 +1,2 @@
# Normalize all the line endings
32a74f95a5c80a0ed18e693f13a47522099df5c3
-# Enabled NRT globally
-f8830c6850128456266c82de83273204f8b74ac0
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.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 59bc2dabb2..4e5f8d37b1 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -154,7 +154,7 @@ namespace osu.Desktop.Updater
Activated = () =>
{
updateManager.PrepareUpdateAsync()
- .ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit()));
+ .ContinueWith(_ => updateManager.Schedule(() => game?.AttemptExit()));
return true;
};
}
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 ffef8a50ad..d259c2af8e 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
@@ -30,9 +29,9 @@ namespace osu.Game.Rulesets.Mania.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.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/Evaluators/SpeedEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
index 4b1e12d5c6..5187717639 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/SpeedEvaluator.cs
@@ -4,7 +4,6 @@
#nullable disable
using System;
-using osu.Framework.Utils;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
@@ -33,14 +32,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
// derive strainTime for calculation
var osuCurrObj = (OsuDifficultyHitObject)current;
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
+ var osuNextObj = (OsuDifficultyHitObject)current.Next(0);
double strainTime = osuCurrObj.StrainTime;
double greatWindowFull = greatWindow * 2;
- double speedWindowRatio = strainTime / greatWindowFull;
+ double doubletapness = 1;
- // Aim to nerf cheesy rhythms (Very fast consecutive doubles with large deltatimes between)
- if (osuPrevObj != null && strainTime < greatWindowFull && osuPrevObj.StrainTime > strainTime)
- strainTime = Interpolation.Lerp(osuPrevObj.StrainTime, strainTime, speedWindowRatio);
+ // Nerf doubletappable doubles.
+ if (osuNextObj != null)
+ {
+ double currDeltaTime = Math.Max(1, osuCurrObj.DeltaTime);
+ double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
+ double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
+ double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
+ double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / greatWindowFull), 2);
+ doubletapness = Math.Pow(speedRatio, 1 - windowRatio);
+ }
// Cap deltatime to the OD 300 hitwindow.
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
@@ -55,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
- return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
+ return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) * doubletapness / strainTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 19217015c1..4b06502e32 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;
@@ -96,9 +95,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor);
}
- 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];
@@ -108,6 +107,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
StarRating = values[ATTRIB_ID_DIFFICULTY];
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
+
+ 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/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
index 695bb2f8d0..fffa886dd0 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
@@ -7,6 +7,7 @@ using System;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
+using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
@@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
protected override double StrainValueAt(DifficultyHitObject current)
{
- currentStrain *= strainDecay(current.DeltaTime);
+ currentStrain *= strainDecay(((OsuDifficultyHitObject)current).StrainTime);
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, greatWindow) * skillMultiplier;
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current, greatWindow);
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 92f07af7b1..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,15 +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(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");
- 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 78ab7947a7..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,9 +188,40 @@ 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("Click gameplay scene button", () => skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick());
+ 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"));
+ InputManager.Click(MouseButton.Left);
+ });
AddUntilStep("wait for player", () =>
{
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.Tests/Components/TestSceneDrawableTournamentTeam.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs
index 9ade9965c5..4ea7e8008a 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentTeam.cs
@@ -5,7 +5,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tests.Visual;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
@@ -26,13 +25,13 @@ namespace osu.Game.Tournament.Tests.Components
FullName = { Value = "Australia" },
Players =
{
- new APIUser { Username = "ASecretBox" },
- new APIUser { Username = "Dereban" },
- new APIUser { Username = "mReKk" },
- new APIUser { Username = "uyghti" },
- new APIUser { Username = "Parkes" },
- new APIUser { Username = "Shiroha" },
- new APIUser { Username = "Jordan The Bear" },
+ new TournamentUser { Username = "ASecretBox" },
+ new TournamentUser { Username = "Dereban" },
+ new TournamentUser { Username = "mReKk" },
+ new TournamentUser { Username = "uyghti" },
+ new TournamentUser { Username = "Parkes" },
+ new TournamentUser { Username = "Shiroha" },
+ new TournamentUser { Username = "Jordan The Bear" },
}
};
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs
index f90269ae60..992baa48a3 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tournament.Tests.Components
beatmap.Length = 123456;
beatmap.BPM = 133;
- songBar.Beatmap = beatmap;
+ songBar.Beatmap = new TournamentBeatmap(beatmap);
});
AddStep("set mods to HR", () => songBar.Mods = LegacyMods.HardRock);
AddStep("set mods to DT", () => songBar.Mods = LegacyMods.DoubleTime);
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
index 1030aae903..adfe048ce4 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
@@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tests.Visual;
using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Tests.Components
{
@@ -32,7 +33,7 @@ namespace osu.Game.Tournament.Tests.Components
private void success(APIBeatmap beatmap)
{
- Add(new TournamentBeatmapPanel(beatmap)
+ Add(new TournamentBeatmapPanel(new TournamentBeatmap(beatmap))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
index 4a4def10b5..eb8b0dfbe6 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs
@@ -27,16 +27,16 @@ namespace osu.Game.Tournament.Tests.Components
Colour = "f2ca34"
};
- private readonly APIUser redUser = new APIUser
+ private readonly TournamentUser redUser = new TournamentUser
{
Username = "BanchoBot",
- Id = 3,
+ OnlineID = 3,
};
- private readonly APIUser blueUser = new APIUser
+ private readonly TournamentUser blueUser = new TournamentUser
{
Username = "Zallius",
- Id = 4,
+ OnlineID = 4,
};
[Cached]
@@ -59,11 +59,11 @@ namespace osu.Game.Tournament.Tests.Components
{
Team1 =
{
- Value = new TournamentTeam { Players = new BindableList { redUser } }
+ Value = new TournamentTeam { Players = new BindableList { redUser } }
},
Team2 =
{
- Value = new TournamentTeam { Players = new BindableList { blueUser } }
+ Value = new TournamentTeam { Players = new BindableList { blueUser } }
}
};
@@ -82,19 +82,19 @@ namespace osu.Game.Tournament.Tests.Components
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId())
{
- Sender = redUser,
+ Sender = redUser.ToAPIUser(),
Content = "I am team red."
}));
AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId())
{
- Sender = redUser,
+ Sender = redUser.ToAPIUser(),
Content = "I plan to win!"
}));
AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(nextMessageId())
{
- Sender = blueUser,
+ Sender = blueUser.ToAPIUser(),
Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand."
}));
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
index fdd5578228..263617ddf7 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
@@ -11,6 +11,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Tournament.Components;
+using osu.Game.Tournament.Models;
using osuTK;
namespace osu.Game.Tournament.Tests.Components
@@ -53,7 +54,7 @@ namespace osu.Game.Tournament.Tests.Components
foreach (var mod in mods)
{
- fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym)
+ fillFlow.Add(new TournamentBeatmapPanel(new TournamentBeatmap(beatmap), mod.Acronym)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs
index d631d028bb..b440034f34 100644
--- a/osu.Game.Tournament.Tests/TournamentTestScene.cs
+++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs
@@ -9,14 +9,12 @@ using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Framework.Utils;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Tests.Visual;
using osu.Game.Tournament.IO;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
-using osu.Game.Users;
-using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tournament.Tests
{
@@ -123,11 +121,11 @@ namespace osu.Game.Tournament.Tests
},
Players =
{
- new APIUser { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 12 } },
- new APIUser { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 16 } },
- new APIUser { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 20 } },
- new APIUser { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 24 } },
- new APIUser { Username = "Hello", Statistics = new UserStatistics { GlobalRank = 30 } },
+ new TournamentUser { Username = "Hello", Rank = 12 },
+ new TournamentUser { Username = "Hello", Rank = 16 },
+ new TournamentUser { Username = "Hello", Rank = 20 },
+ new TournamentUser { Username = "Hello", Rank = 24 },
+ new TournamentUser { Username = "Hello", Rank = 30 },
}
}
},
@@ -140,11 +138,11 @@ namespace osu.Game.Tournament.Tests
FullName = { Value = "United States" },
Players =
{
- new APIUser { Username = "Hello" },
- new APIUser { Username = "Hello" },
- new APIUser { Username = "Hello" },
- new APIUser { Username = "Hello" },
- new APIUser { Username = "Hello" },
+ new TournamentUser { Username = "Hello" },
+ new TournamentUser { Username = "Hello" },
+ new TournamentUser { Username = "Hello" },
+ new TournamentUser { Username = "Hello" },
+ new TournamentUser { Username = "Hello" },
}
}
},
@@ -154,10 +152,10 @@ namespace osu.Game.Tournament.Tests
}
};
- public static APIBeatmap CreateSampleBeatmap() =>
- new APIBeatmap
+ public static TournamentBeatmap CreateSampleBeatmap() =>
+ new TournamentBeatmap
{
- BeatmapSet = new APIBeatmapSet
+ Metadata = new BeatmapMetadata
{
Title = "Test Title",
Artist = "Test Artist",
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/DrawableTeamWithPlayers.cs b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs
index 21de2bb0eb..0bb35d534c 100644
--- a/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs
+++ b/osu.Game.Tournament/Components/DrawableTeamWithPlayers.cs
@@ -7,7 +7,6 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
@@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Components
},
};
- TournamentSpriteText createPlayerText(APIUser p) =>
+ TournamentSpriteText createPlayerText(TournamentUser p) =>
new TournamentSpriteText
{
Text = p.Username,
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/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
index ecc655da99..a9056166be 100644
--- a/osu.Game.Tournament/Components/SongBar.cs
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -14,9 +14,9 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Extensions;
using osu.Game.Graphics;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Screens.Menu;
+using osu.Game.Tournament.Models;
using osuTK;
using osuTK.Graphics;
@@ -24,14 +24,14 @@ namespace osu.Game.Tournament.Components
{
public class SongBar : CompositeDrawable
{
- private APIBeatmap beatmap;
+ private TournamentBeatmap beatmap;
public const float HEIGHT = 145 / 2f;
[Resolved]
private IBindable ruleset { get; set; }
- public APIBeatmap Beatmap
+ public TournamentBeatmap Beatmap
{
set
{
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index 6416abe8a9..462743cc3d 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -14,7 +14,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.Models;
using osuTK.Graphics;
@@ -22,7 +21,7 @@ namespace osu.Game.Tournament.Components
{
public class TournamentBeatmapPanel : CompositeDrawable
{
- public readonly APIBeatmap Beatmap;
+ public readonly TournamentBeatmap Beatmap;
private readonly string mod;
@@ -31,7 +30,7 @@ namespace osu.Game.Tournament.Components
private readonly Bindable currentMatch = new Bindable();
private Box flash;
- public TournamentBeatmapPanel(APIBeatmap beatmap, string mod = null)
+ public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null)
{
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
@@ -61,7 +60,7 @@ namespace osu.Game.Tournament.Components
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.5f),
- OnlineInfo = Beatmap.BeatmapSet,
+ OnlineInfo = Beatmap,
},
new FillFlowContainer
{
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index 77af3e9b58..ad564c58c3 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -96,7 +96,7 @@ namespace osu.Game.Tournament.IPC
else
{
beatmapLookupRequest = new GetBeatmapRequest(new APIBeatmap { OnlineID = beatmapId });
- beatmapLookupRequest.Success += b => Beatmap.Value = b;
+ beatmapLookupRequest.Success += b => Beatmap.Value = new TournamentBeatmap(b);
API.Queue(beatmapLookupRequest);
}
}
diff --git a/osu.Game.Tournament/IPC/MatchIPCInfo.cs b/osu.Game.Tournament/IPC/MatchIPCInfo.cs
index ef1e36f871..f438923803 100644
--- a/osu.Game.Tournament/IPC/MatchIPCInfo.cs
+++ b/osu.Game.Tournament/IPC/MatchIPCInfo.cs
@@ -6,13 +6,13 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Legacy;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.IPC
{
public class MatchIPCInfo : Component
{
- public Bindable Beatmap { get; } = new Bindable();
+ public Bindable Beatmap { get; } = new Bindable();
public Bindable Mods { get; } = new Bindable();
public Bindable State { get; } = new Bindable();
public Bindable ChatChannel { get; } = new Bindable();
diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs
index 90e31ba352..65ef77e53d 100644
--- a/osu.Game.Tournament/Models/RoundBeatmap.cs
+++ b/osu.Game.Tournament/Models/RoundBeatmap.cs
@@ -4,7 +4,6 @@
#nullable disable
using Newtonsoft.Json;
-using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tournament.Models
{
@@ -14,6 +13,6 @@ namespace osu.Game.Tournament.Models
public string Mods;
[JsonProperty("BeatmapInfo")]
- public APIBeatmap Beatmap;
+ public TournamentBeatmap Beatmap;
}
}
diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs
index c854488319..03beb7ca9a 100644
--- a/osu.Game.Tournament/Models/SeedingBeatmap.cs
+++ b/osu.Game.Tournament/Models/SeedingBeatmap.cs
@@ -5,7 +5,6 @@
using Newtonsoft.Json;
using osu.Framework.Bindables;
-using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tournament.Models
{
@@ -14,7 +13,7 @@ namespace osu.Game.Tournament.Models
public int ID;
[JsonProperty("BeatmapInfo")]
- public APIBeatmap Beatmap;
+ public TournamentBeatmap Beatmap;
public long Score;
diff --git a/osu.Game.Tournament/Models/TournamentBeatmap.cs b/osu.Game.Tournament/Models/TournamentBeatmap.cs
new file mode 100644
index 0000000000..274fddc490
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentBeatmap.cs
@@ -0,0 +1,99 @@
+// 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.Extensions.ObjectExtensions;
+using osu.Game.Beatmaps;
+using osu.Game.Extensions;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Rulesets;
+
+namespace osu.Game.Tournament.Models
+{
+ public class TournamentBeatmap : IBeatmapInfo, IBeatmapSetOnlineInfo
+ {
+ public int OnlineID { get; set; }
+
+ public string DifficultyName { get; set; } = string.Empty;
+
+ public double BPM { get; set; }
+
+ public double Length { get; set; }
+
+ public double StarRating { get; set; }
+
+ public IBeatmapMetadataInfo Metadata { get; set; } = new BeatmapMetadata();
+
+ public IBeatmapDifficultyInfo Difficulty { get; set; } = new BeatmapDifficulty();
+
+ public BeatmapSetOnlineCovers Covers { get; set; }
+
+ public TournamentBeatmap()
+ {
+ }
+
+ public TournamentBeatmap(APIBeatmap beatmap)
+ {
+ OnlineID = beatmap.OnlineID;
+ DifficultyName = beatmap.DifficultyName;
+ BPM = beatmap.BPM;
+ Length = beatmap.Length;
+ StarRating = beatmap.StarRating;
+ Metadata = beatmap.Metadata;
+ Difficulty = beatmap.Difficulty;
+ Covers = beatmap.BeatmapSet.AsNonNull().Covers;
+ }
+
+ public bool Equals(IBeatmapInfo? other) => other is TournamentBeatmap b && this.MatchesOnlineID(b);
+
+ #region IBeatmapInfo/IBeatmapSetOnlineInfo explicit implementation
+
+ IBeatmapSetInfo IBeatmapInfo.BeatmapSet => throw new NotImplementedException();
+
+ string IBeatmapSetOnlineInfo.Preview => throw new NotImplementedException();
+
+ double IBeatmapSetOnlineInfo.BPM => throw new NotImplementedException();
+
+ int IBeatmapSetOnlineInfo.PlayCount => throw new NotImplementedException();
+
+ int IBeatmapSetOnlineInfo.FavouriteCount => throw new NotImplementedException();
+
+ bool IBeatmapSetOnlineInfo.HasFavourited => throw new NotImplementedException();
+
+ BeatmapSetOnlineAvailability IBeatmapSetOnlineInfo.Availability => throw new NotImplementedException();
+
+ BeatmapSetOnlineGenre IBeatmapSetOnlineInfo.Genre => throw new NotImplementedException();
+
+ BeatmapSetOnlineLanguage IBeatmapSetOnlineInfo.Language => throw new NotImplementedException();
+
+ int? IBeatmapSetOnlineInfo.TrackId => throw new NotImplementedException();
+
+ int[] IBeatmapSetOnlineInfo.Ratings => throw new NotImplementedException();
+
+ BeatmapSetHypeStatus IBeatmapSetOnlineInfo.HypeStatus => throw new NotImplementedException();
+
+ BeatmapSetNominationStatus IBeatmapSetOnlineInfo.NominationStatus => throw new NotImplementedException();
+
+ string IBeatmapInfo.Hash => throw new NotImplementedException();
+
+ string IBeatmapInfo.MD5Hash => throw new NotImplementedException();
+
+ IRulesetInfo IBeatmapInfo.Ruleset => throw new NotImplementedException();
+
+ DateTimeOffset IBeatmapSetOnlineInfo.Submitted => throw new NotImplementedException();
+
+ DateTimeOffset? IBeatmapSetOnlineInfo.Ranked => throw new NotImplementedException();
+
+ DateTimeOffset? IBeatmapSetOnlineInfo.LastUpdated => throw new NotImplementedException();
+
+ BeatmapOnlineStatus IBeatmapSetOnlineInfo.Status => throw new NotImplementedException();
+
+ bool IBeatmapSetOnlineInfo.HasExplicitContent => throw new NotImplementedException();
+
+ bool IBeatmapSetOnlineInfo.HasVideo => throw new NotImplementedException();
+
+ bool IBeatmapSetOnlineInfo.HasStoryboard => throw new NotImplementedException();
+
+ #endregion
+ }
+}
diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs
index 051f1a6d80..ac57f748da 100644
--- a/osu.Game.Tournament/Models/TournamentTeam.cs
+++ b/osu.Game.Tournament/Models/TournamentTeam.cs
@@ -7,7 +7,6 @@ using System;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Bindables;
-using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tournament.Models
{
@@ -38,7 +37,7 @@ namespace osu.Game.Tournament.Models
{
get
{
- int[] ranks = Players.Select(p => p.Statistics?.GlobalRank)
+ int[] ranks = Players.Select(p => p.Rank)
.Where(i => i.HasValue)
.Select(i => i.Value)
.ToArray();
@@ -59,7 +58,7 @@ namespace osu.Game.Tournament.Models
};
[JsonProperty]
- public BindableList Players { get; set; } = new BindableList();
+ public BindableList Players { get; set; } = new BindableList();
public TournamentTeam()
{
@@ -67,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/Models/TournamentUser.cs b/osu.Game.Tournament/Models/TournamentUser.cs
new file mode 100644
index 0000000000..80e58538e5
--- /dev/null
+++ b/osu.Game.Tournament/Models/TournamentUser.cs
@@ -0,0 +1,58 @@
+// 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 Newtonsoft.Json;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Users;
+
+namespace osu.Game.Tournament.Models
+{
+ ///
+ /// A tournament player user, containing simple information about the player.
+ ///
+ [Serializable]
+ public class TournamentUser : IUser
+ {
+ [JsonProperty(@"id")]
+ public int OnlineID { get; set; }
+
+ public string Username { get; set; } = string.Empty;
+
+ ///
+ /// The player's country.
+ ///
+ public Country? Country { get; set; }
+
+ ///
+ /// The player's global rank, or null if not available.
+ ///
+ public int? Rank { get; set; }
+
+ ///
+ /// A URL to the player's profile cover.
+ ///
+ public string CoverUrl { get; set; } = string.Empty;
+
+ public APIUser ToAPIUser()
+ {
+ var user = new APIUser
+ {
+ Id = OnlineID,
+ Username = Username,
+ Country = Country,
+ CoverUrl = CoverUrl,
+ };
+
+ user.Statistics = new UserStatistics
+ {
+ User = user,
+ GlobalRank = Rank
+ };
+
+ return user;
+ }
+
+ bool IUser.IsBot => false;
+ }
+}
diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
index 296267a4bc..763f576afe 100644
--- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
+++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
@@ -7,9 +7,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Legacy;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
+using osu.Game.Tournament.Models;
namespace osu.Game.Tournament.Screens
{
@@ -39,7 +39,7 @@ namespace osu.Game.Tournament.Screens
SongBar.Mods = mods.NewValue;
}
- private void beatmapChanged(ValueChangedEvent beatmap)
+ private void beatmapChanged(ValueChangedEvent beatmap)
{
SongBar.FadeInFromZero(300, Easing.OutQuint);
SongBar.Beatmap = beatmap.NewValue;
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/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
index 214ae65289..1b670f4b69 100644
--- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -239,7 +239,7 @@ namespace osu.Game.Tournament.Screens.Editors
req.Success += res =>
{
- Model.Beatmap = res;
+ Model.Beatmap = new TournamentBeatmap(res);
updatePanel();
};
diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
index 1377c11225..1bc929604d 100644
--- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
@@ -241,7 +241,7 @@ namespace osu.Game.Tournament.Screens.Editors
req.Success += res =>
{
- Model.Beatmap = res;
+ Model.Beatmap = new TournamentBeatmap(res);
updatePanel();
};
diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
index 005d8f36bb..11db37c8b7 100644
--- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs
@@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.API;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Settings;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models;
@@ -202,14 +201,14 @@ namespace osu.Game.Tournament.Screens.Editors
public void CreateNew()
{
- var user = new APIUser();
- team.Players.Add(user);
- flow.Add(new PlayerRow(team, user));
+ var player = new TournamentUser();
+ team.Players.Add(player);
+ flow.Add(new PlayerRow(team, player));
}
public class PlayerRow : CompositeDrawable
{
- private readonly APIUser user;
+ private readonly TournamentUser user;
[Resolved]
protected IAPIProvider API { get; private set; }
@@ -217,11 +216,11 @@ namespace osu.Game.Tournament.Screens.Editors
[Resolved]
private TournamentGameBase game { get; set; }
- private readonly Bindable userId = new Bindable();
+ private readonly Bindable playerId = new Bindable();
private readonly Container drawableContainer;
- public PlayerRow(TournamentTeam team, APIUser user)
+ public PlayerRow(TournamentTeam team, TournamentUser user)
{
this.user = user;
@@ -254,7 +253,7 @@ namespace osu.Game.Tournament.Screens.Editors
LabelText = "User ID",
RelativeSizeAxes = Axes.None,
Width = 200,
- Current = userId,
+ Current = playerId,
},
drawableContainer = new Container
{
@@ -281,10 +280,10 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader]
private void load()
{
- userId.Value = user.Id;
- userId.BindValueChanged(id =>
+ playerId.Value = user.OnlineID;
+ playerId.BindValueChanged(id =>
{
- user.Id = id.NewValue ?? 0;
+ user.OnlineID = id.NewValue ?? 0;
if (id.NewValue != id.OldValue)
user.Username = string.Empty;
@@ -295,13 +294,13 @@ namespace osu.Game.Tournament.Screens.Editors
return;
}
- game.PopulateUser(user, updatePanel, updatePanel);
+ game.PopulatePlayer(user, updatePanel, updatePanel);
}, true);
}
private void updatePanel()
{
- drawableContainer.Child = new UserGridPanel(user) { Width = 300 };
+ drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 };
}
}
}
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/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index 7ac4510ae5..5eb2142fae 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
@@ -107,7 +106,7 @@ namespace osu.Game.Tournament.Screens.MapPool
ipc.Beatmap.BindValueChanged(beatmapChanged);
}
- private void beatmapChanged(ValueChangedEvent beatmap)
+ private void beatmapChanged(ValueChangedEvent beatmap)
{
if (CurrentMatch.Value == null || CurrentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2)
return;
diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
index fb33b3662a..447d6f44ce 100644
--- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
+++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tournament.Screens.Setup
dropdown.Items = storage.ListTournaments();
dropdown.Current.BindValueChanged(v => Button.Enabled.Value = v.NewValue != startupTournament, true);
- Action = () => game.GracefullyExit();
+ Action = () => game.AttemptExit();
folderButton.Action = () => storage.PresentExternally();
ButtonText = "Close osu!";
diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
index b9559015d6..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)
});
}
@@ -257,7 +257,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
};
foreach (var p in team.Players)
- fill.Add(new RowDisplay(p.Username, p.Statistics?.GlobalRank?.ToString("\\##,0") ?? "-"));
+ fill.Add(new RowDisplay(p.Username, p.Rank?.ToString("\\##,0") ?? "-"));
}
internal class RowDisplay : CompositeDrawable
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 2d6d5a01e4..75c9f17d4c 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -22,7 +22,6 @@ using osu.Game.Tournament.IO;
using osu.Game.Tournament.IPC;
using osu.Game.Tournament.Models;
using osuTK.Input;
-using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tournament
{
@@ -63,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));
}
@@ -187,9 +186,7 @@ namespace osu.Game.Tournament
{
var playersRequiringPopulation = ladder.Teams
.SelectMany(t => t.Players)
- .Where(p => string.IsNullOrEmpty(p.Username)
- || p.Statistics?.GlobalRank == null
- || p.Statistics?.CountryRank == null).ToList();
+ .Where(p => string.IsNullOrEmpty(p.Username) || p.Rank == null).ToList();
if (playersRequiringPopulation.Count == 0)
return false;
@@ -197,7 +194,7 @@ namespace osu.Game.Tournament
for (int i = 0; i < playersRequiringPopulation.Count; i++)
{
var p = playersRequiringPopulation[i];
- PopulateUser(p, immediate: true);
+ PopulatePlayer(p, immediate: true);
updateLoadProgressMessage($"Populating user stats ({i} / {playersRequiringPopulation.Count})");
}
@@ -211,7 +208,7 @@ namespace osu.Game.Tournament
{
var beatmapsRequiringPopulation = ladder.Rounds
.SelectMany(r => r.Beatmaps)
- .Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList();
+ .Where(b => b.Beatmap?.OnlineID == 0 && b.ID > 0).ToList();
if (beatmapsRequiringPopulation.Count == 0)
return false;
@@ -222,7 +219,7 @@ namespace osu.Game.Tournament
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID });
API.Perform(req);
- b.Beatmap = req.Response ?? new APIBeatmap();
+ b.Beatmap = new TournamentBeatmap(req.Response ?? new APIBeatmap());
updateLoadProgressMessage($"Populating round beatmaps ({i} / {beatmapsRequiringPopulation.Count})");
}
@@ -238,7 +235,7 @@ namespace osu.Game.Tournament
var beatmapsRequiringPopulation = ladder.Teams
.SelectMany(r => r.SeedingResults)
.SelectMany(r => r.Beatmaps)
- .Where(b => string.IsNullOrEmpty(b.Beatmap?.BeatmapSet?.Title) && b.ID > 0).ToList();
+ .Where(b => b.Beatmap?.OnlineID == 0 && b.ID > 0).ToList();
if (beatmapsRequiringPopulation.Count == 0)
return false;
@@ -249,7 +246,7 @@ namespace osu.Game.Tournament
var req = new GetBeatmapRequest(new APIBeatmap { OnlineID = b.ID });
API.Perform(req);
- b.Beatmap = req.Response ?? new APIBeatmap();
+ b.Beatmap = new TournamentBeatmap(req.Response ?? new APIBeatmap());
updateLoadProgressMessage($"Populating seeding beatmaps ({i} / {beatmapsRequiringPopulation.Count})");
}
@@ -259,9 +256,9 @@ namespace osu.Game.Tournament
private void updateLoadProgressMessage(string s) => Schedule(() => initialisationText.Text = s);
- public void PopulateUser(APIUser user, Action success = null, Action failure = null, bool immediate = false)
+ public void PopulatePlayer(TournamentUser user, Action success = null, Action failure = null, bool immediate = false)
{
- var req = new GetUserRequest(user.Id, ladder.Ruleset.Value);
+ var req = new GetUserRequest(user.OnlineID, ladder.Ruleset.Value);
if (immediate)
{
@@ -270,10 +267,10 @@ namespace osu.Game.Tournament
}
else
{
- req.Success += res => { populate(); };
+ req.Success += _ => { populate(); };
req.Failure += _ =>
{
- user.Id = 1;
+ user.OnlineID = 1;
failure?.Invoke();
};
@@ -287,12 +284,12 @@ namespace osu.Game.Tournament
if (res == null)
return;
- user.Id = res.Id;
+ user.OnlineID = res.Id;
user.Username = res.Username;
- user.Statistics = res.Statistics;
+ user.CoverUrl = res.CoverUrl;
user.Country = res.Country;
- user.Cover = res.Cover;
+ user.Rank = res.Statistics?.GlobalRank;
success?.Invoke();
}
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;
-
///