diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56b3ebe87b..213c5082ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: run: dotnet tool restore - name: Restore Packages - run: dotnet restore + run: dotnet restore osu.Desktop.slnf - name: Restore inspectcode cache uses: actions/cache@v3 @@ -113,27 +113,36 @@ jobs: with: dotnet-version: "6.0.x" - - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1 + - name: Install .NET workloads + run: dotnet workload install maui-android - - name: Build - run: msbuild osu.Android/osu.Android.csproj /restore /p:Configuration=Debug + - name: Compile + run: dotnet build -c Debug osu.Android.slnf build-only-ios: name: Build only (iOS) - runs-on: macos-latest + # change to macos-latest once GitHub finishes migrating all repositories to macOS 12. + runs-on: macos-12 timeout-minutes: 60 steps: - name: Checkout uses: actions/checkout@v2 + # see https://github.com/actions/runner-images/issues/6771#issuecomment-1354713617 + # remove once all workflow VMs use Xcode 14.1 + - name: Set Xcode Version + shell: bash + run: | + sudo xcode-select -s "/Applications/Xcode_14.1.app" + echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.1.app" >> $GITHUB_ENV + - name: Install .NET 6.0.x uses: actions/setup-dotnet@v1 with: dotnet-version: "6.0.x" - # 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. + - name: Install .NET Workloads + run: dotnet workload install maui-ios + - name: Build - run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug + run: dotnet build -c Debug osu.iOS diff --git a/README.md b/README.md index 75d61dad4d..0de82eba75 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj index 092a013614..d09e7647e0 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 osu.Game.Rulesets.EmptyFreeform Library AnyCPU @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index a3607343c9..9c8867f4ef 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 osu.Game.Rulesets.Pippidon Library AnyCPU @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj index 2ea52429ab..5bf3884f53 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 osu.Game.Rulesets.EmptyScrolling Library AnyCPU @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj index a3607343c9..9c8867f4ef 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 osu.Game.Rulesets.Pippidon Library AnyCPU @@ -12,4 +12,4 @@ - \ No newline at end of file + diff --git a/appveyor.yml b/appveyor.yml index 5be73f9875..ed48a997e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2019 +image: Visual Studio 2022 cache: - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' @@ -11,6 +11,8 @@ dotnet_csproj: before_build: - cmd: dotnet --info # Useful when version mismatch between CI and local + - cmd: dotnet workload install maui-android # Change to `dotnet workload restore` once there's no old projects + - cmd: dotnet workload install maui-ios # Change to `dotnet workload restore` once there's no old projects - cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects build: diff --git a/osu.Android.props b/osu.Android.props index 147d236d98..e934b2da6a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -1,64 +1,20 @@  - 8.0 - bin\$(Configuration) - 4 - 2.0 - false - false - Library - 512 - Off - True - Xamarin.Android.Net.AndroidClientHandler - v10.0 - false - true - armeabi-v7a;x86;arm64-v8a - true - cjk,mideast,other,rare,west - SdkOnly - prompt - - - True - portable - False - DEBUG;TRACE - false - true - false - - - false - None - True - false - False + 21.0 + android-x86;android-arm;android-arm64 + apk + CJK;Mideast;Rare;West;Other; + Xamarin.Android.Net.AndroidMessageHandler + + true true - - osu.licenseheader - - - - - - - - - - - - - - - - - - - - + + + + true + diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/AndroidManifest.xml similarity index 95% rename from osu.Android/Properties/AndroidManifest.xml rename to osu.Android/AndroidManifest.xml index 165a64a424..be326be5eb 100644 --- a/osu.Android/Properties/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Android/Linker.xml b/osu.Android/Linker.xml deleted file mode 100644 index c7d31fe087..0000000000 --- a/osu.Android/Linker.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index be40db7508..ca3d628447 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Android.App; using Android.Content; @@ -74,11 +75,23 @@ namespace osu.Android Debug.Assert(Resources?.DisplayMetrics != null); Point displaySize = new Point(); +#pragma warning disable 618 // GetSize is deprecated WindowManager.DefaultDisplay.GetSize(displaySize); +#pragma warning restore 618 float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; + + // Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android. + // The assembly files are not available as files either after native AOT. + // Manually load them so that they can be loaded by RulesetStore.loadFromAppDomain. + // REMEMBER to fully uninstall previous version every time when investigating this! + // Don't forget osu.Game.Tests.Android too. + Assembly.Load("osu.Game.Rulesets.Osu"); + Assembly.Load("osu.Game.Rulesets.Taiko"); + Assembly.Load("osu.Game.Rulesets.Catch"); + Assembly.Load("osu.Game.Rulesets.Mania"); } protected override void OnNewIntent(Intent intent) => handleIntent(intent); @@ -127,7 +140,7 @@ namespace osu.Android cursor.MoveToFirst(); - int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); + int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName); string filename = cursor.GetString(filenameColumn); // SharpCompress requires archive streams to be seekable, which the stream opened by diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 1c6f41a7ec..0227d2aec2 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,7 @@ using System; using Android.App; -using Android.OS; +using Microsoft.Maui.Devices; using osu.Framework.Allocation; using osu.Framework.Android.Input; using osu.Framework.Input.Handlers; @@ -14,7 +14,6 @@ using osu.Game; using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; -using Xamarin.Essentials; namespace osu.Android { @@ -48,7 +47,7 @@ namespace osu.Android // https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated string versionName = string.Empty; - if (Build.VERSION.SdkInt >= BuildVersionCodes.P) + if (OperatingSystem.IsAndroidVersionAtLeast(28)) { versionName = packageInfo.LongVersionCode.ToString(); // ensure we only read the trailing portion of long (the part we are interested in). diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 004cc8c39c..de53e5dd59 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -1,73 +1,19 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Android osu.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - false - - - cjk;mideast;other;rare;west - d8 - r8 - - - None - cjk;mideast;other;rare;west - true + true + + false - - - - - - + + + + + - - - - - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - - - - - - - - 5.0.0 - - - - - - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml similarity index 96% rename from osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml index f8c3fcd894..bf7c0bfeca 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj index 94fdba4a3e..4ee3219442 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj +++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs index 71d943ece1..1fcb0aa427 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Catch.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 16a2b99997..5ace6c07f5 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index be6044bbd0..acf12bb0ac 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs index e5cdcf706c..43918bda57 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs @@ -121,9 +121,7 @@ namespace osu.Game.Rulesets.Catch.Edit return new SnapResult(originPosition, StartTime); } - return enumerateSnappingCandidates(time) - .OrderBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition)) - .FirstOrDefault(); + return enumerateSnappingCandidates(time).MinBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition)); } private IEnumerable enumerateSnappingCandidates(double time) diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index e2f95ca177..ecce7c1b3f 100644 --- a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj +++ b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Library true catch the fruit. to the beat. @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml similarity index 95% rename from osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index de7935b2ef..4a1545a423 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj index 9674186039..25335754d2 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj +++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {531F1092-DB27-445D-AA33-2A77C7187C99} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs index 2d1015387a..a508198f7f 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Mania.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 82d1c8ea24..ff5dde856e 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 88ad484bc1..51e07dd6c1 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 308238d87a..77f93b4ef9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(hitObject, beatmap, previousPattern) { - if (random == null) throw new ArgumentNullException(nameof(random)); - if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); + ArgumentNullException.ThrowIfNull(random); + ArgumentNullException.ThrowIfNull(originalBeatmap); Random = random; OriginalBeatmap = originalBeatmap; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index b2e89c3410..931673f337 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) { - if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); + ArgumentNullException.ThrowIfNull(hitObject); + ArgumentNullException.ThrowIfNull(beatmap); + ArgumentNullException.ThrowIfNull(previousPattern); HitObject = hitObject; Beatmap = beatmap; diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 1a67117c03..4d93826240 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils public static void Sort(T[] keys, IComparer comparer) { - if (keys == null) - throw new ArgumentNullException(nameof(keys)); + ArgumentNullException.ThrowIfNull(keys); if (keys.Length == 0) return; diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6a31fb3fda..10460f52fd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -134,6 +134,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (skin != null) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 01e9926ad7..e3ebadc836 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI public ManiaPlayfield(List stageDefinitions) { - if (stageDefinitions == null) - throw new ArgumentNullException(nameof(stageDefinitions)); + ArgumentNullException.ThrowIfNull(stageDefinitions); if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index fc38a96a35..c1d3e85bf1 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the judgement pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (currentSkin != null) diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 4f6840f9ca..72f172188e 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Library true smash the keys. to the beat. @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml similarity index 95% rename from osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index 3ce17ccc27..45d27dda70 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj index f4b673f10b..e8a46a9828 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj +++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj @@ -1,49 +1,27 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {90CAB706-39CB-4B93-9629-3218A6FF8E9B} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + - - 5.0.0 - + - \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs index ad23f3ee33..6ef29fa68e 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Osu.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index a88b74695c..1e33f2ff16 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index 545abcec6c..7d50deb8ba 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -1,35 +1,20 @@ - - + - Debug - iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96} + Exe + net6.0-ios + 13.4 Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 3a175888d9..66d99e0bf1 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -133,6 +133,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(e.NewItems != null); + // If inserting in the path (not appending), // update indices of existing connections after insert location if (e.NewStartingIndex < Pieces.Count) @@ -164,6 +166,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(e.OldItems != null); + foreach (var point in e.OldItems.Cast()) { foreach (var piece in Pieces.Where(p => p.ControlPoint == point).ToArray()) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 1cb3208c30..74e16f7e0b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -82,10 +82,10 @@ namespace osu.Game.Rulesets.Osu.Replays private class ReplayFrameComparer : IComparer { - public int Compare(ReplayFrame f1, ReplayFrame f2) + public int Compare(ReplayFrame? f1, ReplayFrame? f2) { - if (f1 == null) throw new ArgumentNullException(nameof(f1)); - if (f2 == null) throw new ArgumentNullException(nameof(f2)); + ArgumentNullException.ThrowIfNull(f1); + ArgumentNullException.ThrowIfNull(f2); return f1.Time.CompareTo(f2.Time); } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 98f1e69bd1..bf776fe5dd 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Library true click the circles. to the beat. @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml similarity index 95% rename from osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index d9de0fde4e..452b9683ec 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index 4d4dabebe6..a639326ebd 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {3701A0A1-8476-42C6-B5C4-D24129B4A484} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs index 1ebbd61a94..0e3a953728 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Taiko.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 9628475b3e..76cb3c0db0 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index 8ee640cd99..e648a11299 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index b752c13d18..f0e1cb8e8f 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Library true bash the drum. to the beat. diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml similarity index 95% rename from osu.Game.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Tests.Android/AndroidManifest.xml index 4a63f0c357..f25b2e5328 100644 --- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/MainActivity.cs b/osu.Game.Tests.Android/MainActivity.cs index 6c4f9bac58..bdb947fbb4 100644 --- a/osu.Game.Tests.Android/MainActivity.cs +++ b/osu.Game.Tests.Android/MainActivity.cs @@ -3,7 +3,9 @@ #nullable disable +using System.Reflection; using Android.App; +using Android.OS; using osu.Framework.Android; namespace osu.Game.Tests.Android @@ -12,5 +14,16 @@ namespace osu.Game.Tests.Android public class MainActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuTestBrowser(); + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + // See the comment in OsuGameActivity + Assembly.Load("osu.Game.Rulesets.Osu"); + Assembly.Load("osu.Game.Rulesets.Taiko"); + Assembly.Load("osu.Game.Rulesets.Catch"); + Assembly.Load("osu.Game.Rulesets.Mania"); + } } } diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index afafec6b1f..b745d91980 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -1,88 +1,34 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Tests osu.Game.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - - - - $(NoWarn);CA2007 - - None - cjk;mideast;other;rare;west - true - - + %(RecursiveDir)%(Filename)%(Extension) - - + + %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + + + + - - 5.0.0 - - diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs index cf36fea139..4678be4fb8 100644 --- a/osu.Game.Tests.iOS/Application.cs +++ b/osu.Game.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 31e2b3f257..ac661f6263 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 05b3cad6da..79771fcd50 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,54 +1,26 @@ - - + - Debug - iPhoneSimulator Exe - {65FF8E19-6934-469B-B690-23C6D6E56A17} + net6.0-ios + 13.4 osu.Game.Tests osu.Game.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - $(NoWarn);CA2007 - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - + + + + + - diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index ebfa9bd8b7..3c35dc311f 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -26,6 +26,16 @@ namespace osu.Game.Tests.Chat MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl; } + [Test] + public void TestUnsupportedProtocolLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a gopher://really-old-protocol we don't support." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("gopher://really-old-protocol", result.Links[0].Url); + } + [Test] public void TestBareLink() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 6bc2922253..a141e4d431 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene { - private ZoomableScrollContainer scrollContainer; + private TestZoomableScrollContainer scrollContainer; private Drawable innerBox; [SetUpSteps] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer(1, 60, 1) + scrollContainer = new TestZoomableScrollContainer(1, 60, 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,6 +93,14 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } + [Test] + public void TestWidthUpdatesOnSecondZoomSetup() + { + AddAssert("Inner container width = 1x", () => innerBox.DrawWidth == scrollContainer.DrawWidth); + AddStep("reload zoom", () => scrollContainer.SetupZoom(10, 10, 60)); + AddAssert("Inner container width = 10x", () => innerBox.DrawWidth == scrollContainer.DrawWidth * 10); + } + [Test] public void TestZoom0() { @@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad; private Quad boxQuad => innerBox.ScreenSpaceDrawQuad; + + private partial class TestZoomableScrollContainer : ZoomableScrollContainer + { + public TestZoomableScrollContainer(int minimum, float maximum, float initial) + : base(minimum, maximum, initial) + { + } + + public new void SetupZoom(float initial, float minimum, float maximum) => base.SetupZoom(initial, minimum, maximum); + } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs index 7981e212d4..d925141510 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs @@ -77,14 +77,14 @@ namespace osu.Game.Tests.Visual.Online { var comments = this.ChildrenOfType(); var ourComment = comments.SingleOrDefault(x => x.Comment.Id == 1); - return ourComment != null && ourComment.ChildrenOfType().Any(x => x.Text == "Delete"); + return ourComment != null && ourComment.ChildrenOfType().Any(x => x.Text == "delete"); }); AddAssert("Second doesn't", () => { var comments = this.ChildrenOfType(); var ourComment = comments.Single(x => x.Comment.Id == 2); - return ourComment.ChildrenOfType().All(x => x.Text != "Delete"); + return ourComment.ChildrenOfType().All(x => x.Text != "delete"); }); } @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("It has delete button", () => { - var btn = ourComment.ChildrenOfType().Single(x => x.Text == "Delete"); + var btn = ourComment.ChildrenOfType().Single(x => x.Text == "delete"); InputManager.MoveMouseTo(btn); }); AddStep("Click delete button", () => @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("It has delete button", () => { - var btn = ourComment.ChildrenOfType().Single(x => x.Text == "Delete"); + var btn = ourComment.ChildrenOfType().Single(x => x.Text == "delete"); InputManager.MoveMouseTo(btn); }); AddStep("Click delete button", () => @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online if (request is not CommentDeleteRequest req) return false; - req.TriggerFailure(new Exception()); + req.TriggerFailure(new InvalidOperationException()); delete = true; return false; }; @@ -245,7 +245,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Click the button", () => { - var btn = targetComment.ChildrenOfType().Single(x => x.Text == "Report"); + var btn = targetComment.ChildrenOfType().Single(x => x.Text == "report"); InputManager.MoveMouseTo(btn); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b486f800c6..b353123649 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -16,13 +16,17 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Containers.Markdown; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; +using osu.Game.Users.Drawables; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public partial class TestSceneWikiMarkdownContainer : OsuTestScene + public partial class TestSceneWikiMarkdownContainer : OsuManualInputManagerTestScene { + private OverlayScrollContainer scrollContainer; private TestMarkdownContainer markdownContainer; [Cached] @@ -38,15 +42,25 @@ namespace osu.Game.Tests.Visual.Online Colour = overlayColour.Background5, RelativeSizeAxes = Axes.Both, }, - new BasicScrollContainer + scrollContainer = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), - Child = markdownContainer = new TestMarkdownContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } + } + }; + + scrollContainer.Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayScrollContainer), scrollContainer) + }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = markdownContainer = new TestMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, } }; }); @@ -197,6 +211,63 @@ Line after image"; markdownContainer.CurrentPath = @"https://dev.ppy.sh"; markdownContainer.Text = "::{flag=\"AU\"}:: ::{flag=\"ZZ\"}::"; }); + AddAssert("Two flags visible", () => markdownContainer.ChildrenOfType().Count(), () => Is.EqualTo(2)); + } + + [Test] + public void TestHeadingWithIdAttribute() + { + AddStep("Add heading with ID", () => + { + markdownContainer.Text = "# This is a heading with an ID {#this-is-the-id}"; + }); + AddAssert("ID not visible", () => markdownContainer.ChildrenOfType().All(spriteText => spriteText.Text != "{#this-is-the-id}")); + } + + [Test] + public void TestFootnotes() + { + AddStep("set content", () => markdownContainer.Text = @"This text has a footnote[^test]. + +Here's some more text[^test2] with another footnote! + +# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam efficitur laoreet posuere. Ut accumsan tortor in ipsum tincidunt ultrices. Suspendisse a malesuada tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce a sagittis nibh. In et velit sit amet mauris aliquet consectetur quis vehicula lorem. Etiam sit amet tellus ac velit ornare maximus. Donec quis metus eget libero ullamcorper imperdiet id vitae arcu. Vivamus iaculis rhoncus purus malesuada mollis. Vestibulum dictum at nisi sed tincidunt. Suspendisse finibus, ipsum ut dapibus commodo, leo eros porttitor sapien, non scelerisque nisi ligula sed ex. Pellentesque magna orci, hendrerit eu iaculis sit amet, ullamcorper in urna. Vivamus dictum mauris orci, nec facilisis dolor fringilla eu. Sed at porttitor nisi, at venenatis urna. Ut at orci vitae libero semper ullamcorper eu ut risus. Mauris hendrerit varius enim, ut varius nisi feugiat mattis. + +## In at eros urna. Sed ipsum lorem, tempor sit amet purus in, vehicula pellentesque leo. Fusce volutpat pellentesque velit sit amet porttitor. Nulla eget erat ex. Praesent eu lacinia est, quis vehicula lacus. Donec consequat ultrices neque, at finibus quam efficitur vel. Vestibulum molestie nisl sit amet metus semper, at vestibulum massa rhoncus. Quisque imperdiet suscipit augue, et dignissim odio eleifend ut. + +Aliquam sed vestibulum mauris, ut lobortis elit. Sed quis lacinia erat. Nam ultricies, risus non pellentesque sollicitudin, mauris dolor tincidunt neque, ac porta ipsum dui quis libero. Integer eget velit neque. Vestibulum venenatis mauris vitae rutrum vestibulum. Maecenas suscipit eu purus eu tempus. Nam dui nisl, bibendum condimentum mollis et, gravida vel dui. Sed et eros rutrum, facilisis sapien eu, mattis ligula. Fusce finibus pulvinar dolor quis consequat. + +Donec ipsum felis, feugiat vel fermentum at, commodo eu sapien. Suspendisse nec enim vitae felis laoreet laoreet. Phasellus purus quam, fermentum a pharetra vel, tempor et urna. Integer vitae quam diam. Aliquam tincidunt tortor a iaculis convallis. Suspendisse potenti. Cras quis risus quam. Nullam tincidunt in lorem posuere sagittis. + +Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed urna tristique, laoreet pharetra nulla. Vivamus maximus turpis purus, eu viverra dolor sodales porttitor. Praesent bibendum sapien purus, sed ultricies dolor iaculis sed. Fusce congue hendrerit malesuada. Nulla nulla est, auctor ac fringilla sed, ornare a lorem. Donec quis velit imperdiet, imperdiet sem non, pellentesque sapien. Maecenas in orci id ipsum placerat facilisis non sed nisi. Duis dictum lorem sodales odio dictum eleifend. Vestibulum bibendum euismod quam, eget pharetra orci facilisis sed. Vivamus at diam non ipsum consequat tristique. Pellentesque gravida dignissim pellentesque. Donec ullamcorper lacinia orci, id consequat purus faucibus quis. Phasellus metus nunc, iaculis a interdum vel, congue sed erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam eros libero, hendrerit luctus nulla vitae, luctus maximus nunc. + +[^test]: This is a **footnote**. +[^test2]: This is another footnote [with a link](https://google.com/)!"); + AddStep("shrink scroll height", () => scrollContainer.Height = 0.5f); + + AddStep("press second footnote link", () => + { + InputManager.MoveMouseTo(markdownContainer.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("second footnote scrolled into view", () => + { + var footnote = markdownContainer.ChildrenOfType().ElementAt(1); + return scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.TopLeft) + && scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.BottomRight); + }); + + AddStep("press first footnote backlink", () => + { + InputManager.MoveMouseTo(markdownContainer.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("first footnote link scrolled into view", () => + { + var footnote = markdownContainer.ChildrenOfType().First(); + return scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.TopLeft) + && scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.BottomRight); + }); } private partial class TestMarkdownContainer : WikiMarkdownContainer diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index e47b7e25a8..11d55bc0bd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.SongSelect if (testRequest.Progress >= 0.5f) { - testRequest.TriggerFailure(new Exception()); + testRequest.TriggerFailure(new InvalidOperationException()); return true; } } diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 52769321a9..1157b50377 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null) { - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + ArgumentNullException.ThrowIfNull(beatmap); Beatmap = beatmap; this.mod = mod; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index f940571ffe..2d47560947 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -245,8 +245,10 @@ namespace osu.Game.Tournament.IPC { string stableInstallPath; +#pragma warning disable CA1416 using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); +#pragma warning restore CA1416 if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index a81f11cbe1..1bc576bc63 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tournament private async Task checkForChanges() { - string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()); + string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()).ConfigureAwait(false); // If a save hasn't been triggered by the user yet, populate the initial value lastSerialisedLadder ??= serialisedLadder; diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 988f0a02f0..eb5c9a879a 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -54,8 +53,6 @@ namespace osu.Game.Tournament.Screens.Editors }); } - Debug.Assert(countries != null); - foreach (var c in countries) Storage.Add(c); } diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 8f0d1de0cb..0fb6c1367b 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -102,10 +103,14 @@ namespace osu.Game.Tournament.Screens.Editors switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + args.NewItems.Cast().ForEach(i => flow.Add(CreateDrawable(i))); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + args.OldItems.Cast().ForEach(i => flow.RemoveAll(d => d.Model == i, true)); break; } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 603a7830c7..5aea551c00 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -20,8 +21,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public partial class LadderEditorSettings : PlayerSettingsGroup { - private const int padding = 10; - private SettingsDropdown roundDropdown; private PlayerCheckbox losersCheckbox; private DateTextBox dateTimeBox; @@ -103,10 +102,14 @@ namespace osu.Game.Tournament.Screens.Ladder.Components switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + args.NewItems.Cast().ForEach(add); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); break; } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs index c90cdb7775..f7a42e4f50 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -25,10 +26,14 @@ namespace osu.Game.Tournament.Screens.Ladder.Components switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + args.NewItems.Cast().ForEach(add); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + args.OldItems.Cast().ForEach(i => Control.RemoveDropdownItem(i)); break; } diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 595f08ed36..176c06c0e5 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Caching; @@ -81,11 +82,15 @@ namespace osu.Game.Tournament.Screens.Ladder switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + foreach (var p in args.NewItems.Cast()) addMatch(p); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + foreach (var p in args.OldItems.Cast()) { foreach (var d in MatchesContainer.Where(d => d.Match == p)) @@ -153,7 +158,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var round in LadderInfo.Rounds) { - var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).MinBy(p => p.Y); if (topMatch == null) continue; @@ -167,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var round in LadderInfo.Rounds) { - var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).MinBy(p => p.Y); if (topMatch == null) continue; diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index b049542bb0..ab67e490cd 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,6 +1,6 @@  - netstandard2.1 + net6.0 Library true tools for tournaments. @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 19c78f34b2..0191f96825 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -35,7 +35,7 @@ namespace osu.Game.Audio public bool Equals(SampleInfo? other) => other != null && sampleNames.SequenceEqual(other.sampleNames); - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is SampleInfo other && Equals(other); } } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 8a6315fc65..4752a88199 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { - var imported = await Import(notification, new[] { importTask }); + var imported = await Import(notification, new[] { importTask }).ConfigureAwait(true); if (!imported.Any()) return null; diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 2fd1d06b7b..71d40b1a48 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps { try { - await cacheDownloadRequest.PerformAsync(); + await cacheDownloadRequest.PerformAsync().ConfigureAwait(false); } catch { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0a09e6e7e6..f46e4af332 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -16,7 +16,7 @@ namespace osu.Game.Beatmaps.ControlPoints public void AttachGroup(ControlPointGroup pointGroup) => Time = pointGroup.Time; - public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); + public int CompareTo(ControlPoint? other) => Time.CompareTo(other?.Time); public virtual Color4 GetRepresentingColour(OsuColour colours) => colours.Yellow; @@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public ControlPoint DeepClone() { - var copy = (ControlPoint)Activator.CreateInstance(GetType()); + var copy = (ControlPoint)Activator.CreateInstance(GetType())!; copy.CopyFrom(this); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index db479f0e5b..1f34f3777d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -26,7 +26,7 @@ namespace osu.Game.Beatmaps.ControlPoints Time = time; } - public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time); + public int CompareTo(ControlPointGroup? other) => Time.CompareTo(other?.Time); public void Add(ControlPoint point) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 422e306450..29b7191ecf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -70,14 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// [JsonIgnore] public double BPMMaximum => - 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; + 60000 / (TimingPoints.MinBy(c => c.BeatLength) ?? TimingControlPoint.DEFAULT).BeatLength; /// /// Finds the minimum BPM represented by any timing control point. /// [JsonIgnore] public double BPMMinimum => - 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; + 60000 / (TimingPoints.MaxBy(c => c.BeatLength) ?? TimingControlPoint.DEFAULT).BeatLength; /// /// Remove all s and return to a pristine state. @@ -211,8 +211,7 @@ namespace osu.Game.Beatmaps.ControlPoints public static T BinarySearch(IReadOnlyList list, double time) where T : class, IControlPoint { - if (list == null) - throw new ArgumentNullException(nameof(list)); + ArgumentNullException.ThrowIfNull(list); if (list.Count == 0) return null; @@ -300,7 +299,7 @@ namespace osu.Game.Beatmaps.ControlPoints public ControlPointInfo DeepClone() { - var controlPointInfo = (ControlPointInfo)Activator.CreateInstance(GetType()); + var controlPointInfo = (ControlPointInfo)Activator.CreateInstance(GetType())!; foreach (var point in AllControlPoints) controlPointInfo.Add(point.Time, point.DeepClone()); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index 7a23b32c84..ec00756fd9 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -51,11 +51,11 @@ namespace osu.Game.Beatmaps if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation)) continue; - BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r)).OrderBy(b => + BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r, StringComparison.Ordinal)).MinBy(b => { double difference = b.StarRating - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder - }).FirstOrDefault(); + }); if (beatmapInfo != null) return beatmapInfo; @@ -90,7 +90,7 @@ namespace osu.Game.Beatmaps return recommendedDifficultyMapping .OrderByDescending(pair => pair.Value) .Select(pair => pair.Key) - .Where(r => !r.Equals(ruleset.Value.ShortName)) + .Where(r => !r.Equals(ruleset.Value.ShortName, StringComparison.Ordinal)) .Prepend(ruleset.Value.ShortName); } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index d31a7ae2fe..767504fcb1 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -15,8 +15,7 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapBackgroundSprite(IWorkingBeatmap working) { - if (working == null) - throw new ArgumentNullException(nameof(working)); + ArgumentNullException.ThrowIfNull(working); this.working = working; } diff --git a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs index e4ffc1d553..fc7c14e734 100644 --- a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs @@ -18,8 +18,7 @@ namespace osu.Game.Beatmaps.Drawables public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) { - if (set == null) - throw new ArgumentNullException(nameof(set)); + ArgumentNullException.ThrowIfNull(set); this.set = set; this.type = type; diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index ca1bcc97fd..4f0f11d053 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -57,8 +57,7 @@ namespace osu.Game.Beatmaps.Formats public static Decoder GetDecoder(LineBufferedReader stream) where T : new() { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) throw new IOException(@"Unknown decoder type"); diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index 043bba3134..1e425c88a6 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -91,15 +91,15 @@ namespace osu.Game.Configuration OrderPosition = orderPosition; } - public int CompareTo(SettingSourceAttribute other) + public int CompareTo(SettingSourceAttribute? other) { - if (OrderPosition == other.OrderPosition) + if (OrderPosition == other?.OrderPosition) return 0; // unordered items come last (are greater than any ordered items). if (OrderPosition == null) return 1; - if (other.OrderPosition == null) + if (other?.OrderPosition == null) return -1; // ordered items are sorted by the order value. @@ -113,7 +113,7 @@ namespace osu.Game.Configuration { foreach (var (attr, property) in obj.GetOrderedSettingsSourceProperties()) { - object value = property.GetValue(obj); + object value = property.GetValue(obj)!; if (attr.SettingControlType != null) { @@ -121,7 +121,7 @@ namespace osu.Game.Configuration if (controlType.EnumerateBaseTypes().All(t => !t.IsGenericType || t.GetGenericTypeDefinition() != typeof(SettingsItem<>))) throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} had an unsupported custom control type ({controlType.ReadableName()})"); - var control = (Drawable)Activator.CreateInstance(controlType); + var control = (Drawable)Activator.CreateInstance(controlType)!; controlType.GetProperty(nameof(SettingsItem.SettingSourceObject))?.SetValue(control, obj); controlType.GetProperty(nameof(SettingsItem.LabelText))?.SetValue(control, attr.Label); controlType.GetProperty(nameof(SettingsItem.TooltipText))?.SetValue(control, attr.Description); @@ -188,7 +188,7 @@ namespace osu.Game.Configuration case IBindable bindable: var dropdownType = typeof(ModSettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]); - var dropdown = (Drawable)Activator.CreateInstance(dropdownType); + var dropdown = (Drawable)Activator.CreateInstance(dropdownType)!; dropdownType.GetProperty(nameof(SettingsDropdown.LabelText))?.SetValue(dropdown, attr.Label); dropdownType.GetProperty(nameof(SettingsDropdown.TooltipText))?.SetValue(dropdown, attr.Description); @@ -231,7 +231,7 @@ namespace osu.Game.Configuration // An unknown (e.g. enum) generic type. var valueMethod = u.GetType().GetProperty(nameof(IBindable.Value)); Debug.Assert(valueMethod != null); - return valueMethod.GetValue(u); + return valueMethod.GetValue(u)!; default: // fall back for non-bindable cases. diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 3f1ddb1eda..f9136fa8b6 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -79,16 +79,16 @@ namespace osu.Game.Database switch (content) { case StableContent.Beatmaps: - return await new LegacyBeatmapImporter(beatmaps).GetAvailableCount(stableStorage); + return await new LegacyBeatmapImporter(beatmaps).GetAvailableCount(stableStorage).ConfigureAwait(false); case StableContent.Skins: - return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); + return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage).ConfigureAwait(false); case StableContent.Collections: - return await new LegacyCollectionImporter(realmAccess).GetAvailableCount(stableStorage); + return await new LegacyCollectionImporter(realmAccess).GetAvailableCount(stableStorage).ConfigureAwait(false); case StableContent.Scores: - return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); + return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage).ConfigureAwait(false); default: throw new ArgumentException($"Only one {nameof(StableContent)} flag should be specified."); diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index 3bb11c3a50..2df6f3f508 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -61,6 +61,6 @@ namespace osu.Game.Database public override int GetHashCode() => HashCode.Combine(ID); - public override string ToString() => PerformRead(i => i.ToString()); + public override string? ToString() => PerformRead(i => i.ToString()); } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 2f082306d0..8aece748a8 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -71,9 +71,9 @@ namespace osu.Game.Database bool importSuccessful; if (originalModel != null) - importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel)) != null; + importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel).ConfigureAwait(false)) != null; else - importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) })).Any(); + importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) }).ConfigureAwait(false)).Any(); // for now a failed import will be marked as a failed download for simplicity. if (!importSuccessful) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1a938c12e5..177c671bca 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -481,7 +481,7 @@ namespace osu.Game.Database // server, which we don't use. May want to report upstream or revisit in the future. using (var realm = getRealmInstance()) // ReSharper disable once AccessToDisposedClosure (WriteAsync should be marked as [InstantHandle]). - await realm.WriteAsync(() => action(realm)); + await realm.WriteAsync(() => action(realm)).ConfigureAwait(false); pendingAsyncWrites.Signal(); }); @@ -558,7 +558,7 @@ namespace osu.Game.Database return new InvokeOnDisposal(() => model.PropertyChanged -= onPropertyChanged); - void onPropertyChanged(object sender, PropertyChangedEventArgs args) + void onPropertyChanged(object? sender, PropertyChangedEventArgs args) { if (args.PropertyName == propertyName) onChanged(propLookupCompiled(model)); diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index 35f2d61437..65b9e46764 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -66,10 +66,10 @@ namespace osu.Game.Extensions foreach (var (_, property) in component.GetSettingsSourceProperties()) { - if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) + if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) continue; - skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue); + skinnable.CopyAdjustedSetting(((IBindable)property.GetValue(component)!), settingValue); } } diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index b4a0c02e35..43abb59042 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -37,7 +37,7 @@ namespace osu.Game.Extensions if (cancellationToken.IsCancellationRequested) { - tcs.SetCanceled(); + tcs.SetCanceled(cancellationToken); } else { diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 735b8b4e7d..984d60d35e 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -36,8 +36,7 @@ namespace osu.Game.Graphics.Containers /// The easing type of the initial transform. public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) { - if (logo == null) - throw new ArgumentNullException(nameof(logo)); + ArgumentNullException.ThrowIfNull(logo); if (logo.IsTracking && Logo == null) throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); diff --git a/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs b/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs new file mode 100644 index 0000000000..caed4b26b9 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; +using Markdig.Extensions.GenericAttributes; +using Markdig.Renderers; +using Markdig.Syntax; + +namespace osu.Game.Graphics.Containers.Markdown.Extensions +{ + /// + /// A variant of + /// which only handles generic attributes in the current markdown and ignores inline generic attributes. + /// + /// + /// For rationale, see implementation of . + /// + public class BlockAttributeExtension : IMarkdownExtension + { + private readonly GenericAttributesExtension genericAttributesExtension = new GenericAttributesExtension(); + + public void Setup(MarkdownPipelineBuilder pipeline) + { + genericAttributesExtension.Setup(pipeline); + + // GenericAttributesExtension registers a GenericAttributesParser in pipeline.InlineParsers. + // this conflicts with the CustomContainerExtension, leading to some custom containers (e.g. flags) not displaying. + // as a workaround, remove the inline parser here before it can do damage. + pipeline.InlineParsers.RemoveAll(parser => parser is GenericAttributesParser); + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) => genericAttributesExtension.Setup(pipeline, renderer); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs b/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs new file mode 100644 index 0000000000..10542abe71 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; + +namespace osu.Game.Graphics.Containers.Markdown.Extensions +{ + public static class OsuMarkdownExtensions + { + /// + /// Uses the block attributes extension. + /// + /// The pipeline. + /// The modified pipeline. + public static MarkdownPipelineBuilder UseBlockAttributes(this MarkdownPipelineBuilder pipeline) + { + pipeline.Extensions.AddIfNotAlready(); + return pipeline; + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs new file mode 100644 index 0000000000..e92d866eed --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Extensions.Footnotes; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Containers.Markdown.Footnotes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnote : MarkdownFootnote + { + public OsuMarkdownFootnote(Footnote footnote) + : base(footnote) + { + } + + public override SpriteText CreateOrderMarker(int order) => CreateSpriteText().With(marker => + { + marker.Text = LocalisableString.Format("{0}.", order); + }); + + public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(textFlow => + { + textFlow.Margin = new MarginPadding { Left = 30 }; + }); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs new file mode 100644 index 0000000000..22c02ea720 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs @@ -0,0 +1,62 @@ +// 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 Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteBacklink : OsuHoverContainer + { + private readonly FootnoteLink backlink; + + private SpriteIcon spriteIcon = null!; + + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } = null!; + + protected override IEnumerable EffectTargets => spriteIcon.Yield(); + + public OsuMarkdownFootnoteBacklink(FootnoteLink backlink) + { + this.backlink = backlink; + } + + [BackgroundDependencyLoader(true)] + private void load(OverlayColourProvider colourProvider, OsuMarkdownContainer markdownContainer, OverlayScrollContainer? scrollContainer) + { + float fontSize = parentTextComponent.CreateSpriteText().Font.Size; + Size = new Vector2(fontSize); + + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + + Add(spriteIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Left = 5 }, + Size = new Vector2(fontSize / 2), + Icon = FontAwesome.Solid.ArrowUp, + }); + + if (scrollContainer != null) + { + Action = () => + { + var footnoteLink = markdownContainer.ChildrenOfType().Single(footnoteLink => footnoteLink.FootnoteLink.Index == backlink.Index); + scrollContainer.ScrollIntoView(footnoteLink); + }; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs new file mode 100644 index 0000000000..c9bd408e9e --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs @@ -0,0 +1,80 @@ +// 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 Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteLink : OsuHoverContainer, IHasCustomTooltip + { + public readonly FootnoteLink FootnoteLink; + + private SpriteText spriteText = null!; + + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuMarkdownContainer markdownContainer { get; set; } = null!; + + protected override IEnumerable EffectTargets => spriteText.Yield(); + + public OsuMarkdownFootnoteLink(FootnoteLink footnoteLink) + { + FootnoteLink = footnoteLink; + + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuMarkdownContainer markdownContainer, OverlayScrollContainer? scrollContainer) + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + + spriteText = parentTextComponent.CreateSpriteText(); + + Add(spriteText.With(t => + { + float baseSize = t.Font.Size; + t.Font = t.Font.With(size: baseSize * 0.58f); + t.Margin = new MarginPadding { Bottom = 0.33f * baseSize }; + t.Text = LocalisableString.Format("[{0}]", FootnoteLink.Index); + })); + + if (scrollContainer != null) + { + Action = () => + { + var footnote = markdownContainer.ChildrenOfType().Single(footnote => footnote.Footnote.Label == FootnoteLink.Footnote.Label); + scrollContainer.ScrollIntoView(footnote); + }; + } + } + + public object TooltipContent + { + get + { + var span = FootnoteLink.Footnote.LastChild.Span; + return markdownContainer.Text.Substring(span.Start, span.Length); + } + } + + public ITooltip GetCustomTooltip() => new OsuMarkdownFootnoteTooltip(colourProvider); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs new file mode 100644 index 0000000000..af64913212 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteTooltip : CompositeDrawable, ITooltip + { + private readonly FootnoteMarkdownContainer markdownContainer; + + [Cached] + private OverlayColourProvider colourProvider; + + public OsuMarkdownFootnoteTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + + Masking = true; + Width = 200; + AutoSizeAxes = Axes.Y; + CornerRadius = 4; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6 + }, + markdownContainer = new FootnoteMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + DocumentMargin = new MarginPadding(), + DocumentPadding = new MarginPadding { Horizontal = 10, Vertical = 5 } + } + }; + } + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(object content) => markdownContainer.SetContent((string)content); + + private partial class FootnoteMarkdownContainer : OsuMarkdownContainer + { + private string? lastFootnote; + + public void SetContent(string footnote) + { + if (footnote == lastFootnote) + return; + + lastFootnote = Text = footnote; + } + + public override MarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer(); + } + + private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer + { + protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) + { + // we don't want footnote backlinks to show up in tooltips. + } + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index e884b5db69..5b1780a068 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -4,41 +4,25 @@ #nullable disable using Markdig; -using Markdig.Extensions.AutoLinks; -using Markdig.Extensions.CustomContainers; -using Markdig.Extensions.EmphasisExtras; using Markdig.Extensions.Footnotes; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Containers.Markdown.Footnotes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Graphics.Containers.Markdown { + [Cached] public partial class OsuMarkdownContainer : MarkdownContainer { - /// - /// Allows this markdown container to parse and link footnotes. - /// - /// - protected virtual bool Footnotes => false; - - /// - /// Allows this markdown container to make URL text clickable. - /// - /// - protected virtual bool Autolinks => false; - - /// - /// Allows this markdown container to parse custom containers (used for flags and infoboxes). - /// - /// - protected virtual bool CustomContainers => false; - public OsuMarkdownContainer() { LineSpacing = 21; @@ -99,25 +83,17 @@ namespace osu.Game.Graphics.Containers.Markdown return new OsuMarkdownUnorderedListItem(level); } - // reference: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 - protected override MarkdownPipeline CreateBuilder() - { - var pipeline = new MarkdownPipelineBuilder() - .UseAutoIdentifiers() - .UsePipeTables() - .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) - .UseYamlFrontMatter(); + protected override MarkdownFootnoteGroup CreateFootnoteGroup(FootnoteGroup footnoteGroup) => base.CreateFootnoteGroup(footnoteGroup).With(g => g.Spacing = new Vector2(5)); - if (Footnotes) - pipeline = pipeline.UseFootnotes(); + protected override MarkdownFootnote CreateFootnote(Footnote footnote) => new OsuMarkdownFootnote(footnote); - if (Autolinks) - pipeline = pipeline.UseAutoLinks(); + protected sealed override MarkdownPipeline CreateBuilder() + => Options.BuildPipeline(); - if (CustomContainers) - pipeline.UseCustomContainers(); - - return pipeline.Build(); - } + /// + /// Creates a instance which is used to determine + /// which CommonMark/Markdig extensions should be enabled for this . + /// + protected virtual OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions(); } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs new file mode 100644 index 0000000000..1648ffbf90 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; +using Markdig.Extensions.AutoLinks; +using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.EmphasisExtras; +using Markdig.Extensions.Footnotes; +using osu.Game.Graphics.Containers.Markdown.Extensions; + +namespace osu.Game.Graphics.Containers.Markdown +{ + /// + /// Groups options of customising the set of available extensions to instances. + /// + public class OsuMarkdownContainerOptions + { + /// + /// Allows the to parse and link footnotes. + /// + /// + public bool Footnotes { get; init; } + + /// + /// Allows the container to make URL text clickable. + /// + /// + public bool Autolinks { get; init; } + + /// + /// Allows the to parse custom containers (used for flags and infoboxes). + /// + /// + public bool CustomContainers { get; init; } + + /// + /// Allows the to parse custom attributes in block elements (used e.g. for custom anchor names in the wiki). + /// + /// + public bool BlockAttributes { get; init; } + + /// + /// Returns a prepared according to the options specified by the current instance. + /// + /// + /// Compare: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 + /// + public MarkdownPipeline BuildPipeline() + { + var pipeline = new MarkdownPipelineBuilder() + .UseAutoIdentifiers() + .UsePipeTables() + .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) + .UseYamlFrontMatter(); + + if (Footnotes) + pipeline = pipeline.UseFootnotes(); + + if (Autolinks) + pipeline = pipeline.UseAutoLinks(); + + if (CustomContainers) + pipeline = pipeline.UseCustomContainers(); + + if (BlockAttributes) + pipeline = pipeline.UseBlockAttributes(); + + return pipeline.Build(); + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 7de63fe09c..5f5b9acf56 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.Footnotes; using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Overlays; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -36,6 +38,10 @@ namespace osu.Game.Graphics.Containers.Markdown Text = codeInline.Content }); + protected override void AddFootnoteLink(FootnoteLink footnoteLink) => AddDrawable(new OsuMarkdownFootnoteLink(footnoteLink)); + + protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) => AddDrawable(new OsuMarkdownFootnoteBacklink(footnoteBacklink)); + protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 6ec393df4b..ce50dbdc39 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.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.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -46,5 +47,8 @@ namespace osu.Game.Graphics.Containers AddInternal(content); Add(CreateHoverSounds(sampleSet)); } + + protected override void ClearInternal(bool disposeChildren = true) => + throw new InvalidOperationException($"Clearing {nameof(InternalChildren)} will cause critical failure. Use {nameof(Clear)} instead."); } } diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 4f56872f42..7921dcf593 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -114,8 +114,7 @@ namespace osu.Game.Graphics.UserInterface get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 1114c29afd..a65204e6b3 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -277,7 +277,7 @@ namespace osu.Game.Graphics.UserInterface { var samples = sampleMap[feedbackSampleType]; - if (samples == null || samples.Length == 0) + if (samples.Length == 0) return null; return samples[RNG.Next(0, samples.Length)]?.GetChannel(); diff --git a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs index 8e6c3e5f3d..d47f936eb3 100644 --- a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs +++ b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs @@ -23,8 +23,7 @@ namespace osu.Game.IO.FileAbstraction public void CloseStream(Stream stream) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); stream.Close(); } diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 176e4e240d..de25d3e30e 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -63,7 +63,7 @@ namespace osu.Game.IO.Serialization.Converters throw new JsonException("Expected $type token."); string typeName = lookupTable[(int)tok["$type"]]; - var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull()); + var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull())!; serializer.Populate(itemReader, instance); list.Add(instance); diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index e20ffc0808..6997f04f44 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -27,7 +27,7 @@ namespace osu.Game.Models public bool IsBot => false; - public bool Equals(RealmUser other) + public bool Equals(RealmUser? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 176f10975d..45128375ab 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -39,7 +39,7 @@ namespace osu.Game.Online.API foreach (var (_, property) in mod.GetSettingsSourceProperties()) { - var bindable = (IBindable)property.GetValue(mod); + var bindable = (IBindable)property.GetValue(mod)!; if (!bindable.IsDefault) Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); @@ -60,16 +60,16 @@ namespace osu.Game.Online.API { foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) { - if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) + if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object? settingValue)) continue; try { - resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod)!, settingValue); } catch (Exception ex) { - Logger.Log($"Failed to copy mod setting value '{settingValue ?? "null"}' to \"{property.Name}\": {ex.Message}"); + Logger.Log($"Failed to copy mod setting value '{settingValue}' to \"{property.Name}\": {ex.Message}"); } } } @@ -79,7 +79,7 @@ namespace osu.Game.Online.API public bool ShouldSerializeSettings() => Settings.Count > 0; - public bool Equals(APIMod other) + public bool Equals(APIMod? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 8a77801c3a..27ad2a746c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -143,7 +143,7 @@ namespace osu.Game.Online.API.Requests.Responses public bool Equals(IRulesetInfo? other) => other is APIRuleset r && this.MatchesOnlineID(r); - public int CompareTo(IRulesetInfo other) + public int CompareTo(IRulesetInfo? other) { if (!(other is APIRuleset ruleset)) throw new ArgumentException($@"Object is not of type {nameof(APIRuleset)}.", nameof(other)); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index a4661dcbd7..5d55374373 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat /// public void OpenChannel(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); + ArgumentNullException.ThrowIfNull(name); CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name); } @@ -130,8 +129,7 @@ namespace osu.Game.Online.Chat /// The user the private channel is opened with. public void OpenPrivateChannel(APIUser user) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); if (user.Id == api.LocalUser.Value.Id) return; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 7a040d9446..b9bca9dc20 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Chat { int index = m.Index - captureOffset; - string? displayText = string.Format(display, + string displayText = string.Format(display, m.Groups[0], m.Groups["text"].Value, m.Groups["url"].Value).Trim(); @@ -109,7 +109,7 @@ namespace osu.Game.Online.Chat foreach (Match m in regex.Matches(result.Text, startIndex)) { int index = m.Index; - string? linkText = m.Groups["link"].Value; + string linkText = m.Groups["link"].Value; int indexLength = linkText.Length; var details = GetLinkDetails(linkText); @@ -125,7 +125,7 @@ namespace osu.Game.Online.Chat public static LinkDetails GetLinkDetails(string url) { - string[]? args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); + string[] args = url.Split('/', StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); switch (args[0]) @@ -362,6 +362,6 @@ namespace osu.Game.Online.Chat public bool Overlaps(Link otherLink) => Index < otherLink.Index + otherLink.Length && otherLink.Index < Index + Length; - public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; + public int CompareTo(Link? otherLink) => Index > otherLink?.Index ? 1 : -1; } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 4872d93467..9b2ad666b2 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; using osu.Framework.Allocation; @@ -61,12 +62,16 @@ namespace osu.Game.Online.Chat switch (e.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(e.NewItems != null); + foreach (var channel in e.NewItems.Cast()) channel.NewMessagesArrived += checkNewMessages; break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(e.OldItems != null); + foreach (var channel in e.OldItems.Cast()) channel.NewMessagesArrived -= checkNewMessages; diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index ca6d2932f7..8fd79bd703 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -59,17 +60,21 @@ namespace osu.Game.Online var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => { - // Use HttpClient.DefaultProxy once on net6 everywhere. - // The credential setter can also be removed at this point. - options.Proxy = WebRequest.DefaultWebProxy; - if (options.Proxy != null) - options.Proxy.Credentials = CredentialCache.DefaultCredentials; + // Configuring proxies is not supported on iOS, see https://github.com/xamarin/xamarin-macios/issues/14632. + if (RuntimeInfo.OS != RuntimeInfo.Platform.iOS) + { + // Use HttpClient.DefaultProxy once on net6 everywhere. + // The credential setter can also be removed at this point. + options.Proxy = WebRequest.DefaultWebProxy; + if (options.Proxy != null) + options.Proxy.Credentials = CredentialCache.DefaultCredentials; + } options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); options.Headers.Add("OsuVersionHash", versionHash); }); - if (RuntimeInfo.SupportsJIT && preferMessagePack) + if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack) { builder.AddMessagePackProtocol(options => { diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index ba7ccb24f7..57311419f7 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -68,7 +68,7 @@ namespace osu.Game.Online.Metadata while (true) { Logger.Log($"Requesting catch-up from {lastQueueId.Value}"); - var catchUpChanges = await GetChangesSince(lastQueueId.Value); + var catchUpChanges = await GetChangesSince(lastQueueId.Value).ConfigureAwait(true); lastQueueId.Value = catchUpChanges.LastProcessedQueueID; @@ -78,7 +78,7 @@ namespace osu.Game.Online.Metadata break; } - await ProcessChanges(catchUpChanges.BeatmapSetIDs); + await ProcessChanges(catchUpChanges.BeatmapSetIDs).ConfigureAwait(true); } } catch (Exception e) @@ -101,7 +101,7 @@ namespace osu.Game.Online.Metadata if (!catchingUp) lastQueueId.Value = updates.LastProcessedQueueID; - await ProcessChanges(updates.BeatmapSetIDs); + await ProcessChanges(updates.BeatmapSetIDs).ConfigureAwait(false); } public override Task GetChangesSince(int queueId) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index bd87f2d43e..2be7327234 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -822,7 +822,7 @@ namespace osu.Game.Online.Multiplayer { if (cancellationToken.IsCancellationRequested) { - tcs.SetCanceled(); + tcs.SetCanceled(cancellationToken); return; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs index 681a839b89..d70a2797c4 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerRoomUser.cs @@ -54,10 +54,10 @@ namespace osu.Game.Online.Multiplayer return UserID == other.UserID; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; + if (obj?.GetType() != GetType()) return false; return Equals((MultiplayerRoomUser)obj); } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 386a3d5262..8ff0ce4065 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.Multiplayer try { - return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); + return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty).ConfigureAwait(false); } catch (HubException exception) { @@ -88,8 +88,8 @@ namespace osu.Game.Online.Multiplayer { Debug.Assert(connector != null); - await connector.Reconnect(); - return await JoinRoom(roomId, password); + await connector.Reconnect().ConfigureAwait(false); + return await JoinRoom(roomId, password).ConfigureAwait(false); } throw; diff --git a/osu.Game/Online/Notifications/NotificationsClientConnector.cs b/osu.Game/Online/Notifications/NotificationsClientConnector.cs index d2c2e6673c..34ce186cb8 100644 --- a/osu.Game/Online/Notifications/NotificationsClientConnector.cs +++ b/osu.Game/Online/Notifications/NotificationsClientConnector.cs @@ -27,7 +27,7 @@ namespace osu.Game.Online.Notifications protected sealed override async Task BuildConnectionAsync(CancellationToken cancellationToken) { - var client = await BuildNotificationClientAsync(cancellationToken); + var client = await BuildNotificationClientAsync(cancellationToken).ConfigureAwait(false); client.ChannelJoined = c => ChannelJoined?.Invoke(c); client.ChannelParted = c => ChannelParted?.Invoke(c); diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs index d8d78297e3..73e5dcec6f 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClient.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Diagnostics; +using System.Net; using System.Net.WebSockets; using System.Text; using System.Threading; @@ -36,11 +37,11 @@ namespace osu.Game.Online.Notifications.WebSocket public override async Task ConnectAsync(CancellationToken cancellationToken) { await socket.ConnectAsync(new Uri(endpoint), cancellationToken).ConfigureAwait(false); - await sendMessage(new StartChatRequest(), CancellationToken.None); + await sendMessage(new StartChatRequest(), CancellationToken.None).ConfigureAwait(false); runReadLoop(cancellationToken); - await base.ConnectAsync(cancellationToken); + await base.ConnectAsync(cancellationToken).ConfigureAwait(false); } private void runReadLoop(CancellationToken cancellationToken) => Task.Run(async () => @@ -52,7 +53,7 @@ namespace osu.Game.Online.Notifications.WebSocket { try { - WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken); + WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, cancellationToken).ConfigureAwait(false); switch (result.MessageType) { @@ -72,7 +73,7 @@ namespace osu.Game.Online.Notifications.WebSocket break; } - await onMessageReceivedAsync(message); + await onMessageReceivedAsync(message).ConfigureAwait(false); } break; @@ -81,12 +82,12 @@ namespace osu.Game.Online.Notifications.WebSocket throw new NotImplementedException("Binary message type not supported."); case WebSocketMessageType.Close: - throw new Exception("Connection closed by remote host."); + throw new WebException("Connection closed by remote host."); } } catch (Exception ex) { - await InvokeClosed(ex); + await InvokeClosed(ex).ConfigureAwait(false); return; } } @@ -109,7 +110,7 @@ namespace osu.Game.Online.Notifications.WebSocket if (socket.State != WebSocketState.Open) return; - await socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken); + await socket.SendAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message)), WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); } private async Task onMessageReceivedAsync(SocketMessage message) @@ -141,7 +142,7 @@ namespace osu.Game.Online.Notifications.WebSocket Debug.Assert(messageData != null); foreach (var msg in messageData.Messages) - HandleChannelJoined(await getChannel(msg.ChannelId)); + HandleChannelJoined(await getChannel(msg.ChannelId).ConfigureAwait(false)); HandleMessages(messageData.Messages); break; @@ -150,7 +151,7 @@ namespace osu.Game.Online.Notifications.WebSocket private async Task getChannel(long channelId) { - if (channelsMap.TryGetValue(channelId, out Channel channel)) + if (channelsMap.TryGetValue(channelId, out Channel? channel)) return channel; var tsc = new TaskCompletionSource(); @@ -166,13 +167,13 @@ namespace osu.Game.Online.Notifications.WebSocket API.Queue(req); - return await tsc.Task; + return await tsc.Task.ConfigureAwait(false); } public override async ValueTask DisposeAsync() { - await base.DisposeAsync(); - await closeAsync(); + await base.DisposeAsync().ConfigureAwait(false); + await closeAsync().ConfigureAwait(false); socket.Dispose(); } } diff --git a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs index 21335a3b59..f50369a06c 100644 --- a/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs +++ b/osu.Game/Online/Notifications/WebSocket/WebSocketNotificationsClientConnector.cs @@ -32,7 +32,7 @@ namespace osu.Game.Online.Notifications.WebSocket req.Failure += ex => tcs.SetException(ex); api.Queue(req); - string endpoint = await tcs.Task; + string endpoint = await tcs.Task.ConfigureAwait(false); ClientWebSocket socket = new ClientWebSocket(); socket.Options.SetRequestHeader(@"Authorization", @$"Bearer {api.AccessToken}"); diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 55e9190f99..e33924047d 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -65,11 +65,11 @@ namespace osu.Game.Online { case APIState.Failing: case APIState.Offline: - await disconnect(true); + await disconnect(true).ConfigureAwait(true); break; case APIState.Online: - await connect(); + await connect().ConfigureAwait(true); break; } } @@ -147,7 +147,7 @@ namespace osu.Game.Online { bool hasBeenCancelled = cancellationToken.IsCancellationRequested; - await disconnect(true); + await disconnect(true).ConfigureAwait(false); if (ex != null) await handleErrorAndDelay(ex, CancellationToken.None).ConfigureAwait(false); diff --git a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs index a999cb47f8..86708bee82 100644 --- a/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs +++ b/osu.Game/Online/SignalRDerivedTypeWorkaroundJsonConverter.cs @@ -33,7 +33,8 @@ namespace osu.Game.Online object? instance = Activator.CreateInstance(resolvedType); - jsonSerializer.Populate(obj["$value"]!.CreateReader(), instance); + if (instance != null) + jsonSerializer.Populate(obj["$value"]!.CreateReader(), instance); return instance; } diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index ccba280001..605ebc4ef0 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -32,5 +32,12 @@ namespace osu.Game.Online.Spectator /// The user. /// The frame data. Task UserSentFrames(int userId, FrameDataBundle data); + + /// + /// Signals that a user's submitted score was fully processed. + /// + /// The ID of the user who achieved the score. + /// The ID of the score. + Task UserScoreProcessed(int userId, long scoreId); } } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index d5c1e56761..3118e05053 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -41,6 +41,7 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed); }; IsConnected.BindTo(connector.IsConnected); @@ -56,7 +57,7 @@ namespace osu.Game.Online.Spectator try { - await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), scoreToken, state); + await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), scoreToken, state).ConfigureAwait(false); } catch (Exception exception) { @@ -64,8 +65,8 @@ namespace osu.Game.Online.Spectator { Debug.Assert(connector != null); - await connector.Reconnect(); - await BeginPlayingInternal(scoreToken, state); + await connector.Reconnect().ConfigureAwait(false); + await BeginPlayingInternal(scoreToken, state).ConfigureAwait(false); } // Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart. diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fce61c019b..b60cef2835 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -64,6 +64,11 @@ namespace osu.Game.Online.Spectator /// public virtual event Action? OnUserFinishedPlaying; + /// + /// Called whenever a user-submitted score has been fully processed. + /// + public virtual event Action? OnUserScoreProcessed; + /// /// A dictionary containing all users currently being watched, with the number of watching components for each user. /// @@ -160,6 +165,13 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } + Task ISpectatorClient.UserScoreProcessed(int userId, long scoreId) + { + Schedule(() => OnUserScoreProcessed?.Invoke(userId, scoreId)); + + return Task.CompletedTask; + } + public void BeginPlaying(long? scoreToken, GameplayState state, Score score) { // This schedule is only here to match the one below in `EndPlaying`. diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs index 75b6a6e83b..1c505ea107 100644 --- a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -184,7 +184,7 @@ namespace osu.Game.Online.Spectator Header = header; } - public int CompareTo(TimedFrame other) => Time.CompareTo(other.Time); + public int CompareTo(TimedFrame? other) => Time.CompareTo(other?.Time); } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8fb6806c27..de9a009f44 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -16,6 +16,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; @@ -332,7 +333,7 @@ namespace osu.Game /// The link to load. public void HandleLink(LinkDetails link) => Schedule(() => { - string argString = link.Argument.ToString(); + string argString = link.Argument.ToString() ?? string.Empty; switch (link.Action) { @@ -406,6 +407,16 @@ namespace osu.Game if (url.StartsWith('/')) url = $"{API.APIEndpointUrl}{url}"; + if (!url.CheckIsValidUrl()) + { + Notifications.Post(new SimpleErrorNotification + { + Text = $"The URL {url} has an unsupported or dangerous protocol and will not be opened.", + }); + + return; + } + externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); }); @@ -1029,7 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return; + if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return; const int short_term_display_limit = 3; @@ -1043,7 +1054,7 @@ namespace osu.Game } else if (recentLogCount == short_term_display_limit) { - string logFile = $@"{entry.Target.ToString().ToLowerInvariant()}.log"; + string logFile = $@"{entry.Target.Value.ToString().ToLowerInvariant()}.log"; Schedule(() => Notifications.Post(new SimpleNotification { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 90863a90a2..671d649dcf 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays /// are specified, the header will instantly display them. public void ShowBuild([NotNull] APIChangelogBuild build) { - if (build == null) throw new ArgumentNullException(nameof(build)); + ArgumentNullException.ThrowIfNull(build); Current.Value = build; Show(); @@ -78,8 +78,8 @@ namespace osu.Game.Overlays public void ShowBuild([NotNull] string updateStream, [NotNull] string version) { - if (updateStream == null) throw new ArgumentNullException(nameof(updateStream)); - if (version == null) throw new ArgumentNullException(nameof(version)); + ArgumentNullException.ThrowIfNull(updateStream); + ArgumentNullException.ThrowIfNull(version); performAfterFetch(() => { diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 98e153108f..c539cdc5ec 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -352,11 +352,13 @@ namespace osu.Game.Overlays protected virtual DrawableChannel CreateDrawableChannel(Channel newChannel) => new DrawableChannel(newChannel); - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + private void joinedChannelsChanged(object? sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + IEnumerable newChannels = args.NewItems.OfType().Where(isChatChannel); foreach (var channel in newChannels) @@ -365,6 +367,8 @@ namespace osu.Game.Overlays break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + IEnumerable leftChannels = args.OldItems.OfType().Where(isChatChannel); foreach (var channel in leftChannels) @@ -384,7 +388,7 @@ namespace osu.Game.Overlays } } - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + private void availableChannelsChanged(object? sender, NotifyCollectionChangedEventArgs args) => channelListing.UpdateAvailableChannels(channelManager.AvailableChannels); private void handleChatMessage(string message) diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index 664946fc63..9cc20caa05 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -11,7 +11,10 @@ namespace osu.Game.Overlays.Comments { public partial class CommentMarkdownContainer : OsuMarkdownContainer { - protected override bool Autolinks => true; + protected override OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions + { + Autolinks = true + }; protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock); diff --git a/osu.Game/Overlays/Comments/CommentReportButton.cs b/osu.Game/Overlays/Comments/CommentReportButton.cs index 10bd3a64bf..ba5319094b 100644 --- a/osu.Game/Overlays/Comments/CommentReportButton.cs +++ b/osu.Game/Overlays/Comments/CommentReportButton.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.Comments } }; - link.AddLink(UsersStrings.ReportButtonText, this.ShowPopover); + link.AddLink(ReportStrings.CommentButton.ToLower(), this.ShowPopover); } private void report(CommentReportReason reason, string comments) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7bada5ef2a..834abd5e9f 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -19,6 +19,8 @@ using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using System.Diagnostics; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Game.Graphics.UserInterface; @@ -335,7 +337,7 @@ namespace osu.Game.Overlays.Comments actionsContainer.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); if (Comment.UserId.HasValue && Comment.UserId.Value == api.LocalUser.Value.Id) - actionsContainer.AddLink(CommonStrings.ButtonsDelete, deleteComment); + actionsContainer.AddLink(CommonStrings.ButtonsDelete.ToLower(), deleteComment); else actionsContainer.AddArbitraryDrawable(new CommentReportButton(Comment)); @@ -359,6 +361,8 @@ namespace osu.Game.Overlays.Comments switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + onRepliesAdded(args.NewItems.Cast()); break; diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 04aa976ff1..8b85bb49a5 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -126,7 +126,8 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { - copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + copyInformation.Text = + "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; @@ -173,6 +174,18 @@ namespace osu.Game.Overlays.FirstRunSetup c.Current.Disabled = !allow; } + public override void OnSuspending(ScreenTransitionEvent e) + { + stableLocatorTextBox.HidePopover(); + base.OnSuspending(e); + } + + public override bool OnExiting(ScreenExitEvent e) + { + stableLocatorTextBox.HidePopover(); + return base.OnExiting(e); + } + private partial class ImportCheckbox : SettingsCheckbox { public readonly StableContent StableContent; diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 63688841d0..1bcb1bcdf4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; @@ -58,7 +57,7 @@ namespace osu.Game.Overlays.FirstRunSetup Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.None, - Size = new Vector2(screen_width, screen_width / 16f * 9 / 2), + Size = new Vector2(screen_width, screen_width / 16f * 9), Children = new Drawable[] { new GridContainer @@ -68,7 +67,6 @@ namespace osu.Game.Overlays.FirstRunSetup { new Drawable[] { - new SampleScreenContainer(new PinnedMainMenu()), new SampleScreenContainer(new NestedSongSelect()), }, // TODO: add more screens here in the future (gameplay / results) @@ -109,17 +107,6 @@ namespace osu.Game.Overlays.FirstRunSetup public override bool? AllowTrackAdjustments => false; } - private partial class PinnedMainMenu : MainMenu - { - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - Buttons.ReturnToTopOnIdle = false; - Buttons.State = ButtonSystemState.TopLevel; - } - } - private partial class UIScaleSlider : OsuSliderBar { public override LocalisableString TooltipText => base.TooltipText + "x"; diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index fe3aaeb9d8..4af40e5ad6 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -87,18 +87,21 @@ namespace osu.Game.Overlays.FirstRunSetup }); frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + frameworkLocale.BindValueChanged(_ => onLanguageChange()); localisationParameters = localisation.CurrentParameters.GetBoundCopy(); - localisationParameters.BindValueChanged(p => - { - var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue); + localisationParameters.BindValueChanged(_ => onLanguageChange(), true); + } - // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. - // Scheduling ensures the button animation plays smoothly after any blocking operation completes. - // Note that a delay is required (the alternative would be a double-schedule; delay feels better). - updateSelectedDelegate?.Cancel(); - updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); - }, true); + private void onLanguageChange() + { + var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); + + // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. + // Scheduling ensures the button animation plays smoothly after any blocking operation completes. + // Note that a delay is required (the alternative would be a double-schedule; delay feels better). + updateSelectedDelegate?.Cancel(); + updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); } private void updateSelectedStates(Language language) @@ -147,7 +150,7 @@ namespace osu.Game.Overlays.FirstRunSetup [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRange(new Drawable[] { backgroundBox = new Box { @@ -162,7 +165,7 @@ namespace osu.Game.Overlays.FirstRunSetup Colour = colourProvider.Light1, Text = Language.GetDescription(), } - }; + }); } protected override void LoadComplete() diff --git a/osu.Game/Overlays/FirstRunSetupOverlay.cs b/osu.Game/Overlays/FirstRunSetupOverlay.cs index 45fc9d27ea..f2fdaefbb4 100644 --- a/osu.Game/Overlays/FirstRunSetupOverlay.cs +++ b/osu.Game/Overlays/FirstRunSetupOverlay.cs @@ -301,7 +301,7 @@ namespace osu.Game.Overlays if (currentStepIndex < steps.Count) { - var nextScreen = (Screen)Activator.CreateInstance(steps[currentStepIndex.Value]); + var nextScreen = (Screen)Activator.CreateInstance(steps[currentStepIndex.Value])!; loadingShowDelegate = Scheduler.AddDelayed(() => loading.Show(), 200); nextScreen.OnLoadComplete += _ => diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index d60077cfa9..4f2dba7b2c 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays /// If is already being tracked from the same . public void BeginTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (trackedConfigManagers.ContainsKey((source, configManager))) throw new InvalidOperationException($"{nameof(configManager)} is already registered."); @@ -82,7 +82,7 @@ namespace osu.Game.Overlays /// If is not being tracked from the same . public void StopTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) return; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index 414c6f194a..a31ee88f3b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -131,8 +130,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { var metadata = beatmapInfo.Metadata; - Debug.Assert(metadata != null); - return new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index f1f3ecd4fb..67df4825cc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -269,8 +268,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { var metadata = beatmapInfo.Metadata; - Debug.Assert(metadata != null); - return new Drawable[] { new OsuSpriteText diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 24dec44588..9d5e5db6e6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -7,18 +7,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Overlays { - public partial class RestoreDefaultValueButton : OsuButton, IHasTooltip, IHasCurrentValue + public partial class RestoreDefaultValueButton : OsuClickableContainer, IHasCurrentValue { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -51,15 +53,32 @@ namespace osu.Game.Overlays private const float size = 4; + private CircularContainer circle = null!; + private Box background = null!; + + public RestoreDefaultValueButton() + : base(HoverSampleSet.Button) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colour) { - BackgroundColour = colour.Lime1; + // size intentionally much larger than actual drawn content, so that the button is easier to click. Size = new Vector2(3 * size); - Content.RelativeSizeAxes = Axes.None; - Content.Size = new Vector2(size); - Content.CornerRadius = size / 2; + Add(circle = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour.Lime1 + } + }); Alpha = 0f; @@ -77,7 +96,7 @@ namespace osu.Game.Overlays FinishTransforms(true); } - public LocalisableString TooltipText => "revert to default"; + public override LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { @@ -104,8 +123,8 @@ namespace osu.Game.Overlays if (!Current.Disabled) { this.FadeTo(Current.IsDefault ? 0 : 1, fade_duration, Easing.OutQuint); - Background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = (IsHovered ? colours.Lime1 : colours.Lime3).Opacity(0.4f), Radius = IsHovered ? 8 : 4, @@ -114,8 +133,8 @@ namespace osu.Game.Overlays } else { - Background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = colours.Lime3.Opacity(0.1f), Radius = 2, diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index a4ec919658..982cbec376 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -44,10 +44,13 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - localisationParameters.BindValueChanged(p - => languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue), true); + frameworkLocale.BindValueChanged(_ => updateSelection()); + localisationParameters.BindValueChanged(_ => updateSelection(), true); languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } + + private void updateSelection() => + languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); } } diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs index dcdb15fc19..19f0d0f7d1 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs @@ -3,7 +3,6 @@ #nullable disable -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -29,8 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input var r = ruleset.CreateInstance(); - Debug.Assert(r != null); - foreach (int variant in r.AvailableVariants) Add(new VariantBindingsSubsection(ruleset, variant)); } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 826a1e7404..f75656cc99 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections dropdownItems.Clear(); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_SKIN).ToLive(realm)); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_PRO_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.TRIANGLES_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 3bea1c840e..9cc346a38b 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -28,8 +28,7 @@ namespace osu.Game.Overlays.Volume get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 4ef9be90c9..7c36caa62f 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -16,8 +16,12 @@ namespace osu.Game.Overlays.Wiki.Markdown { public partial class WikiMarkdownContainer : OsuMarkdownContainer { - protected override bool Footnotes => true; - protected override bool CustomContainers => true; + protected override OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions + { + Footnotes = true, + CustomContainers = true, + BlockAttributes = true + }; public string CurrentPath { diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9d9c10b3ea..38ced4c9e7 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Mods get => this; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); if (currentBound != null) UnbindFrom(currentBound); BindTo(currentBound = value); diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 1f9e26c9d7..05b2510e53 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -55,6 +55,6 @@ namespace osu.Game.Rulesets.Mods /// /// Create a fresh instance based on this mod. /// - Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType()); + Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType())!; } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 98df540de4..04d55bc5fe 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mods foreach ((SettingSourceAttribute attr, PropertyInfo property) in this.GetOrderedSettingsSourceProperties()) { - var bindable = (IBindable)property.GetValue(this); + var bindable = (IBindable)property.GetValue(this)!; if (!bindable.IsDefault) tooltipTexts.Add($"{attr.Label} {bindable}"); @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mods /// public virtual Mod DeepClone() { - var result = (Mod)Activator.CreateInstance(GetType()); + var result = (Mod)Activator.CreateInstance(GetType())!; result.CopyFrom(this); return result; } @@ -150,8 +150,8 @@ namespace osu.Game.Rulesets.Mods foreach (var (_, prop) in this.GetSettingsSourceProperties()) { - var targetBindable = (IBindable)prop.GetValue(this); - var sourceBindable = (IBindable)prop.GetValue(source); + var targetBindable = (IBindable)prop.GetValue(this)!; + var sourceBindable = (IBindable)prop.GetValue(source)!; CopyAdjustedSetting(targetBindable, sourceBindable); } @@ -183,9 +183,9 @@ namespace osu.Game.Rulesets.Mods } } - public bool Equals(IMod other) => other is Mod them && Equals(them); + public bool Equals(IMod? other) => other is Mod them && Equals(them); - public bool Equals(Mod other) + public bool Equals(Mod? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; @@ -209,16 +209,16 @@ namespace osu.Game.Rulesets.Mods /// /// Reset all custom settings for this mod back to their defaults. /// - public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType())); + public virtual void ResetSettingsToDefaults() => CopyFrom((Mod)Activator.CreateInstance(GetType())!); private class ModSettingsEqualityComparer : IEqualityComparer { public static ModSettingsEqualityComparer Default { get; } = new ModSettingsEqualityComparer(); - public bool Equals(IBindable x, IBindable y) + public bool Equals(IBindable? x, IBindable? y) { - object xValue = x.GetUnderlyingSettingValue(); - object yValue = y.GetUnderlyingSettingValue(); + object? xValue = x?.GetUnderlyingSettingValue(); + object? yValue = y?.GetUnderlyingSettingValue(); return EqualityComparer.Default.Equals(xValue, yValue); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 096132d024..be5a7f71e7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -208,8 +208,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public void Apply([NotNull] HitObject hitObject) { - if (hitObject == null) - throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); + ArgumentNullException.ThrowIfNull(hitObject); Apply(new SyntheticHitObjectEntry(hitObject)); } diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index ddc121eb5b..c9d2928dc3 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -57,12 +58,16 @@ namespace osu.Game.Rulesets.Objects switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + foreach (var c in args.NewItems.Cast()) c.Changed += invalidate; break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + foreach (var c in args.OldItems.Cast()) c.Changed -= invalidate; break; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a446210c8a..a73151362b 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -89,6 +89,8 @@ namespace osu.Game.Rulesets // Confine all mods of each mod type into a single IEnumerable .SelectMany(GetModsFor) // Filter out all null mods + // This is to handle old rulesets which were doing mods bad. Can be removed at some point we are sure nulls will not appear here. + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract .Where(mod => mod != null) // Resolve MultiMods as their .Mods property .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 91954320a4..6a4e0f0b48 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -53,21 +53,21 @@ namespace osu.Game.Rulesets public bool Equals(IRulesetInfo? other) => other is RulesetInfo r && Equals(r); - public int CompareTo(RulesetInfo other) + public int CompareTo(RulesetInfo? other) { - if (OnlineID >= 0 && other.OnlineID >= 0) + if (OnlineID >= 0 && other?.OnlineID >= 0) return OnlineID.CompareTo(other.OnlineID); // Official rulesets are always given precedence for the time being. if (OnlineID >= 0) return -1; - if (other.OnlineID >= 0) + if (other?.OnlineID >= 0) return 1; - return string.Compare(ShortName, other.ShortName, StringComparison.Ordinal); + return string.Compare(ShortName, other?.ShortName, StringComparison.Ordinal); } - public int CompareTo(IRulesetInfo other) + public int CompareTo(IRulesetInfo? other) { if (!(other is RulesetInfo ruleset)) throw new ArgumentException($@"Object is not of type {nameof(RulesetInfo)}.", nameof(other)); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index e2b8cd2c4e..881b09bd1b 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -75,10 +75,7 @@ namespace osu.Game.Rulesets return false; return args.Name.Contains(name, StringComparison.Ordinal); - }) - // Pick the greatest assembly version. - .OrderByDescending(a => a.GetName().Version) - .FirstOrDefault(); + }).MaxBy(a => a.GetName().Version); if (domainAssembly != null) return domainAssembly; @@ -114,7 +111,10 @@ namespace osu.Game.Rulesets { try { - string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); + // On net6-android (Debug), StartupDirectory can be different from where assemblies are placed. + // Search sub-directories too. + + string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll", SearchOption.AllDirectories); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets private void loadRulesetFromFile(string file) { - string? filename = Path.GetFileNameWithoutExtension(file); + string filename = Path.GetFileNameWithoutExtension(file); if (LoadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename)) return; @@ -158,7 +158,7 @@ namespace osu.Game.Rulesets } catch (Exception e) { - LogFailedLoad(assembly.GetName().Name.Split('.').Last(), e); + LogFailedLoad(assembly.GetName().Name!.Split('.').Last(), e); } } diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index cdfe71943e..b70ddd5e24 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Scoring { foreach (var condition in FailConditions.GetInvocationList()) { - bool conditionResult = (bool)condition.Method.Invoke(condition.Target, new object[] { this, result }); + bool conditionResult = (bool)condition.Method.Invoke(condition.Target, new object[] { this, result })!; if (conditionResult) return true; } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b048566c8a..4228840461 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -534,7 +534,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicResult ??= ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result; break; } diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index d068f8d016..d2244df3b8 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -79,9 +79,7 @@ namespace osu.Game.Rulesets.UI // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. fallbackObject = hitObjectContainer.Entries - .Where(e => e.Result?.HasResult != true) - .OrderBy(e => e.HitObject.StartTime) - .FirstOrDefault(); + .Where(e => e.Result?.HasResult != true).MinBy(e => e.HitObject.StartTime); // In the case there are no unjudged objects, the last hit object should be used instead. fallbackObject ??= hitObjectContainer.Entries.LastOrDefault(); diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 8381e6d6b5..7181e80206 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.UI { public override void Add(T judgement) { - if (judgement == null) throw new ArgumentNullException(nameof(judgement)); + ArgumentNullException.ThrowIfNull(judgement); // remove any existing judgements for the judged object. // this can be the case when rewinding. diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 4656f02897..a3d7fe5de0 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -71,8 +71,8 @@ namespace osu.Game.Scoring // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. - if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); - if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + ArgumentNullException.ThrowIfNull(model.BeatmapInfo); + ArgumentNullException.ThrowIfNull(model.Ruleset); PopulateMaximumStatistics(model); @@ -101,8 +101,7 @@ namespace osu.Game.Scoring // Populate the maximum statistics. HitResult maxBasicResult = rulesetInstance.GetHitResults() .Select(h => h.result) - .Where(h => h.IsBasic()) - .OrderByDescending(Judgement.ToNumericResult).First(); + .Where(h => h.IsBasic()).MaxBy(Judgement.ToNumericResult); foreach ((HitResult result, int count) in score.Statistics) { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a9ced62c95..1009474d89 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -317,7 +317,7 @@ namespace osu.Game.Scoring #endregion - public bool Equals(ScoreInfo other) => other.ID == ID; + public bool Equals(ScoreInfo? other) => other?.ID == ID; public override string ToString() => this.GetDisplayTitle(); } diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 514b7a57de..c5434e8faf 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -17,7 +17,7 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); - public override ArchiveDownloadRequest GetExistingDownload(IScoreInfo model) + public override ArchiveDownloadRequest? GetExistingDownload(IScoreInfo model) => CurrentDownloads.Find(r => r.Model.MatchesOnlineID(model)); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 2d26e6f90b..111ba0b732 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; @@ -33,6 +34,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts break; case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + foreach (var group in args.NewItems.OfType()) { // as an optimisation, don't add a visualisation if there are already groups with the same types in close proximity. @@ -47,6 +50,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + foreach (var group in args.OldItems.OfType()) { var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 903c117422..9f422d5aa9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -479,7 +479,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // copied from SliderBar so we can do custom spacing logic. float xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; - CurrentNumber.Value = beatDivisor.ValidDivisors.Value.Presets.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); + CurrentNumber.Value = beatDivisor.ValidDivisors.Value.Presets.MinBy(d => Math.Abs(getMappedPosition(d) - xPosition)); OnUserChange(Current.Value); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 60fec5bcc6..77892f21e6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -58,6 +58,8 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + foreach (object o in args.NewItems) { if (blueprintMap.TryGetValue((T)o, out var blueprint)) @@ -67,6 +69,8 @@ namespace osu.Game.Screens.Edit.Compose.Components break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + foreach (object o in args.OldItems) { if (blueprintMap.TryGetValue((T)o, out var blueprint)) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 9783c4184a..29983c9cbf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; @@ -33,11 +34,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + foreach (var group in args.NewItems.OfType()) Add(new TimelineControlPointGroup(group)); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + foreach (var group in args.OldItems.OfType()) { var matching = Children.SingleOrDefault(gv => ReferenceEquals(gv.Group, group)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 28f7731354..951f4129d4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -99,9 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline minZoom = minimum; maxZoom = maximum; - CurrentZoom = zoomTarget = initial; - isZoomSetUp = true; + CurrentZoom = zoomTarget = initial; + zoomedContentWidthCache.Invalidate(); + + isZoomSetUp = true; zoomedContent.Show(); } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 3af7a400e2..dc026f7eac 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose if (composer == null) return string.Empty; - double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; + double displayTime = EditorBeatmap.SelectedHitObjects.MinBy(h => h.StartTime)?.StartTime ?? clock.CurrentTime; string selectionAsString = composer.ConvertSelectionToString(); return !string.IsNullOrEmpty(selectionAsString) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index e204b44db3..1554bf1bb1 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -304,7 +304,7 @@ namespace osu.Game.Screens.Edit /// The index of the to remove. public void RemoveAt(int index) { - var hitObject = (HitObject)mutableHitObjects[index]; + HitObject hitObject = (HitObject)mutableHitObjects[index]!; mutableHitObjects.RemoveAt(index); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c67850bdf6..5000a97b3d 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu private void addAmplitudesFromSource(IHasAmplitudes source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + ArgumentNullException.ThrowIfNull(source); var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes.Span; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs index 3b66355dab..c31633eefc 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoomParticipantsList.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -197,11 +198,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components switch (e.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(e.NewItems != null); + foreach (var added in e.NewItems.OfType()) addUser(added); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(e.OldItems != null); + foreach (var removed in e.OldItems.OfType()) removeUser(removed); break; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index e723dfe3e6..ac6403bb34 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -117,10 +118,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components switch (args.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(args.NewItems != null); + addRooms(args.NewItems.Cast()); break; case NotifyCollectionChangedAction.Remove: + Debug.Assert(args.OldItems != null); + removeRooms(args.OldItems.Cast()); break; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index fe27e2cf20..2d2aa0f1d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// /// Whether all spectating players have finished loading. /// - public bool AllPlayersLoaded => instances.All(p => p?.PlayerLoaded == true); + public bool AllPlayersLoaded => instances.All(p => p.PlayerLoaded); protected override UserActivity InitialActivity => new UserActivity.SpectatingMultiplayerGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); @@ -171,9 +171,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!isCandidateAudioSource(currentAudioSource?.SpectatorPlayerClock)) { - currentAudioSource = instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)) - .OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)) - .FirstOrDefault(); + currentAudioSource = instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)).MinBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)); // Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio. if (currentAudioSource != null) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 50a9992f4f..e93f56c2e2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -349,7 +349,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public void SelectBeatmap() => editPlaylistButton.TriggerClick(); - private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) => + private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 3b50a22e3c..8b2b8f9464 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -33,8 +33,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs index 23030e640b..38027c64ac 100644 --- a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs @@ -30,8 +30,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs index 2743173a6d..620f3718c2 100644 --- a/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/MultiplayerGameplayLeaderboard.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using System.Threading; using osu.Framework.Allocation; @@ -153,11 +154,13 @@ namespace osu.Game.Screens.Play.HUD } } - private void playingUsersChanged(object sender, NotifyCollectionChangedEventArgs e) + private void playingUsersChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Remove: + Debug.Assert(e.OldItems != null); + foreach (int userId in e.OldItems.OfType()) { spectatorClient.StopWatchingUser(userId); diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index 6d63776dbb..da759b4329 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Play.HUD foreach (var (_, property) in component.GetSettingsSourceProperties()) { - var bindable = (IBindable)property.GetValue(component); + var bindable = (IBindable)property.GetValue(component)!; if (!bindable.IsDefault) Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); @@ -88,7 +88,7 @@ namespace osu.Game.Screens.Play.HUD { try { - Drawable d = (Drawable)Activator.CreateInstance(Type); + Drawable d = (Drawable)Activator.CreateInstance(Type)!; d.ApplySkinnableInfo(this); return d; } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index d9ad3cfaf7..bb50d4a539 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play public override void Add(KeyCounter key) { - if (key == null) throw new ArgumentNullException(nameof(key)); + ArgumentNullException.ThrowIfNull(key); base.Add(key); key.IsCounting = IsCounting; diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 4c507f8c67..5fa6508a31 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; @@ -130,6 +131,7 @@ namespace osu.Game.Screens.Play score.ScoreInfo.Date = DateTimeOffset.Now; await submitScore(score).ConfigureAwait(false); + spectatorClient.EndPlaying(GameplayState); } [Resolved] @@ -157,8 +159,11 @@ namespace osu.Game.Screens.Play if (LoadedBeatmapSuccessfully) { - submitScore(Score.DeepClone()); - spectatorClient.EndPlaying(GameplayState); + Task.Run(async () => + { + await submitScore(Score.DeepClone()).ConfigureAwait(false); + spectatorClient.EndPlaying(GameplayState); + }).FireAndForget(); } return exiting; diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 8ff6d3cf4f..212cebeaf5 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -259,10 +259,7 @@ namespace osu.Game.Screens.Utility var displayMode = host.Window?.CurrentDisplayMode.Value; - string exclusive = "unknown"; - - if (host.Renderer is IWindowsRenderer windowsRenderer) - exclusive = windowsRenderer.FullscreenCapability.ToString(); + string exclusive = (host.Renderer as IWindowsRenderer)?.FullscreenCapability.ToString() ?? "unknown"; statusText.Clear(); diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs new file mode 100644 index 0000000000..b753dd8fbe --- /dev/null +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Extensions; +using osu.Game.IO; + +namespace osu.Game.Skinning +{ + public class ArgonProSkin : ArgonSkin + { + public new static SkinInfo CreateInfo() => new SkinInfo + { + ID = Skinning.SkinInfo.ARGON_PRO_SKIN, + Name = "osu! \"argon\" pro (2022)", + Creator = "team osu!", + Protected = true, + InstantiationInfo = typeof(ArgonProSkin).GetInvariantInstantiationInfo() + }; + + public override ISample? GetSample(ISampleInfo sampleInfo) + { + foreach (string lookup in sampleInfo.LookupNames) + { + string remappedLookup = lookup.Replace(@"Gameplay/", @"Gameplay/ArgonPro/"); + + var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); + if (sample != null) + return sample; + } + + return null; + } + + public ArgonProSkin(IStorageResourceProvider resources) + : this(CreateInfo(), resources) + { + } + + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + public ArgonProSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin, resources) + { + } + } +} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6a0c4a23e5..d78147aaea 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning InstantiationInfo = typeof(ArgonSkin).GetInvariantInstantiationInfo() }; - private readonly IStorageResourceProvider resources; + protected readonly IStorageResourceProvider Resources; public ArgonSkin(IStorageResourceProvider resources) : this(CreateInfo(), resources) @@ -41,7 +41,7 @@ namespace osu.Game.Skinning public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { - this.resources = resources; + Resources = resources; Configuration.CustomComboColours = new List { @@ -72,7 +72,7 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - var sample = Samples?.Get(lookup) ?? resources.AudioManager?.Samples.Get(lookup); + var sample = Samples?.Get(lookup) ?? Resources.AudioManager?.Samples.Get(lookup); if (sample != null) return sample; } diff --git a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs index 8e9256214f..9812406aad 100644 --- a/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs +++ b/osu.Game/Skinning/Editor/SkinBlueprintContainer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -65,17 +66,24 @@ namespace osu.Game.Skinning.Editor switch (e.Action) { case NotifyCollectionChangedAction.Add: + Debug.Assert(e.NewItems != null); + foreach (var item in e.NewItems.Cast()) AddBlueprintFor(item); break; case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Reset: + Debug.Assert(e.OldItems != null); + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); break; case NotifyCollectionChangedAction.Replace: + Debug.Assert(e.NewItems != null); + Debug.Assert(e.OldItems != null); + foreach (var item in e.OldItems.Cast()) RemoveBlueprintFor(item); diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 68ac84df48..053119557f 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -59,9 +58,7 @@ namespace osu.Game.Skinning.Editor { try { - var instance = (Drawable)Activator.CreateInstance(type); - - Debug.Assert(instance != null); + Drawable instance = (Drawable)Activator.CreateInstance(type)!; if (!((ISkinnableDrawable)instance).IsEditable) return; diff --git a/osu.Game/Skinning/GameplaySkinComponentLookup.cs b/osu.Game/Skinning/GameplaySkinComponentLookup.cs index 2d1dec4691..9247ca0e4d 100644 --- a/osu.Game/Skinning/GameplaySkinComponentLookup.cs +++ b/osu.Game/Skinning/GameplaySkinComponentLookup.cs @@ -16,7 +16,7 @@ namespace osu.Game.Skinning } protected virtual string RulesetPrefix => string.Empty; - protected virtual string ComponentName => Component.ToString(); + protected virtual string ComponentName => Component.ToString() ?? string.Empty; public string LookupName => string.Join('/', new[] { "Gameplay", RulesetPrefix, ComponentName }.Where(s => !string.IsNullOrEmpty(s))); diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ea223d172d..5f12d2ce23 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -105,7 +105,7 @@ namespace osu.Game.Skinning return SkinUtils.As(GetComboColour(Configuration, comboColour.ColourIndex, comboColour.Combo)); case SkinCustomColourLookup customColour: - return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString())); + return SkinUtils.As(getCustomColour(Configuration, customColour.Lookup.ToString() ?? string.Empty)); case LegacyManiaSkinConfigurationLookup maniaLookup: if (!AllowManiaSkin) @@ -277,7 +277,7 @@ namespace osu.Game.Skinning => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; private IBindable? getManiaImage(LegacyManiaSkinConfiguration source, string lookup) - => source.ImageLookups.TryGetValue(lookup, out string image) ? new Bindable(image) : null; + => source.ImageLookups.TryGetValue(lookup, out string? image) ? new Bindable(image) : null; private IBindable? legacySettingLookup(SkinConfiguration.LegacySetting legacySetting) where TValue : notnull @@ -298,7 +298,7 @@ namespace osu.Game.Skinning { try { - if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString(), out string val)) + if (Configuration.ConfigDictionary.TryGetValue(lookup.ToString() ?? string.Empty, out string? val)) { // special case for handling skins which use 1 or 0 to signify a boolean state. // ..or in some cases 2 (https://github.com/ppy/osu/issues/18579). diff --git a/osu.Game/Skinning/RealmBackedResourceStore.cs b/osu.Game/Skinning/RealmBackedResourceStore.cs index 0cc90f1dcb..cc887a7a61 100644 --- a/osu.Game/Skinning/RealmBackedResourceStore.cs +++ b/osu.Game/Skinning/RealmBackedResourceStore.cs @@ -52,7 +52,7 @@ namespace osu.Game.Skinning private string? getPathForFile(string filename) { - if (fileToStoragePathMapping.Value.TryGetValue(filename.ToLowerInvariant(), out string path)) + if (fileToStoragePathMapping.Value.TryGetValue(filename.ToLowerInvariant(), out string? path)) return path; return null; diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index f594a7b2d2..1685562cc7 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Skinning protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == @".osk"; + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == @".osk"; protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name ?? @"No name" }; diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index d051149155..9ad91f8725 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -20,6 +20,7 @@ namespace osu.Game.Skinning { internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); internal static readonly Guid ARGON_SKIN = new Guid("CFFA69DE-B3E3-4DEE-8563-3C4F425C05D0"); + internal static readonly Guid ARGON_PRO_SKIN = new Guid("9FC9CF5D-0F16-4C71-8256-98868321AC43"); internal static readonly Guid CLASSIC_SKIN = new Guid("81F02CD3-EEC6-4865-AC23-FAE26A386187"); internal static readonly Guid RANDOM_SKIN = new Guid("D39DFEFB-477C-4372-B1EA-2BCEA5FB8908"); @@ -56,7 +57,7 @@ namespace osu.Game.Skinning return new TrianglesSkin(this, resources); } - return (Skin)Activator.CreateInstance(type, this, resources); + return (Skin)Activator.CreateInstance(type, this, resources)!; } public IList Files { get; } = null!; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2ad62dbb61..f750bfad8a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Skinning DefaultClassicSkin = new DefaultLegacySkin(this), trianglesSkin = new TrianglesSkin(this), argonSkin = new ArgonSkin(this), + new ArgonProSkin(this), }; // Ensure the default entries are present. diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1a8a3a26c9..a66f3e0549 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -111,6 +111,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) => skin.GetType() == typeof(TrianglesSkin) + || skin.GetType() == typeof(ArgonProSkin) || skin.GetType() == typeof(ArgonSkin) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index e86ee9e63d..e598c79b08 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -137,7 +137,7 @@ namespace osu.Game.Storyboards.Drawables // When reading from a skin, we match stables weird behaviour where `FrameCount` is ignored // and resources are retrieved until the end of the animation. - foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path), default, default, true, string.Empty, out _)) + foreach (var texture in skin.GetTextures(Path.GetFileNameWithoutExtension(Animation.Path)!, default, default, true, string.Empty, out _)) AddFrame(texture, Animation.FrameDelay); } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8133244e89..566e064aad 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -32,7 +32,7 @@ namespace osu.Game.Storyboards /// /// This iterates all elements and as such should be used sparingly or stored locally. /// - public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime; + public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).MinBy(e => e.StartTime)?.StartTime; /// /// Across all layers, find the latest point in time that a storyboard element ends at. @@ -42,7 +42,7 @@ namespace osu.Game.Storyboards /// This iterates all elements and as such should be used sparingly or stored locally. /// Videos and samples return StartTime as their EndTIme. /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.GetEndTime()).LastOrDefault()?.GetEndTime(); + public double? LatestEventTime => Layers.SelectMany(l => l.Elements).MaxBy(e => e.GetEndTime())?.GetEndTime(); /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index cd7788bb08..5b7b194be7 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -48,7 +48,7 @@ namespace osu.Game.Storyboards if (alphaCommands.Count > 0) { - var firstAlpha = alphaCommands.OrderBy(t => t.startTime).First(); + var firstAlpha = alphaCommands.MinBy(t => t.startTime); if (firstAlpha.isZeroStartValue) return firstAlpha.startTime; diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index c5f6f58b86..79f629ce49 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -185,7 +185,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - string localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); + string localPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs index c7441f68bd..16434406b5 100644 --- a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.IO; using System.Reflection; using NUnit.Framework; @@ -54,7 +53,7 @@ namespace osu.Game.Tests.Beatmaps private Stream openResource(string name) { - string localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)).AsNonNull(); + string localPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location).AsNonNull(); return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); } diff --git a/osu.Game/Tests/PollingNotificationsClient.cs b/osu.Game/Tests/PollingNotificationsClient.cs index 571a7a1ed1..450c763170 100644 --- a/osu.Game/Tests/PollingNotificationsClient.cs +++ b/osu.Game/Tests/PollingNotificationsClient.cs @@ -24,8 +24,8 @@ namespace osu.Game.Tests { while (!cancellationToken.IsCancellationRequested) { - await API.PerformAsync(CreateInitialFetchRequest()); - await Task.Delay(1000, cancellationToken); + await API.PerformAsync(CreateInitialFetchRequest()).ConfigureAwait(true); + await Task.Delay(1000, cancellationToken).ConfigureAwait(true); } }, cancellationToken); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 765c665966..ad5e3f6c4d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -108,9 +108,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams - .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) - .OrderBy(pair => pair.userCount) - .First().teamID; + .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))).MinBy(pair => pair.userCount).teamID; user.MatchState = new TeamVersusUserState { TeamID = bestTeam }; ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).WaitSafely(); diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 0d209f47e8..929a29251d 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -27,8 +27,7 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(TextureStore ts) { - if (ts == null) - throw new ArgumentNullException(nameof(ts)); + ArgumentNullException.ThrowIfNull(ts); string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e7af127a30..2f7232d5ea 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -36,8 +36,7 @@ namespace osu.Game.Users protected UserPanel(APIUser user) : base(HoverSampleSet.Button) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); User = user; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8639ce6361..83dbf7e370 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,6 +1,6 @@ - netstandard2.1 + net6.0 Library true @@ -35,8 +35,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 184e3303c8..c6798cd5e4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -1,92 +1,26 @@  - 8.0 - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Resources - PackageReference - bin\$(Platform)\$(Configuration) - cjk,mideast,other,rare,west - false - NSUrlSessionHandler - iPhone Developer - true - - - - --nosymbolstrip=BASS_FX_BPM_BeatCallbackReset --nosymbolstrip=BASS_FX_BPM_BeatCallbackSet --nosymbolstrip=BASS_FX_BPM_BeatDecodeGet --nosymbolstrip=BASS_FX_BPM_BeatFree --nosymbolstrip=BASS_FX_BPM_BeatGetParameters --nosymbolstrip=BASS_FX_BPM_BeatSetParameters --nosymbolstrip=BASS_FX_BPM_CallbackReset --nosymbolstrip=BASS_FX_BPM_CallbackSet --nosymbolstrip=BASS_FX_BPM_DecodeGet --nosymbolstrip=BASS_FX_BPM_Free --nosymbolstrip=BASS_FX_BPM_Translate --nosymbolstrip=BASS_FX_GetVersion --nosymbolstrip=BASS_FX_ReverseCreate --nosymbolstrip=BASS_FX_ReverseGetSource --nosymbolstrip=BASS_FX_TempoCreate --nosymbolstrip=BASS_FX_TempoGetRateRatio --nosymbolstrip=BASS_FX_TempoGetSource --nosymbolstrip=BASS_Mixer_ChannelFlags --nosymbolstrip=BASS_Mixer_ChannelGetData --nosymbolstrip=BASS_Mixer_ChannelGetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelGetLevel --nosymbolstrip=BASS_Mixer_ChannelGetLevelEx --nosymbolstrip=BASS_Mixer_ChannelGetMatrix --nosymbolstrip=BASS_Mixer_ChannelGetMixer --nosymbolstrip=BASS_Mixer_ChannelGetPosition --nosymbolstrip=BASS_Mixer_ChannelGetPositionEx --nosymbolstrip=BASS_Mixer_ChannelIsActive --nosymbolstrip=BASS_Mixer_ChannelRemove --nosymbolstrip=BASS_Mixer_ChannelRemoveSync --nosymbolstrip=BASS_Mixer_ChannelSetEnvelope --nosymbolstrip=BASS_Mixer_ChannelSetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelSetMatrix --nosymbolstrip=BASS_Mixer_ChannelSetMatrixEx --nosymbolstrip=BASS_Mixer_ChannelSetPosition --nosymbolstrip=BASS_Mixer_ChannelSetSync --nosymbolstrip=BASS_Mixer_GetVersion --nosymbolstrip=BASS_Mixer_StreamAddChannel --nosymbolstrip=BASS_Mixer_StreamAddChannelEx --nosymbolstrip=BASS_Mixer_StreamCreate --nosymbolstrip=BASS_Mixer_StreamGetChannels --nosymbolstrip=BASS_Split_StreamCreate --nosymbolstrip=BASS_Split_StreamGetAvailable --nosymbolstrip=BASS_Split_StreamGetSource --nosymbolstrip=BASS_Split_StreamGetSplits --nosymbolstrip=BASS_Split_StreamReset --nosymbolstrip=BASS_Split_StreamResetEx - - --nolinkaway --nostrip $(GeneratedMtouchSymbolStripFlags) - - - true - full - false - DEBUG;ENABLE_TEST_CLOUD; - true - true - - - pdbonly - true - - - x86_64 - None + true + + true + + $(NoWarn);MT7091 - true - SdkOnly - ARM64 - Entitlements.plist + ios-arm64 - - true - 25823 - false - - - true - - - true - 28126 + + iossimulator-x64 - - - - - - - - - - - - - - $(NoWarn);NU1605 - - - - - none - - - none - - - - - - - - - - - - - + + + + + + diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index c5b2d0b451..64eb5c63f5 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.iOS @@ -12,7 +11,7 @@ namespace osu.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 16cb68fa7d..c4b08ab78e 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -15,7 +15,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.iOS/Linker.xml b/osu.iOS/Linker.xml deleted file mode 100644 index b55be3be39..0000000000 --- a/osu.iOS/Linker.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index b3194e497b..3e79bc6ad6 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -5,6 +5,7 @@ using System; using Foundation; +using Microsoft.Maui.Devices; using osu.Framework.Graphics; using osu.Framework.Input.Handlers; using osu.Framework.iOS.Input; @@ -12,7 +13,6 @@ using osu.Game; using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; -using Xamarin.Essentials; namespace osu.iOS { @@ -33,7 +33,7 @@ namespace osu.iOS { switch (handler) { - case IOSMouseHandler _: + case IOSMouseHandler: return new IOSMouseSettings(); default: diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index b9da874f30..def538af1a 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,124 +1,16 @@ - - - - Debug - iPhoneSimulator + + + net6.0-ios + 13.4 Exe - {3F082D0B-A964-43D7-BDF7-C256D76A50D0} - osu.iOS - osu.iOS - false + true - + + + + + - - - - - - - - - - - - - - - - - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - - - - - - - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - - - - diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 154ad0fe8c..ef3b08e1f5 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -218,6 +218,7 @@ WARNING WARNING WARNING + WARNING WARNING HINT WARNING