diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 103e4dbc30..de902df93f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,17 +15,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side. - # https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e - - name: Install .NET 3.1.x LTS + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "3.1.x" - - - name: Install .NET 6.0.x - uses: actions/setup-dotnet@v3 - with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Restore Tools run: dotnet tool restore @@ -79,10 +72,10 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Compile run: dotnet build -c Debug -warnaserror osu.Desktop.slnf @@ -114,10 +107,10 @@ jobs: distribution: microsoft java-version: 11 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Install .NET workloads run: dotnet workload install maui-android @@ -135,13 +128,16 @@ jobs: - name: Checkout uses: actions/checkout@v3 - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Install .NET Workloads run: dotnet workload install maui-ios + - name: Select Xcode 15.2 + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer + - name: Build run: dotnet build -c Debug osu.iOS diff --git a/.github/workflows/update-web-mod-definitions.yml b/.github/workflows/update-web-mod-definitions.yml index 32d3d37ffe..5827a6cdbf 100644 --- a/.github/workflows/update-web-mod-definitions.yml +++ b/.github/workflows/update-web-mod-definitions.yml @@ -12,10 +12,10 @@ jobs: name: Update osu-web mod definitions runs-on: ubuntu-latest steps: - - name: Install .NET 6.0.x + - name: Install .NET 8.0.x uses: actions/setup-dotnet@v3 with: - dotnet-version: "6.0.x" + dotnet-version: "8.0.x" - name: Checkout ppy/osu uses: actions/checkout@v3 diff --git a/Directory.Build.props b/Directory.Build.props index b08283f071..2d289d0f22 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@  - 10.0 + 12.0 true enable diff --git a/README.md b/README.md index d7e710f392..dc5809d46b 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ If you are just looking to give the game a whirl, you can grab the latest releas ### Latest release: -| [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) | -| ------------- | ------------- | ------------- | ------------- | ------------- | +| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([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) | +|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- | You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). diff --git a/Templates/Rulesets/ruleset-empty/Directory.Build.props b/Templates/Rulesets/ruleset-empty/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-empty/app.manifest b/Templates/Rulesets/ruleset-empty/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-empty/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index 5babdb47ff..7d43eb2b05 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.EmptyFreeform.Tests diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs index 744e207b57..e53fe01157 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(HitResult.Perfect); } protected override void UpdateHitStateTransforms(ArmedState state) 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 d09e7647e0..89abd5665c 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 @@  - net6.0 + net8.0 osu.Game.Rulesets.EmptyFreeform Library AnyCPU diff --git a/Templates/Rulesets/ruleset-example/Directory.Build.props b/Templates/Rulesets/ruleset-example/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-example/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-example/app.manifest b/Templates/Rulesets/ruleset-example/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-example/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5d64ca832a..7dc8a1336b 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.Pippidon.Tests diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index c5ada4288d..b1be25727f 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -49,7 +48,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss); + { + if (IsHovered) + ApplyMaxResult(); + else + ApplyMinResult(); + } } protected override double InitialLifetimeOffset => time_preempt; 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 9c8867f4ef..165b6b6c6b 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 @@  - net6.0 + net8.0 osu.Game.Rulesets.Pippidon Library AnyCPU diff --git a/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props b/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/app.manifest b/Templates/Rulesets/ruleset-scrolling-empty/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-empty/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 6796a8962b..9c4c8217f0 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.EmptyScrolling.Tests diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs index a3c3b89105..adcbd36485 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables { if (timeOffset >= 0) // todo: implement judgement logic - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) 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 5bf3884f53..6d9565a6f2 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 @@  - net6.0 + net8.0 osu.Game.Rulesets.EmptyScrolling Library AnyCPU diff --git a/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props b/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props new file mode 100644 index 0000000000..74d05ff690 --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/Directory.Build.props @@ -0,0 +1,10 @@ + + + + $(MSBuildThisFileDirectory)app.manifest + + + true + $(NoWarn);CS1591 + + diff --git a/Templates/Rulesets/ruleset-scrolling-example/app.manifest b/Templates/Rulesets/ruleset-scrolling-example/app.manifest new file mode 100644 index 0000000000..1c1e5f540c --- /dev/null +++ b/Templates/Rulesets/ruleset-scrolling-example/app.manifest @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 5d64ca832a..7dc8a1336b 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -18,7 +18,7 @@ WinExe - net6.0 + net8.0 osu.Game.Rulesets.Pippidon.Tests diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs index d198fa81cb..3ad636a601 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Pippidon.UI; -using osu.Game.Rulesets.Scoring; using osuTK; using osuTK.Graphics; @@ -49,7 +48,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss); + { + if (currentLane.Value == HitObject.Lane) + ApplyMaxResult(); + else + ApplyMinResult(); + } } protected override void UpdateHitStateTransforms(ArmedState state) 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 9c8867f4ef..165b6b6c6b 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 @@  - net6.0 + net8.0 osu.Game.Rulesets.Pippidon Library AnyCPU diff --git a/global.json b/global.json index 5dcd5f425a..789bff3bd0 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "6.0.100", - "rollForward": "latestFeature" + "version": "8.0.100", + "rollForward": "latestFeature", + "allowPrerelease": false } -} - +} \ No newline at end of file diff --git a/osu.Android.props b/osu.Android.props index d944e2ce8e..d7f29beeb3 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + - false 0.0.0 1 $(Version) @@ -19,4 +16,7 @@ + + + diff --git a/osu.Desktop/NVAPI.cs b/osu.Desktop/NVAPI.cs index bb3a59cc7f..78a814c585 100644 --- a/osu.Desktop/NVAPI.cs +++ b/osu.Desktop/NVAPI.cs @@ -138,7 +138,7 @@ namespace osu.Desktop return false; // Make sure that this is a laptop. - var gpus = new IntPtr[64]; + IntPtr[] gpus = new IntPtr[64]; if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount))) return false; @@ -456,7 +456,7 @@ namespace osu.Desktop [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)] public string ProfileName; - [MarshalAs(UnmanagedType.ByValArray)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public uint[] GPUSupport; public uint IsPredefined; diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 57752aa1f7..e7a63bd921 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 WinExe true A free-to-win rhythm game. Rhythm is just a *click* away! diff --git a/osu.Game.Benchmarks/BenchmarkUnstableRate.cs b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs new file mode 100644 index 0000000000..aa229c7d06 --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkUnstableRate.cs @@ -0,0 +1,31 @@ +// 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 BenchmarkDotNet.Attributes; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkUnstableRate : BenchmarkTest + { + private List events = null!; + + public override void SetUp() + { + base.SetUp(); + events = new List(); + + for (int i = 0; i < 1000; i++) + events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, new HitObject(), null, null)); + } + + [Benchmark] + public void CalculateUnstableRate() + { + _ = events.CalculateUnstableRate(); + } + } +} diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 47c93fbd02..64da5412a8 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 Exe false diff --git a/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml index 52b34959b9..b6ab91ed5c 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/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 4ee3219442..4b2e54be67 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,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + 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 acf12bb0ac..9c262a752a 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,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.iOS diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 0a77845343..619081c754 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 7f8c17861d..64705f9909 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -63,7 +63,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (CheckPosition == null) return; if (timeOffset >= 0 && Result != null) - ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult); + { + if (CheckPosition.Invoke(HitObject)) + ApplyMaxResult(); + else + ApplyMinResult(); + } } protected override void UpdateHitStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj b/osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj index ecce7c1b3f..a5138ffb39 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 @@  - net6.0 + net8.0 Library true catch the fruit. to the beat. diff --git a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index f5a49210ea..df4930419c 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/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 25335754d2..2866508a02 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,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + 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 51e07dd6c1..d51e541e95 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,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.iOS diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 877b9c3ea4..eee06acdb8 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 6bb6879052..4190e74e51 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; - public override int Version => 20220902; + public override int Version => 20230817; public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 050b302bd8..88d6a19822 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter) { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index d9de06a811..189c4b3a5f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -8,5 +8,6 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModHardRock : ModHardRock { public override double ScoreMultiplier => 1; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 31f52610e9..7dd0c499da 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "One Key"; public override string Acronym => "1K"; public override LocalisableString Description => @"Play with one key."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs index 67e65b887a..a6c57d4597 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Ten Keys"; public override string Acronym => "10K"; public override LocalisableString Description => @"Play with ten keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 0f8148d252..0d04395a52 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Two Keys"; public override string Acronym => "2K"; public override LocalisableString Description => @"Play with two keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 0f8af7940c..c83b0979ee 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -11,5 +11,6 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Three Keys"; public override string Acronym => "3K"; public override LocalisableString Description => @"Play with three keys."; + public override bool Ranked => false; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index f9690b4298..cc7e270dda 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModMirror : ModMirror, IApplicableToBeatmap { public override LocalisableString Description => "Notes are flipped horizontally."; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToBeatmap(IBeatmap beatmap) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 3490d50871..2b55e81788 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -265,7 +265,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (Tail.AllJudged) { if (Tail.IsHit) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); else MissForcefully(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs index 1b2efbafdf..6259033235 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteBody.cs @@ -25,7 +25,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (AllJudged) return; - ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 8498fd36de..e98622b8bf 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public virtual void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); + public virtual void MissForcefully() => ApplyMinResult(); } public abstract partial class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 680009bc4c..f6b92ab405 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -89,18 +89,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) return; result = GetCappedResult(result); - - ApplyResult(r => r.Type = result); + ApplyResult(result); } /// diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index bea536e4af..decf670c5d 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -1,16 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; @@ -59,10 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); - [Resolved] - private ISkinSource skin { get; set; } + private ISkinSource currentSkin = null!; - public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList? mods = null) : base(ruleset, beatmap, mods) { BarLines = new BarLineGenerator(Beatmap).BarLines; @@ -72,8 +71,12 @@ namespace osu.Game.Rulesets.Mania.UI } [BackgroundDependencyLoader] - private void load() + private void load(ISkinSource source) { + currentSkin = source; + currentSkin.SourceChanged += onSkinChange; + skinChanged(); + foreach (var mod in Mods.OfType()) mod.ApplyToTrack(speedAdjustmentTrack); @@ -109,12 +112,28 @@ namespace osu.Game.Rulesets.Mania.UI updateTimeRange(); } + private ScheduledDelegate? pendingSkinChange; + private float hitPosition; + + private void onSkinChange() + { + // schedule required to avoid calls after disposed. + // note that this has the side-effect of components only performing a skin change when they are alive. + pendingSkinChange?.Cancel(); + pendingSkinChange = Scheduler.Add(skinChanged); + } + + private void skinChanged() + { + hitPosition = currentSkin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? Stage.HIT_TARGET_POSITION; + + pendingSkinChange = null; + } + private void updateTimeRange() { - float hitPosition = skin.GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value - ?? Stage.HIT_TARGET_POSITION; - const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION; float lengthToHitPosition = 768 - hitPosition; @@ -139,10 +158,18 @@ namespace osu.Game.Rulesets.Mania.UI protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) => null; + public override DrawableHitObject? CreateDrawableRepresentation(ManiaHitObject h) => null; protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (currentSkin.IsNotNull()) + currentSkin.SourceChanged -= onSkinChange; + } } } diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 72f172188e..3bca938450 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 @@  - net6.0 + net8.0 Library true smash the keys. to the beat. diff --git a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index ed4725dd94..d0c3484cfd 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/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 e8a46a9828..b79de6d40b 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,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.Android @@ -24,4 +24,4 @@ - \ No newline at end of file + 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 7d50deb8ba..cc0233d7fd 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,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 Exe osu.Game.Rulesets.Osu.Tests diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index f99518997b..5524af2061 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(130) + Size = new Vector2(300) } }; }); @@ -85,6 +85,30 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("return user input", () => InputManager.UseParentInput = true); } + [Test] + public void TestAllPoints() + { + AddStep("add points", () => + { + float minX = object1.DrawPosition.X - object1.DrawSize.X / 2; + float maxX = object1.DrawPosition.X + object1.DrawSize.X / 2; + + float minY = object1.DrawPosition.Y - object1.DrawSize.Y / 2; + float maxY = object1.DrawPosition.Y + object1.DrawSize.Y / 2; + + for (int i = 0; i < 10; i++) + { + for (float x = minX; x <= maxX; x += 0.5f) + { + for (float y = minY; y <= maxY; y += 0.5f) + { + accuracyHeatmap.AddPoint(object2.Position, object1.Position, new Vector2(x, y), RNG.NextSingle(10, 500)); + } + } + } + }); + } + protected override bool OnMouseDown(MouseDownEvent e) { accuracyHeatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 30b0451a3b..abe950f9bb 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && timeOffset > hitOffset && CheckHittable?.Invoke(this, Time.Current, HitResult.Great) == ClickAction.Hit) { // force success - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs index 7824f26251..838b426cb4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs @@ -208,7 +208,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (shouldHit && !userTriggered && timeOffset >= 0) { // force success - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(HitResult.Great); } else base.CheckForResult(userTriggered, timeOffset); diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 9c248abd66..ea54c8d313 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -8,7 +8,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 8b0adbe50f..bb0e984418 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; + public override bool Ranked => true; private DrawableOsuBlinds blinds = null!; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index b70d607ca1..a9111eec1f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -75,8 +75,10 @@ namespace osu.Game.Rulesets.Osu.Mods { double time = playfield.Time.Current; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + switch (drawable) { case DrawableHitCircle circle: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index befee4af5a..b49fb931d1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -49,8 +49,10 @@ namespace osu.Game.Rulesets.Osu.Mods { var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + switch (drawable) { case DrawableHitCircle circle: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 40fadfb77e..3679425389 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (!slider.HeadCircle.IsHit) handleHitCircle(slider.HeadCircle); - requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true); + requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(slider.Tracking.Value); break; case DrawableSpinner spinner: diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 91feb33931..ced98f0cd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -48,8 +48,10 @@ namespace osu.Game.Rulesets.Osu.Mods { var cursorPos = playfield.Cursor.AsNonNull().ActiveCursor.DrawPosition; - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var drawable = entry.Value; + var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE); if (drawable.HitObject is Slider thisSlider) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index f691731afe..df9544b71e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot), typeof(OsuModTargetPractice) }; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToDrawableHitObject(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f1468d414e..917685cdad 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -10,5 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTouchDevice : ModTouchDevice { public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); + public override bool Ranked => UsesDefaultConfiguration; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 0d665cad0c..b1c9bef6c4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } @@ -169,19 +169,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (result == HitResult.None || clickAction != ClickAction.Hit) return; - ApplyResult(r => + Vector2? hitPosition = null; + + // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. + if (result.IsHit()) + { + var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + hitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); + } + + ApplyResult<(HitResult result, Vector2? position)>((r, state) => { var circleResult = (OsuHitCircleJudgementResult)r; - // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result.IsHit()) - { - var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); - } - - circleResult.Type = result; - }); + circleResult.Type = state.result; + circleResult.CursorPositionAtHit = state.position; + }, (result, hitPosition)); } /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 5b379a0d90..5271c03e08 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -100,12 +100,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get hit, disregarding all conditions in implementations of . /// - public void HitForcefully() => ApplyResult(r => r.Type = r.Judgement.MaxResult); + public void HitForcefully() => ApplyMaxResult(); /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); + public void MissForcefully() => ApplyMinResult(); private RectangleF parentScreenSpaceRectangle => ((DrawableOsuHitObject)ParentHitObject)?.parentScreenSpaceRectangle ?? Parent!.ScreenSpaceDrawQuad.AABBFloat; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 080d07be49..6d492e7b08 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -292,10 +292,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (HitObject.ClassicSliderBehaviour) { // Classic behaviour means a slider is judged proportionally to the number of nested hitobjects hit. This is the classic osu!stable scoring. - ApplyResult(r => + ApplyResult(static (r, hitObject) => { - int totalTicks = NestedHitObjects.Count; - int hitTicks = NestedHitObjects.Count(h => h.IsHit); + int totalTicks = hitObject.NestedHitObjects.Count; + int hitTicks = hitObject.NestedHitObjects.Count(h => h.IsHit); if (hitTicks == totalTicks) r.Type = HitResult.Great; @@ -312,7 +312,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // If only the nested hitobjects are judged, then the slider's own judgement is ignored for scoring purposes. // But the slider needs to still be judged with a reasonable hit/miss result for visual purposes (hit/miss transforms, etc). - ApplyResult(r => r.Type = NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, hitObject) => + { + r.Type = hitObject.NestedHitObjects.Any(h => h.Result.IsHit) ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 9a98286738..11120e49b5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -258,15 +258,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var tick in ticks.Where(t => !t.Result.HasResult)) tick.TriggerResult(false); - ApplyResult(r => + ApplyResult(static (r, hitObject) => { - if (Progress >= 1) + var spinner = (DrawableSpinner)hitObject; + if (spinner.Progress >= 1) r.Type = HitResult.Great; - else if (Progress > .9) + else if (spinner.Progress > .9) r.Type = HitResult.Ok; - else if (Progress > .75) + else if (spinner.Progress > .75) r.Type = HitResult.Meh; - else if (Time.Current >= HitObject.EndTime) + else if (spinner.Time.Current >= spinner.HitObject.EndTime) r.Type = r.Judgement.MinResult; }); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index 5b55533edd..0a77faf924 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -35,6 +35,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Apply a judgement result. /// /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + internal void TriggerResult(bool hit) + { + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index ceee513412..ee2490439f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -30,7 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects public class TailJudgement : SliderEndJudgement { public override HitResult MaxResult => HitResult.SliderTailHit; - public override HitResult MinResult => HitResult.IgnoreMiss; } } } diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 83bab7dc01..f9d4a3b325 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Statistics for (int c = 0; c < points_per_dimension; c++) { - HitPointType pointType = Vector2.Distance(new Vector2(c, r), centre) <= innerRadius + HitPointType pointType = Vector2.Distance(new Vector2(c + 0.5f, r + 0.5f), centre) <= innerRadius ? HitPointType.Hit : HitPointType.Miss; diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 75656e2976..518ab362ca 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 @@  - net6.0 + net8.0 Library true click the circles. to the beat. diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index cc88d3080a..0ae9ee43ad 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/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 a639326ebd..ee973e8544 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,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.Android @@ -21,4 +21,4 @@ - \ No newline at end of file + 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 e648a11299..ee2d4d703e 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,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.iOS diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png new file mode 100644 index 0000000000..8ad7849e53 Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taiko-bar-right@2x.png differ diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png new file mode 100644 index 0000000000..73f856b16b Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircle@2x.png differ diff --git a/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png new file mode 100644 index 0000000000..83647266bc Binary files /dev/null and b/osu.Game.Rulesets.Taiko.Tests/Resources/special-skin/taikohitcircleoverlay@2x.png differ diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 4eb6b0ab3d..26afd42445 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -7,7 +7,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index ab193caaa3..b84c2d25ee 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { private const double difficulty_multiplier = 1.35; - public override int Version => 20220902; + public override int Version => 20221107; public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2bf0c04adf..1af4719b02 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (timeOffset < 0) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -192,7 +192,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, hitObject) => + { + var drumRoll = (StrongNestedHit)hitObject; + r.Type = drumRoll.ParentHitObject!.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index c900165d34..0333fd71a9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -49,14 +49,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } public override void OnKilled() @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables base.OnKilled(); if (Time.Current > HitObject.GetEndTime() && !Judged) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -105,7 +105,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Judged) return; - ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); + ApplyResult(static (r, hitObject) => + { + var nestedHit = (StrongNestedHit)hitObject; + r.Type = nestedHit.ParentHitObject!.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult; + }); } public override bool OnPressed(KeyBindingPressEvent e) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs index a039ce3407..aad9214c5e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableFlyingHit.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void LoadComplete() { base.LoadComplete(); - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void LoadSamples() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 1ef426854e..4fb69056da 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } @@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); else - ApplyResult(r => r.Type = result); + ApplyResult(result); } public override bool OnPressed(KeyBindingPressEvent e) @@ -209,19 +209,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!ParentHitObject.Result.IsHit) { - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (!userTriggered) { if (timeOffset - ParentHitObject.Result.TimeOffset > SECOND_HIT_WINDOW) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); return; } if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= SECOND_HIT_WINDOW) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } public override bool OnPressed(KeyBindingPressEvent e) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index 724d59edcd..11759927a9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // it can happen that the hit window of the nested strong hit extends past the lifetime of the parent object. // this is a safety to prevent such cases from causing the nested hit to never be judged and as such prevent gameplay from completing. if (!Judged && Time.Current > ParentHitObject?.HitObject.GetEndTime()) - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index e4a083f218..e1fc28fe16 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint); if (numHits == HitObject.RequiredHits) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } else { @@ -227,7 +227,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - ApplyResult(r => r.Type = numHits == HitObject.RequiredHits ? r.Judgement.MaxResult : r.Judgement.MinResult); + if (numHits == HitObject.RequiredHits) + ApplyMaxResult(); + else + ApplyMinResult(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 3a5c006962..04dd01e066 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -30,7 +30,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); + + if (hit) + ApplyMaxResult(); + else + ApplyMinResult(); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs index 9fe861c08c..84dea474c5 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSettingsSubsection.cs @@ -1,9 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Taiko.Configuration; @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko { new SettingsEnumDropdown { - LabelText = "Touch control scheme", + LabelText = RulesetSettingsStrings.TouchControlScheme, Current = config.GetBindable(TaikoRulesetSetting.TouchControlScheme) } }; diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index f0e1cb8e8f..cacba55c2a 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 @@  - net6.0 + net8.0 Library true bash the drum. to the beat. diff --git a/osu.Game.Tests.Android/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml index 6f91fb928c..71793af977 100644 --- a/osu.Game.Tests.Android/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/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index b745d91980..889f0a3583 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -1,7 +1,7 @@  - net6.0-android + net8.0-android Exe osu.Game.Tests osu.Game.Tests.Android diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 79771fcd50..e4b9d2ba94 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,7 +1,7 @@  Exe - net6.0-ios + net8.0-ios 13.4 osu.Game.Tests osu.Game.Tests.iOS diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 10dbede2e0..73177e36e1 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Gameplay LifetimeStart = LIFETIME_ON_APPLY; } - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(HitResult.Miss); protected override void UpdateHitStateTransforms(ArmedState state) { diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index ad1a3fd63f..2ac89efb69 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.NonVisual var task3 = addTask(); // Cancel task2, allow task3 to complete. - task2.cancellation.Cancel(); + await task2.cancellation.CancelAsync(); task2.mutex.Set(); task3.mutex.Set(); diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 73465fae08..a3f91fffba 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -31,8 +29,8 @@ namespace osu.Game.Tests.Rulesets.Scoring { public partial class ScoreProcessorTest { - private ScoreProcessor scoreProcessor; - private IBeatmap beatmap; + private ScoreProcessor scoreProcessor = null!; + private IBeatmap beatmap = null!; [SetUp] public void SetUp() @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Standardised, HitResult.SmallTickHit, HitResult.SmallTickHit, 493_652)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Standardised, HitResult.LargeTickHit, HitResult.LargeTickHit, 326_963)] - [TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 326_963)] + [TestCase(ScoringMode.Standardised, HitResult.SliderTailHit, HitResult.SliderTailHit, 371_627)] [TestCase(ScoringMode.Standardised, HitResult.SmallBonus, HitResult.SmallBonus, 1_000_030)] [TestCase(ScoringMode.Standardised, HitResult.LargeBonus, HitResult.LargeBonus, 1_000_150)] [TestCase(ScoringMode.Classic, HitResult.Miss, HitResult.Great, 0)] @@ -99,7 +97,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(ScoringMode.Classic, HitResult.SmallTickHit, HitResult.SmallTickHit, 49_365)] [TestCase(ScoringMode.Classic, HitResult.LargeTickMiss, HitResult.LargeTickHit, 0)] [TestCase(ScoringMode.Classic, HitResult.LargeTickHit, HitResult.LargeTickHit, 32_696)] - [TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 32_696)] + [TestCase(ScoringMode.Classic, HitResult.SliderTailHit, HitResult.SliderTailHit, 37_163)] [TestCase(ScoringMode.Classic, HitResult.SmallBonus, HitResult.SmallBonus, 100_003)] [TestCase(ScoringMode.Classic, HitResult.LargeBonus, HitResult.LargeBonus, 100_015)] public void TestFourVariousResultsOneMiss(ScoringMode scoringMode, HitResult hitResult, HitResult maxResult, int expectedScore) @@ -171,7 +169,7 @@ namespace osu.Game.Tests.Rulesets.Scoring [TestCase(HitResult.Perfect, HitResult.Miss)] [TestCase(HitResult.SmallTickHit, HitResult.SmallTickMiss)] [TestCase(HitResult.LargeTickHit, HitResult.LargeTickMiss)] - [TestCase(HitResult.SliderTailHit, HitResult.LargeTickMiss)] + [TestCase(HitResult.SliderTailHit, HitResult.IgnoreMiss)] [TestCase(HitResult.SmallBonus, HitResult.IgnoreMiss)] [TestCase(HitResult.LargeBonus, HitResult.IgnoreMiss)] public void TestMinResults(HitResult hitResult, HitResult expectedMinResult) @@ -476,7 +474,7 @@ namespace osu.Game.Tests.Rulesets.Scoring public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index fea7456472..b567e8de8d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -431,7 +431,7 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset > HitObject.Duration) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } protected override void UpdateHitStateTransforms(ArmedState state) @@ -468,7 +468,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override void OnKilled() { base.OnKilled(); - ApplyResult(r => r.Type = r.Judgement.MinResult); + ApplyMinResult(); } } @@ -547,7 +547,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } } @@ -596,7 +596,7 @@ namespace osu.Game.Tests.Visual.Gameplay { base.CheckForResult(userTriggered, timeOffset); if (timeOffset >= 0) - ApplyResult(r => r.Type = r.Judgement.MaxResult); + ApplyMaxResult(); } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index b938e59d63..0883c626fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -64,6 +64,12 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select first room", () => container.Rooms.First().TriggerClick()); AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + + AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID?.Value))); + AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category.Value == RoomCategory.Spotlight))); + + AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category.Value == RoomCategory.Spotlight))); + AddAssert("selection vacated", () => checkRoomSelected(null)); } [Test] diff --git a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs index 9407941da6..b6a300322f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs @@ -2,14 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; namespace osu.Game.Tests.Visual.Online { public partial class TestSceneDashboardOverlay : OsuTestScene { - protected override bool UseOnlineAPI => true; - private readonly DashboardOverlay overlay; public TestSceneDashboardOverlay() @@ -17,6 +18,30 @@ namespace osu.Game.Tests.Visual.Online Add(overlay = new DashboardOverlay()); } + [BackgroundDependencyLoader] + private void load() + { + int supportLevel = 0; + + for (int i = 0; i < 1000; i++) + { + supportLevel++; + + if (supportLevel > 3) + supportLevel = 0; + + ((DummyAPIAccess)API).Friends.Add(new APIUser + { + Username = @"peppy", + Id = 2, + Colour = "99EB47", + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = supportLevel > 0, + SupportLevel = supportLevel + }); + } + } + [Test] public void TestShow() { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 685a685896..cf4bec54ff 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -26,8 +26,10 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Expanded.Accuracy; using osu.Game.Screens.Ranking.Expanded.Statistics; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Skinning; using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -44,6 +46,9 @@ namespace osu.Game.Tests.Visual.Ranking [Resolved] private RealmAccess realm { get; set; } + [Resolved] + private SkinManager skins { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -57,8 +62,17 @@ namespace osu.Game.Tests.Visual.Ranking if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); }); + + AddToggleStep("toggle legacy classic skin", v => + { + if (skins != null) + skins.CurrentSkinInfo.Value = v ? skins.DefaultClassicSkin.SkinInfo : skins.CurrentSkinInfo.Default; + }); } + [SetUp] + public void SetUp() => Schedule(() => skins.CurrentSkinInfo.SetDefault()); + [Test] public void TestScaling() { @@ -132,6 +146,46 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestResultsWithFailingRank() + { + TestResultsScreen screen = null; + + loadResultsScreen(() => + { + var score = TestResources.CreateTestScoreInfo(); + + score.OnlineID = onlineScoreID++; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Rank = ScoreRank.F; + return screen = createResultsScreen(score); + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + AddAssert("no badges displayed", () => this.ChildrenOfType().All(b => !b.IsPresent)); + } + + [Test] + public void TestResultsWithFailingRankOnLegacySkin() + { + TestResultsScreen screen = null; + + AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo); + + loadResultsScreen(() => + { + var score = TestResources.CreateTestScoreInfo(); + + score.OnlineID = onlineScoreID++; + score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents(); + score.Rank = ScoreRank.F; + return screen = createResultsScreen(score); + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + AddAssert("no badges displayed", () => this.ChildrenOfType().All(b => !b.IsPresent)); + } + [Test] public void TestShowHideStatisticsViaOutsideClick() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs index a95bb2c9e3..b79ce6c75f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -67,6 +68,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty())); } + [Test] + public void TestUnrankedBadge() + { + AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() })); + AddAssert("Unranked badge shown", () => footerButtonMods.UnrankedBadge.Alpha == 1); + AddStep(@"Clear selected mod", () => changeMods(Array.Empty())); + AddAssert("Unranked badge not shown", () => footerButtonMods.UnrankedBadge.Alpha == 0); + } + private void changeMods(IReadOnlyList mods) { footerButtonMods.Current.Value = mods; @@ -83,6 +93,7 @@ namespace osu.Game.Tests.Visual.UserInterface private partial class TestFooterButtonMods : FooterButtonMods { public new OsuSpriteText MultiplierText => base.MultiplierText; + public new Drawable UnrankedBadge => base.UnrankedBadge; } } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 046954db47..99a5897dff 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("mod multiplier correct", () => { double multiplier = SelectedMods.Value.Aggregate(1d, (m, mod) => m * mod.ScoreMultiplier); - return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().Current.Value); + return Precision.AlmostEquals(multiplier, modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value); }); assertCustomisationToggleState(disabled: false, active: false); AddAssert("setting items created", () => modSelectOverlay.ChildrenOfType().Any()); @@ -846,7 +846,7 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); AddAssert("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.1).Within(Precision.DOUBLE_EPSILON)); // this is highly unorthodox in a test, but because the `ModSettingChangeTracker` machinery heavily leans on events and object disposal and re-creation, // it is instrumental in the reproduction of the failure scenario that this test is supposed to cover. @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType().Single() .ChildrenOfType>().Single().TriggerClick()); AddUntilStep("difficulty multiplier display shows correct value", - () => modSelectOverlay.ChildrenOfType().Single().Current.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); + () => modSelectOverlay.ChildrenOfType().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON)); } private void waitForColumnLoad() => AddUntilStep("all column content loaded", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs similarity index 50% rename from osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs index c2ddd814b7..42f243cc21 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneScoreMultiplierDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingInformationDisplay.cs @@ -11,7 +11,7 @@ using osu.Game.Overlays.Mods; namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public partial class TestSceneScoreMultiplierDisplay : OsuTestScene + public partial class TestSceneRankingInformationDisplay : OsuTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); @@ -19,22 +19,24 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestBasic() { - ScoreMultiplierDisplay multiplierDisplay = null!; + RankingInformationDisplay onlinePropertiesDisplay = null!; - AddStep("create content", () => Child = multiplierDisplay = new ScoreMultiplierDisplay + AddStep("create content", () => Child = onlinePropertiesDisplay = new RankingInformationDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre }); - AddStep("set multiplier below 1", () => multiplierDisplay.Current.Value = 0.5); - AddStep("set multiplier to 1", () => multiplierDisplay.Current.Value = 1); - AddStep("set multiplier above 1", () => multiplierDisplay.Current.Value = 1.5); + AddToggleStep("toggle ranked", ranked => onlinePropertiesDisplay.Ranked.Value = ranked); + + AddStep("set multiplier below 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 0.5); + AddStep("set multiplier to 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1); + AddStep("set multiplier above 1", () => onlinePropertiesDisplay.ModMultiplier.Value = 1.5); AddSliderStep("set multiplier", 0, 2, 1d, multiplier => { - if (multiplierDisplay.IsNotNull()) - multiplierDisplay.Current.Value = multiplier; + if (onlinePropertiesDisplay.IsNotNull()) + onlinePropertiesDisplay.ModMultiplier.Value = multiplier; }); } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 7b08524240..c0bbdfb4ed 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -10,7 +10,7 @@ WinExe - net6.0 + net8.0 tests.ruleset diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 3b00f103c4..8f1d7114b1 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -10,7 +10,7 @@ WinExe - net6.0 + net8.0 diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj index ab67e490cd..c8578ac464 100644 --- a/osu.Game.Tournament/osu.Game.Tournament.csproj +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true tools for tournaments. diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 619bfdad6e..ad57f87d10 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -153,12 +153,12 @@ namespace osu.Game.IO public static extern int link(string oldpath, string newpath); [DllImport("libc", SetLastError = true)] - private static extern int stat(string pathname, out struct_stat statbuf); + private static extern int stat(string pathname, out Stat statbuf); // ReSharper disable once InconsistentNaming // Struct layout is likely non-portable across unices. Tread with caution. [StructLayout(LayoutKind.Sequential)] - private struct struct_stat + private struct Stat { public readonly long st_dev; public readonly long st_ino; @@ -170,14 +170,14 @@ namespace osu.Game.IO public readonly long st_size; public readonly long st_blksize; public readonly long st_blocks; - public readonly timespec st_atim; - public readonly timespec st_mtim; - public readonly timespec st_ctim; + public readonly Timespec st_atim; + public readonly Timespec st_mtim; + public readonly Timespec st_ctim; } // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Sequential)] - private struct timespec + private struct Timespec { public readonly long tv_sec; public readonly long tv_nsec; diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs index 0f0f560df9..89db60d8a6 100644 --- a/osu.Game/Localisation/AudioSettingsStrings.cs +++ b/osu.Game/Localisation/AudioSettingsStrings.cs @@ -64,6 +64,21 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset"); + /// + /// "Play a few beatmaps to receive a suggested offset!" + /// + public static LocalisableString SuggestedOffsetNote => new TranslatableString(getKey(@"suggested_offset_note"), @"Play a few beatmaps to receive a suggested offset!"); + + /// + /// "Based on the last {0} play(s), the suggested offset is {1} ms." + /// + public static LocalisableString SuggestedOffsetValueReceived(int plays, LocalisableString value) => new TranslatableString(getKey(@"suggested_offset_value_received"), @"Based on the last {0} play(s), the suggested offset is {1} ms.", plays, value); + + /// + /// "Apply suggested offset" + /// + public static LocalisableString ApplySuggestedOffset => new TranslatableString(getKey(@"apply_suggested_offset"), @"Apply suggested offset"); + /// /// "Offset wizard" /// diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 18fd3e83da..066c07858c 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files"); + /// + /// "Run latency certifier" + /// + public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier"); + /// /// "Memory" /// diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index f0620245c3..04fecab3df 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -51,6 +51,31 @@ namespace osu.Game.Localisation /// public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0); + /// + /// "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." + /// + public static LocalisableString DataMigrationNoExtraSpace => new TranslatableString(getKey(@"data_migration_no_extra_space"), @"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."); + + /// + /// "Learn more about how "hard links" work" + /// + public static LocalisableString LearnAboutHardLinks => new TranslatableString(getKey(@"learn_about_hard_links"), @"Learn more about how ""hard links"" work"); + + /// + /// "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import." + /// + public static LocalisableString LightweightLinkingNotSupported => new TranslatableString(getKey(@"lightweight_linking_not_supported"), @"Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."); + + /// + /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." + /// + public static LocalisableString SecondCopyWillBeMadeWindows => new TranslatableString(getKey(@"second_copy_will_be_made_windows"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)."); + + /// + /// "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)." + /// + public static LocalisableString SecondCopyWillBeMadeOtherPlatforms => new TranslatableString(getKey(@"second_copy_will_be_made_other_platforms"), @"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs index 1d14b0a596..753444daf1 100644 --- a/osu.Game/Localisation/GraphicsSettingsStrings.cs +++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs @@ -159,6 +159,11 @@ namespace osu.Game.Localisation /// public static LocalisableString MinimiseOnFocusLoss => new TranslatableString(getKey(@"minimise_on_focus_loss"), @"Minimise osu! when switching to another app"); + /// + /// "Shrink game to avoid cameras and notches" + /// + public static LocalisableString ShrinkGameToSafeArea => new TranslatableString(getKey(@"shrink_game_to_safe_area"), @"Shrink game to avoid cameras and notches"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index 86ebebd293..9513eacf02 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -49,6 +49,26 @@ namespace osu.Game.Localisation /// public static LocalisableString ScoreMultiplier => new TranslatableString(getKey(@"score_multiplier"), @"Score Multiplier"); + /// + /// "Ranked" + /// + public static LocalisableString Ranked => new TranslatableString(getKey(@"ranked"), @"Ranked"); + + /// + /// "Performance points can be granted for the active mods." + /// + public static LocalisableString RankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points can be granted for the active mods."); + + /// + /// "Unranked" + /// + public static LocalisableString Unranked => new TranslatableString(getKey(@"unranked"), @"Unranked"); + + /// + /// "Performance points will not be granted due to active mods." + /// + public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"ranked_explanation"), @"Performance points will not be granted due to active mods."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/NotificationsStrings.cs b/osu.Game/Localisation/NotificationsStrings.cs index fb3dab032d..f4965e4ebe 100644 --- a/osu.Game/Localisation/NotificationsStrings.cs +++ b/osu.Game/Localisation/NotificationsStrings.cs @@ -113,6 +113,28 @@ Please try changing your audio device to a working setting."); /// public static LocalisableString MismatchingBeatmapForReplay => new TranslatableString(getKey(@"mismatching_beatmap_for_replay"), @"Your local copy of the beatmap for this replay appears to be different than expected. You may need to update or re-download it."); + /// + /// "You are now running osu! {version}. + /// Click to see what's new!" + /// + public static LocalisableString GameVersionAfterUpdate(string version) => new TranslatableString(getKey(@"game_version_after_update"), @"You are now running osu! {0}. +Click to see what's new!", version); + + /// + /// "Update ready to install. Click to restart!" + /// + public static LocalisableString UpdateReadyToInstall => new TranslatableString(getKey(@"update_ready_to_install"), @"Update ready to install. Click to restart!"); + + /// + /// "Downloading update..." + /// + public static LocalisableString DownloadingUpdate => new TranslatableString(getKey(@"downloading_update"), @"Downloading update..."); + + /// + /// "Installing update..." + /// + public static LocalisableString InstallingUpdate => new TranslatableString(getKey(@"installing_update"), @"Installing update..."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Localisation/RulesetSettingsStrings.cs b/osu.Game/Localisation/RulesetSettingsStrings.cs index 3fa7656cbb..e3d51f1124 100644 --- a/osu.Game/Localisation/RulesetSettingsStrings.cs +++ b/osu.Game/Localisation/RulesetSettingsStrings.cs @@ -84,6 +84,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ScrollSpeedTooltip(int scrollTime, int scrollSpeed) => new TranslatableString(getKey(@"ruleset"), @"{0}ms (speed {1})", scrollTime, scrollSpeed); + /// + /// "Touch control scheme" + /// + public static LocalisableString TouchControlScheme => new TranslatableString(getKey(@"touch_control_scheme"), @"Touch control scheme"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 732da3d5da..e4ae83ca74 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -203,6 +203,7 @@ namespace osu.Game.Online.API.Requests.Responses Ruleset = new RulesetInfo { OnlineID = RulesetID }, Passed = Passed, TotalScore = TotalScore, + LegacyTotalScore = LegacyTotalScore, Accuracy = Accuracy, MaxCombo = MaxCombo, Rank = Rank, diff --git a/osu.Game/Online/Chat/WebSocketChatClient.cs b/osu.Game/Online/Chat/WebSocketChatClient.cs index b74f8bec4b..8e1b501b25 100644 --- a/osu.Game/Online/Chat/WebSocketChatClient.cs +++ b/osu.Game/Online/Chat/WebSocketChatClient.cs @@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat { await client.SendAsync(new StartChatRequest()).ConfigureAwait(false); Logger.Log(@"Now listening to websocket chat messages.", LoggingTarget.Network); - chatStartCancellationSource.Cancel(); + await chatStartCancellationSource.CancelAsync().ConfigureAwait(false); } catch (Exception ex) { diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index 024a0fea73..9e7543ce2b 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Logging; +using osu.Framework.Utils; using osu.Game.Online.API; namespace osu.Game.Online @@ -31,6 +32,12 @@ namespace osu.Game.Online private CancellationTokenSource connectCancelSource = new CancellationTokenSource(); private bool started; + /// + /// How much to delay before attempting to connect again, in milliseconds. + /// Subject to exponential back-off. + /// + private int retryDelay = 3000; + /// /// Constructs a new . /// @@ -78,6 +85,8 @@ namespace osu.Game.Online private async Task connect() { cancelExistingConnect(); + // reset retry delay to default. + retryDelay = 3000; if (!await connectionLock.WaitAsync(10000).ConfigureAwait(false)) throw new TimeoutException("Could not obtain a lock to connect. A previous attempt is likely stuck."); @@ -134,8 +143,15 @@ namespace osu.Game.Online /// private async Task handleErrorAndDelay(Exception exception, CancellationToken cancellationToken) { - Logger.Log($"{ClientName} connect attempt failed: {exception.Message}", LoggingTarget.Network); - await Task.Delay(5000, cancellationToken).ConfigureAwait(false); + // random stagger factor to avoid mass incidental synchronisation + // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L331 + int thisDelay = (int)(retryDelay * RNG.NextDouble(0.75, 1.25)); + // exponential backoff with upper limit + // compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Online/BanchoClient.cs#L539 + retryDelay = Math.Min(120000, (int)(retryDelay * 1.5)); + + Logger.Log($"{ClientName} connect attempt failed: {exception.Message}. Next attempt in {thisDelay / 1000:N0} seconds.", LoggingTarget.Network); + await Task.Delay(thisDelay, cancellationToken).ConfigureAwait(false); } /// diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 24ac5e72e8..b19a9c6c99 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -127,18 +127,17 @@ 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.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); + copyInformation.Text = FirstRunOverlayImportFromStableScreenStrings.DataMigrationNoExtraSpace; + copyInformation.AddLink(FirstRunOverlayImportFromStableScreenStrings.LearnAboutHardLinks, LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } else if (!RuntimeInfo.IsDesktop) - 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."; + copyInformation.Text = FirstRunOverlayImportFromStableScreenStrings.LightweightLinkingNotSupported; else { copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). " - : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links). "; + ? FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMadeWindows + : FirstRunOverlayImportFromStableScreenStrings.SecondCopyWillBeMadeOtherPlatforms; + copyInformation.AddText(@" "); // just to ensure correct spacing copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); diff --git a/osu.Game/Overlays/Mods/EditPresetPopover.cs b/osu.Game/Overlays/Mods/EditPresetPopover.cs index 8bce57c96a..9554ba8ce2 100644 --- a/osu.Game/Overlays/Mods/EditPresetPopover.cs +++ b/osu.Game/Overlays/Mods/EditPresetPopover.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Mods { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(7), Children = new Drawable[] diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 7271c53e7a..ddf96c1cb3 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.Mods private DeselectAllModsButton deselectAllModsButton = null!; private Container aboveColumnsContent = null!; - private ScoreMultiplierDisplay? multiplierDisplay; + private RankingInformationDisplay? rankingInformationDisplay; private BeatmapAttributesDisplay? beatmapAttributesDisplay; protected ShearedButton BackButton { get; private set; } = null!; @@ -185,7 +185,7 @@ namespace osu.Game.Overlays.Mods aboveColumnsContent = new Container { RelativeSizeAxes = Axes.X, - Height = ScoreMultiplierDisplay.HEIGHT, + Height = RankingInformationDisplay.HEIGHT, Padding = new MarginPadding { Horizontal = 100 }, Child = SearchTextBox = new ShearedSearchTextBox { @@ -200,7 +200,7 @@ namespace osu.Game.Overlays.Mods { Padding = new MarginPadding { - Top = ScoreMultiplierDisplay.HEIGHT + PADDING, + Top = RankingInformationDisplay.HEIGHT + PADDING, Bottom = PADDING }, RelativeSizeAxes = Axes.Both, @@ -269,7 +269,7 @@ namespace osu.Game.Overlays.Mods }, Children = new Drawable[] { - multiplierDisplay = new ScoreMultiplierDisplay + rankingInformationDisplay = new RankingInformationDisplay { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight @@ -315,7 +315,7 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(_ => { - updateMultiplier(); + updateRankingInformation(); updateFromExternalSelection(); updateCustomisation(); @@ -328,7 +328,7 @@ namespace osu.Game.Overlays.Mods // // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); - modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); } }, true); @@ -450,9 +450,9 @@ namespace osu.Game.Overlays.Mods modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); } - private void updateMultiplier() + private void updateRankingInformation() { - if (multiplierDisplay == null) + if (rankingInformationDisplay == null) return; double multiplier = 1.0; @@ -460,7 +460,8 @@ namespace osu.Game.Overlays.Mods foreach (var mod in SelectedMods.Value) multiplier *= mod.ScoreMultiplier; - multiplierDisplay.Current.Value = multiplier; + rankingInformationDisplay.ModMultiplier.Value = multiplier; + rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); } private void updateCustomisation() diff --git a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs similarity index 60% rename from osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs rename to osu.Game/Overlays/Mods/RankingInformationDisplay.cs index a86eba81e4..494f8a377f 100644 --- a/osu.Game/Overlays/Mods/ScoreMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/RankingInformationDisplay.cs @@ -6,8 +6,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,15 +22,13 @@ namespace osu.Game.Overlays.Mods /// /// On the mod select overlay, this provides a local updating view of the aggregate score multiplier coming from mods. /// - public partial class ScoreMultiplierDisplay : ModFooterInformationDisplay, IHasCurrentValue + public partial class RankingInformationDisplay : ModFooterInformationDisplay { public const float HEIGHT = 42; - public Bindable Current - { - get => current.Current; - set => current.Current = value; - } + public Bindable ModMultiplier = new BindableDouble(1); + + public Bindable Ranked { get; } = new BindableBool(true); private readonly BindableWithCurrent current = new BindableWithCurrent(); @@ -39,16 +37,11 @@ namespace osu.Game.Overlays.Mods private RollingCounter counter = null!; private Box flashLayer = null!; + private TextWithTooltip rankedText = null!; [Resolved] private OsuColour colours { get; set; } = null!; - public ScoreMultiplierDisplay() - { - Current.Default = 1d; - Current.Value = 1d; - } - [BackgroundDependencyLoader] private void load() { @@ -75,13 +68,20 @@ namespace osu.Game.Overlays.Mods LeftContent.AddRange(new Drawable[] { - new OsuSpriteText + new Container { + Width = 50, + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding(10), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Text = ModSelectOverlayStrings.ScoreMultiplier, - Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + Child = rankedText = new TextWithTooltip + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) + } } }); @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Mods Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Current = { BindTarget = Current } + Current = { BindTarget = ModMultiplier } } }); } @@ -106,30 +106,22 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - Current.BindValueChanged(e => + ModMultiplier.BindValueChanged(e => { - if (e.NewValue > Current.Default) + if (e.NewValue > ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyIncrease), transition_duration, Easing.OutQuint); } - else if (e.NewValue < Current.Default) + else if (e.NewValue < ModMultiplier.Default) { - MainBackground - .FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); - counter.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + counter.FadeColour(colours.ForModType(ModType.DifficultyReduction), transition_duration, Easing.OutQuint); } else { - MainBackground.FadeColour(ColourProvider.Background4, transition_duration, Easing.OutQuint); counter.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); } - flashLayer - .FadeOutFromOne() - .FadeTo(0.15f, 60, Easing.OutQuint) - .Then().FadeOut(500, Easing.OutQuint); + flash(); const float move_amount = 4; if (e.NewValue > e.OldValue) @@ -140,10 +132,43 @@ namespace osu.Game.Overlays.Mods // required to prevent the counter initially rolling up from 0 to 1 // due to `Current.Value` having a nonstandard default value of 1. - counter.SetCountWithoutRolling(Current.Value); + counter.SetCountWithoutRolling(ModMultiplier.Value); + + Ranked.BindValueChanged(e => + { + flash(); + + if (e.NewValue) + { + rankedText.Text = ModSelectOverlayStrings.Ranked; + rankedText.TooltipText = ModSelectOverlayStrings.RankedExplanation; + rankedText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(ColourProvider.Background3, transition_duration, Easing.OutQuint); + } + else + { + rankedText.Text = ModSelectOverlayStrings.Unranked; + rankedText.TooltipText = ModSelectOverlayStrings.UnrankedExplanation; + rankedText.FadeColour(ColourProvider.Background5, transition_duration, Easing.OutQuint); + FrontBackground.FadeColour(colours.Orange1, transition_duration, Easing.OutQuint); + } + }, true); } - private partial class EffectCounter : RollingCounter + private void flash() + { + flashLayer + .FadeOutFromOne() + .FadeTo(0.15f, 60, Easing.OutQuint) + .Then().FadeOut(500, Easing.OutQuint); + } + + private partial class TextWithTooltip : OsuSpriteText, IHasTooltip + { + public LocalisableString TooltipText { get; set; } + } + + private partial class EffectCounter : RollingCounter, IHasTooltip { protected override double RollingDuration => 250; @@ -155,6 +180,8 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.Centre, Font = OsuFont.Default.With(size: 17, weight: FontWeight.SemiBold) }; + + public LocalisableString TooltipText => ModSelectOverlayStrings.ScoreMultiplier; } } } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index a23ec18afe..cf132ed4da 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; -using osu.Game.Localisation; -using osu.Game.Resources.Localisation.Web; using osu.Framework.Graphics; using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Users; @@ -19,8 +17,8 @@ namespace osu.Game.Overlays.Rankings public Bindable Country => countryFilter.Current; - private OverlayRulesetSelector rulesetSelector; - private CountryFilter countryFilter; + private OverlayRulesetSelector rulesetSelector = null!; + private CountryFilter countryFilter = null!; protected override OverlayTitle CreateTitle() => new RankingsTitle(); @@ -39,5 +37,30 @@ namespace osu.Game.Overlays.Rankings Icon = OsuIcon.Ranking; } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(scope => + { + rulesetSelector.FadeTo(showRulesetSelector(scope.NewValue) ? 1 : 0, 200, Easing.OutQuint); + }, true); + + bool showRulesetSelector(RankingsScope scope) + { + switch (scope) + { + case RankingsScope.Performance: + case RankingsScope.Spotlights: + case RankingsScope.Score: + case RankingsScope.Country: + return true; + + default: + return false; + } + } + } } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index ef1691534f..b9f043a233 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -91,7 +92,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio applySuggestion = new RoundedButton { RelativeSizeAxes = Axes.X, - Text = "Apply suggested offset", + Text = AudioSettingsStrings.ApplySuggestedOffset, Action = () => { if (SuggestedOffset.Value.HasValue) @@ -155,8 +156,8 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private void updateHintText() { hintText.Text = SuggestedOffset.Value == null - ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {SuggestedOffset.Value:N0} ms."; + ? AudioSettingsStrings.SuggestedOffsetNote + : AudioSettingsStrings.SuggestedOffsetValueReceived(averageHitErrorHistory.Count, SuggestedOffset.Value.ToLocalisableString(@"N0")); applySuggestion.Enabled.Value = SuggestedOffset.Value != null; } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 049ccedf37..df46e38491 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings }, new SettingsButton { - Text = @"Run latency certifier", + Text = DebugSettingsStrings.RunLatencyCertifier, Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) } }; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 71afec88d4..ce087f1807 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, safeAreaConsiderationsCheckbox = new SettingsCheckbox { - LabelText = "Shrink game to avoid cameras and notches", + LabelText = GraphicsSettingsStrings.ShrinkGameToSafeArea, Current = osuConfig.GetBindable(OsuSetting.SafeAreaConsiderations), }, new SettingsSlider diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 93386de483..d4d06167f1 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -73,9 +73,11 @@ namespace osu.Game.Rulesets.Judgements return HitResult.SmallTickMiss; case HitResult.LargeTickHit: - case HitResult.SliderTailHit: return HitResult.LargeTickMiss; + case HitResult.SliderTailHit: + return HitResult.IgnoreMiss; + default: return HitResult.Miss; } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index b781a13929..4b98df50d7 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The time at which this occurred. - /// Populated when this is applied via . + /// Populated when this is applied via . /// /// /// This is used instead of to check whether this should be reverted. diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 744d02a4fa..3a33d14835 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -66,6 +66,11 @@ namespace osu.Game.Rulesets.Mods /// bool AlwaysValidForSubmission { get; } + /// + /// Whether scores with this mod active can give performance points. + /// + bool Ranked { get; } + /// /// Create a fresh instance based on this mod. /// diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 0500b49513..50c867f41b 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool RequiresConfiguration => false; + /// + /// Whether scores with this mod active can give performance points. + /// + [JsonIgnore] + public virtual bool Ranked => false; + /// /// The mods this mod cannot be enabled with. /// diff --git a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs index 0072c21053..9570cddb0a 100644 --- a/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs +++ b/osu.Game/Rulesets/Mods/ModAccuracyChallenge.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mods public override bool RequiresConfiguration => false; + public override bool Ranked => true; + public override string SettingDescription => base.SettingDescription.Replace(MinimumAccuracy.ToString(), MinimumAccuracy.Value.ToString("##%", NumberFormatInfo.InvariantInfo)); [SettingSource("Minimum accuracy", "Trigger a failure if your accuracy goes below this value.", SettingControlType = typeof(SettingsPercentageSlider))] diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 09b35c249e..359f8a950c 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => null; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Whoaaaaa..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 789291772d..fd5120a767 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModDoubleTime; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Zoooooooooom..."; + public override bool Ranked => SpeedChange.IsDefault; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 0f51e2a6d5..da43a6b294 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModHardRock), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; public virtual void ReadFromDifficulty(BeatmapDifficulty difficulty) { diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index dc2ad6f47e..9227af64b8 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Restricted view area."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public abstract BindableFloat SizeMultiplier { get; } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 8b5dd39584..efdf0d6358 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "Less zoom..."; + public override bool Ranked => SpeedChange.IsDefault; [SettingSource("Speed decrease", "The actual decrease to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(0.75) diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4b2d1d050e..1e99891b99 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) }; + public override bool Ranked => UsesDefaultConfiguration; protected const float ADJUST_RATIO = 1.4f; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 8b25768575..5a1abf115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Mods public override string Acronym => "HD"; public override IconUsage? Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; + public override bool Ranked => UsesDefaultConfiguration; public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 131f501630..3ecd9aa6a1 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "Can you still feel the rhythm without music?"; public override ModType Type => ModType.Fun; public override double ScoreMultiplier => 1; + public override bool Ranked => UsesDefaultConfiguration; } public abstract class ModMuted : ModMuted, IApplicableToDrawableRuleset, IApplicableToTrack, IApplicableToScoreProcessor diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index b42927256c..bb18940f8c 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModNightcore; public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Uguuuuuuuu..."; + public override bool Ranked => UsesDefaultConfiguration; [SettingSource("Speed increase", "The actual increase to apply", SettingControlType = typeof(MultiplierSettingsSlider))] public override BindableNumber SpeedChange { get; } = new BindableDouble(1.5) diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index cc451772b2..1aaef8eac4 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mods public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) }; + public override bool Ranked => UsesDefaultConfiguration; private readonly Bindable showHealthBar = new Bindable(); diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 5b9dfc0430..dd1bd9a719 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.Fun; public override IconUsage? Icon => FontAwesome.Solid.EyeSlash; public override double ScoreMultiplier => 1; + public override bool Ranked => true; /// /// Slightly higher than the cutoff for . diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 0ba40ba070..5bedf443da 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override double ScoreMultiplier => 1; public override LocalisableString Description => "SS or quit."; + public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModSuddenDeath), typeof(ModAccuracyChallenge) }).ToArray(); diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 4e4e8662e8..d07ff6ce87 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyIncrease; public override LocalisableString Description => "Miss and fail."; public override double ScoreMultiplier => 1; + public override bool Ranked => true; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 161537200a..c9192ae3eb 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -683,17 +683,31 @@ namespace osu.Game.Rulesets.Objects.Drawables UpdateResult(false); } + protected void ApplyMaxResult() => ApplyResult((r, _) => r.Type = r.Judgement.MaxResult); + protected void ApplyMinResult() => ApplyResult((r, _) => r.Type = r.Judgement.MinResult); + + protected void ApplyResult(HitResult type) => ApplyResult(static (result, state) => result.Type = state, type); + + [Obsolete("Use overload with state, preferrably with static delegates to avoid allocation overhead.")] // Can be removed 2024-07-26 + protected void ApplyResult(Action application) => ApplyResult((r, _) => application(r), this); + + protected void ApplyResult(Action application) => ApplyResult(application, this); + /// /// Applies the of this , notifying responders such as /// the of the . /// - /// The callback that applies changes to the . - protected void ApplyResult(Action application) + /// The callback that applies changes to the . Using a `static` delegate is recommended to avoid allocation overhead. + /// + /// Use this parameter to pass any data that requires + /// to apply a result, so that it can remain a `static` delegate and thus not allocate. + /// + protected void ApplyResult(Action application, T state) { if (Result.HasResult) throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); - application?.Invoke(Result); + application?.Invoke(Result, state); if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); @@ -738,7 +752,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Checks if a scoring result has occurred for this . /// /// - /// If a scoring result has occurred, this method must invoke to update the result and notify responders. + /// If a scoring result has occurred, this method must invoke to update the result and notify responders. /// /// Whether the user triggered this check. /// The offset from the end time of the at which this check occurred. diff --git a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs index 1b0176cae5..efc10f26e1 100644 --- a/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs +++ b/osu.Game/Rulesets/Objects/Pooling/PooledDrawableWithLifetimeContainer.cs @@ -4,9 +4,11 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Extensions.ListExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Performance; +using osu.Framework.Lists; namespace osu.Game.Rulesets.Objects.Pooling { @@ -35,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Pooling /// /// The enumeration order is undefined. /// - public IEnumerable<(TEntry Entry, TDrawable Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value)); + public readonly SlimReadOnlyDictionaryWrapper AliveEntries; /// /// Whether to remove an entry when clock goes backward and crossed its . @@ -63,6 +65,8 @@ namespace osu.Game.Rulesets.Objects.Pooling lifetimeManager.EntryBecameAlive += entryBecameAlive; lifetimeManager.EntryBecameDead += entryBecameDead; lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary; + + AliveEntries = aliveDrawableMap.AsSlimReadOnly(); } /// diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs index 9fb61c6cd9..6e2852676a 100644 --- a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -13,6 +13,9 @@ namespace osu.Game.Rulesets.Scoring /// /// Calculates the "unstable rate" for a sequence of s. /// + /// + /// Uses Welford's online algorithm. + /// /// /// A non-null value if unstable rate could be calculated, /// and if unstable rate cannot be calculated due to being empty. @@ -21,9 +24,28 @@ namespace osu.Game.Rulesets.Scoring { Debug.Assert(hitEvents.All(ev => ev.GameplayRate != null)); - // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. - double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset / ev.GameplayRate!.Value).ToArray(); - return 10 * standardDeviation(timeOffsets); + int count = 0; + double mean = 0; + double sumOfSquares = 0; + + foreach (var e in hitEvents) + { + if (!affectsUnstableRate(e)) + continue; + + count++; + + // Division by gameplay rate is to account for TimeOffset scaling with gameplay rate. + double currentValue = e.TimeOffset / e.GameplayRate!.Value; + double nextMean = mean + (currentValue - mean) / count; + sumOfSquares += (currentValue - mean) * (currentValue - nextMean); + mean = nextMean; + } + + if (count == 0) + return null; + + return 10.0 * Math.Sqrt(sumOfSquares / count); } /// @@ -44,15 +66,5 @@ namespace osu.Game.Rulesets.Scoring } private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); - - private static double? standardDeviation(double[] timeOffsets) - { - if (timeOffsets.Length == 0) - return null; - - double mean = timeOffsets.Average(); - double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - return Math.Sqrt(squares / timeOffsets.Length); - } } } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 20ec3c4946..b6cfca58db 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -138,7 +138,8 @@ namespace osu.Game.Rulesets.Scoring ComboBreak, /// - /// A special judgement similar to that's used to increase the valuation of the final tick of a slider. + /// A special tick judgement to increase the valuation of the final tick of a slider. + /// The default minimum result is , but may be overridden to . /// [EnumMember(Value = "slider_tail_hit")] [Order(8)] diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index b61e8d9674..177520f28f 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. var candidate = // Use alive entries first as an optimisation. - hitObjectContainer.AliveEntries.Select(tuple => tuple.Entry).Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime) + hitObjectContainer.AliveEntries.Keys.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime) ?? hitObjectContainer.Entries.Where(e => !isAlreadyHit(e)).MinBy(e => e.HitObject.StartTime); // In the case there are no non-judged objects, the last hit object should be used instead. diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 099be486b3..c2879e6d87 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); - public IEnumerable AliveObjects => AliveEntries.Select(pair => pair.Drawable).OrderBy(h => h.HitObject.StartTime); + public IEnumerable AliveObjects => AliveEntries.Values.OrderBy(h => h.HitObject.StartTime); /// /// Invoked when a is judged. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 39ddb5c753..4e72291b9c 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // We are not using AliveObjects directly to avoid selection/sorting overhead since we don't care about the order at which positions will be updated. foreach (var entry in AliveEntries) { - var obj = entry.Drawable; + var obj = entry.Value; updatePosition(obj, Time.Current); diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index ac6403bb34..e842f8c436 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -126,7 +126,13 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components case NotifyCollectionChangedAction.Remove: Debug.Assert(args.OldItems != null); - removeRooms(args.OldItems.Cast()); + // clear operations have a separate path that benefits from async disposal, + // since disposing is quite expensive when performed on a high number of drawables synchronously. + if (args.OldItems.Count == roomFlow.Count) + clearRooms(); + else + removeRooms(args.OldItems.Cast()); + break; } } @@ -146,11 +152,20 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components roomFlow.RemoveAll(d => d.Room == r, true); // selection may have a lease due to being in a sub screen. - if (!SelectedRoom.Disabled) + if (SelectedRoom.Value == r && !SelectedRoom.Disabled) SelectedRoom.Value = null; } } + private void clearRooms() + { + roomFlow.Clear(); + + // selection may have a lease due to being in a sub screen. + if (!SelectedRoom.Disabled) + SelectedRoom.Value = null; + } + private void updateSorting() { foreach (var room in roomFlow) diff --git a/osu.Game/Screens/Play/FailAnimationContainer.cs b/osu.Game/Screens/Play/FailAnimationContainer.cs index 821c67e3cb..ebb0d77726 100644 --- a/osu.Game/Screens/Play/FailAnimationContainer.cs +++ b/osu.Game/Screens/Play/FailAnimationContainer.cs @@ -198,8 +198,10 @@ namespace osu.Game.Screens.Play foreach (var nested in playfield.NestedPlayfields) applyToPlayfield(nested); - foreach (DrawableHitObject obj in playfield.HitObjectContainer.AliveObjects) + foreach (var entry in playfield.HitObjectContainer.AliveEntries) { + var obj = entry.Value; + if (appliedObjects.Contains(obj)) continue; diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 0aff98df2b..d209c305fa 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -259,10 +260,15 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy if (isFailedSDueToMisses) AddInternal(failedSRankText = new RankText(ScoreRank.S)); + var applauseSamples = new List { applauseSampleName }; + if (score.Rank >= ScoreRank.B) + // when rank is B or higher, play legacy applause sample on legacy skins. + applauseSamples.Insert(0, @"applause"); + AddRangeInternal(new Drawable[] { rankImpactSound = new PoolableSkinnableSample(new SampleInfo(impactSampleName)), - rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(@"applause", applauseSampleName)), + rankApplauseSound = new PoolableSkinnableSample(new SampleInfo(applauseSamples.ToArray())), scoreTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/score-tick")), badgeTickSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink")), badgeMaxSound = new PoolableSkinnableSample(new SampleInfo(@"Results/badge-dink-max")), @@ -351,24 +357,28 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy int badgeNum = 0; - foreach (var badge in badges) + if (score.Rank != ScoreRank.F) { - if (badge.Accuracy > score.Accuracy) - continue; - - using (BeginDelayedSequence(inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) + foreach (var badge in badges) { - badge.Appear(); + if (badge.Accuracy > score.Accuracy) + continue; - if (withFlair) + using (BeginDelayedSequence( + inverseEasing(ACCURACY_TRANSFORM_EASING, Math.Min(accuracyX - virtual_ss_percentage, badge.Accuracy) / targetAccuracy) * ACCURACY_TRANSFORM_DURATION)) { - Schedule(() => - { - var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + badge.Appear(); - dink.FrequencyTo(1 + badgeNum++ * 0.05); - dink.Play(); - }); + if (withFlair) + { + Schedule(() => + { + var dink = badgeNum < badges.Count - 1 ? badgeTickSound : badgeMaxSound; + + dink.FrequencyTo(1 + badgeNum++ * 0.05); + dink.Play(); + }); + } } } } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 69782c25bb..5685910c0a 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -1,17 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; using osu.Game.Graphics; @@ -19,6 +19,7 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Utils; namespace osu.Game.Screens.Select @@ -31,26 +32,26 @@ namespace osu.Game.Screens.Select set => modDisplay.Current = value; } - protected readonly OsuSpriteText MultiplierText; + protected OsuSpriteText MultiplierText { get; private set; } = null!; + protected Container UnrankedBadge { get; private set; } = null!; + private readonly ModDisplay modDisplay; + + private ModSettingChangeTracker? modSettingChangeTracker; + private Color4 lowMultiplierColour; private Color4 highMultiplierColour; public FooterButtonMods() { - ButtonContentContainer.Add(modDisplay = new ModDisplay + // must be created in ctor for correct operation of `Current`. + modDisplay = new ModDisplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(0.8f), ExpansionMode = ExpansionMode.AlwaysContracted, - }); - ButtonContentContainer.Add(MultiplierText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - }); + }; } [BackgroundDependencyLoader] @@ -62,10 +63,43 @@ namespace osu.Game.Screens.Select highMultiplierColour = colours.Green; Text = @"mods"; Hotkey = GlobalAction.ToggleModSelection; - } - [CanBeNull] - private ModSettingChangeTracker modSettingChangeTracker; + ButtonContentContainer.AddRange(new Drawable[] + { + modDisplay, + MultiplierText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + }, + UnrankedBadge = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colours.Gray2, + Padding = new MarginPadding(5), + UseFullGlyphHeight = false, + Text = ModSelectOverlayStrings.Unranked.ToLower() + } + } + }, + }); + } protected override void LoadComplete() { @@ -101,6 +135,9 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); + + bool anyUnrankedMods = Current.Value?.Any(m => !m.Ranked) == true; + UnrankedBadge.FadeTo(anyUnrankedMods ? 1 : 0); }); } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index cfa5f972d2..816cfc0a2d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -58,6 +58,9 @@ namespace osu.Game.Skinning { } + protected override IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) + => new LegacyTextureLoaderStore(base.CreateTextureLoaderStore(resources, storage)); + protected override void ParseConfigurationStream(Stream stream) { base.ParseConfigurationStream(stream); diff --git a/osu.Game/Skinning/LegacyTextureLoaderStore.cs b/osu.Game/Skinning/LegacyTextureLoaderStore.cs new file mode 100644 index 0000000000..29206bbb85 --- /dev/null +++ b/osu.Game/Skinning/LegacyTextureLoaderStore.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Processing; + +namespace osu.Game.Skinning +{ + public class LegacyTextureLoaderStore : IResourceStore + { + private readonly IResourceStore? wrappedStore; + + public LegacyTextureLoaderStore(IResourceStore? wrappedStore) + { + this.wrappedStore = wrappedStore; + } + + public TextureUpload Get(string name) + { + var textureUpload = wrappedStore?.Get(name); + + if (textureUpload == null) + return null!; + + return shouldConvertToGrayscale(name) + ? convertToGrayscale(textureUpload) + : textureUpload; + } + + public Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) + { + var textureUpload = wrappedStore?.Get(name); + + if (textureUpload == null) + return null!; + + return shouldConvertToGrayscale(name) + ? Task.Run(() => convertToGrayscale(textureUpload), cancellationToken) + : Task.FromResult(textureUpload); + } + + // https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Graphics/Textures/TextureManager.cs#L91-L96 + private static readonly string[] grayscale_sprites = + { + @"taiko-bar-right", + @"taikobigcircle", + @"taikohitcircle", + @"taikohitcircleoverlay" + }; + + private bool shouldConvertToGrayscale(string name) + { + foreach (string grayscaleSprite in grayscale_sprites) + { + // unfortunately at this level of lookup we can encounter `@2x` scale suffixes in the name, + // so straight equality cannot be used. + if (name.Equals(grayscaleSprite, StringComparison.OrdinalIgnoreCase) + || name.Equals($@"{grayscaleSprite}@2x", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + private TextureUpload convertToGrayscale(TextureUpload textureUpload) + { + var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); + + // stable uses `0.299 * r + 0.587 * g + 0.114 * b` + // (https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/Graphics/Textures/pTexture.cs#L138-L153) + // which matches mode BT.601 (https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems) + image.Mutate(i => i.Grayscale(GrayscaleMode.Bt601)); + + return new TextureUpload(image); + } + + public Stream? GetStream(string name) => wrappedStore?.GetStream(name); + + public IEnumerable GetAvailableResources() => wrappedStore?.GetAvailableResources() ?? Array.Empty(); + + public void Dispose() + { + wrappedStore?.Dispose(); + } + } +} diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 190748137a..8f13e0f42a 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osuTK; @@ -92,7 +93,7 @@ namespace osu.Game.Updater public UpdateCompleteNotification(string version) { this.version = version; - Text = $"You are now running osu! {version}.\nClick to see what's new!"; + Text = NotificationsStrings.GameVersionAfterUpdate(version); } [BackgroundDependencyLoader] @@ -114,7 +115,7 @@ namespace osu.Game.Updater { public UpdateApplicationCompleteNotification() { - Text = @"Update ready to install. Click to restart!"; + Text = NotificationsStrings.UpdateReadyToInstall; } } @@ -166,13 +167,13 @@ namespace osu.Game.Updater { State = ProgressNotificationState.Active; Progress = 0; - Text = @"Downloading update..."; + Text = NotificationsStrings.DownloadingUpdate; } public void StartInstall() { Progress = 0; - Text = @"Installing update..."; + Text = NotificationsStrings.InstallingUpdate; } public void FailDownload() diff --git a/osu.Game/Users/UserGridPanel.cs b/osu.Game/Users/UserGridPanel.cs index fe3435c248..fce543415d 100644 --- a/osu.Game/Users/UserGridPanel.cs +++ b/osu.Game/Users/UserGridPanel.cs @@ -35,98 +35,84 @@ namespace osu.Game.Users { FillFlowContainer details; - var layout = new Container + var layout = new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(margin), - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, margin), - new Dimension() - }, - Content = new[] - { - new Drawable[] + CreateAvatar().With(avatar => { - CreateAvatar().With(avatar => + avatar.Size = new Vector2(60); + avatar.Masking = true; + avatar.CornerRadius = 6; + avatar.Margin = new MarginPadding { Bottom = margin }; + }), + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = margin, Bottom = margin }, + ColumnDimensions = new[] { - avatar.Size = new Vector2(60); - avatar.Masking = true; - avatar.CornerRadius = 6; - }), - new Container + new Dimension() + }, + RowDimensions = new[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = margin }, - Child = new GridContainer + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + details = new FillFlowContainer { - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6), + Children = new Drawable[] { - details = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(6), - Children = new Drawable[] - { - CreateFlag(), - // supporter icon is being added later - } - } - }, - new Drawable[] - { - CreateUsername().With(username => - { - username.Anchor = Anchor.CentreLeft; - username.Origin = Anchor.CentreLeft; - }) + CreateFlag(), + // supporter icon is being added later } } + }, + new Drawable[] + { + CreateUsername().With(username => + { + username.Anchor = Anchor.CentreLeft; + username.Origin = Anchor.CentreLeft; + }) } } - }, - new[] - { - // padding - Empty(), - Empty() - }, - new Drawable[] - { - CreateStatusIcon().With(icon => - { - icon.Anchor = Anchor.Centre; - icon.Origin = Anchor.Centre; - }), - CreateStatusMessage(false).With(message => - { - message.Anchor = Anchor.CentreLeft; - message.Origin = Anchor.CentreLeft; - message.Margin = new MarginPadding { Left = margin }; - }) } + }, + new Drawable[] + { + CreateStatusIcon().With(icon => + { + icon.Anchor = Anchor.Centre; + icon.Origin = Anchor.Centre; + }), + CreateStatusMessage(false).With(message => + { + message.Anchor = Anchor.CentreLeft; + message.Origin = Anchor.CentreLeft; + message.Margin = new MarginPadding { Left = margin }; + }) } } }; diff --git a/osu.Game/Users/UserRankPanel.cs b/osu.Game/Users/UserRankPanel.cs index a38962dfc7..84ff3114fc 100644 --- a/osu.Game/Users/UserRankPanel.cs +++ b/osu.Game/Users/UserRankPanel.cs @@ -81,117 +81,95 @@ namespace osu.Game.Users }, new GridContainer { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(padding), ColumnDimensions = new[] { - new Dimension(GridSizeMode.Absolute, padding), new Dimension(GridSizeMode.AutoSize), new Dimension(), - new Dimension(GridSizeMode.Absolute, padding), }, RowDimensions = new[] { - new Dimension(GridSizeMode.Absolute, padding), - new Dimension(GridSizeMode.AutoSize), + new Dimension() }, Content = new[] { - new[] + new Drawable[] { - // padding - Empty(), - Empty(), - Empty(), - Empty() - }, - new[] - { - Empty(), // padding CreateAvatar().With(avatar => { avatar.Size = new Vector2(60); avatar.Masking = true; avatar.CornerRadius = 6; }), - new Container + new GridContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = padding }, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + new Dimension() + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] { - new Dimension() - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] + details = new FillFlowContainer { - details = new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6), + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(6), - Children = new Drawable[] - { - CreateFlag(), - // supporter icon is being added later - } + CreateFlag(), + // supporter icon is being added later } - }, - new Drawable[] - { - CreateUsername().With(username => - { - username.Anchor = Anchor.CentreLeft; - username.Origin = Anchor.CentreLeft; - }) } + }, + new Drawable[] + { + CreateUsername().With(username => + { + username.Anchor = Anchor.CentreLeft; + username.Origin = Anchor.CentreLeft; + }) } } - }, - Empty() // padding + } } } } } }, - new Container + new GridContainer { Name = "Bottom content", Margin = new MarginPadding { Top = main_content_height }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = 80, Vertical = padding }, - Child = new GridContainer + ColumnDimensions = new[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] + new Dimension(), + new Dimension() + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new Drawable[] { - new Dimension(), - new Dimension() - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - Content = new[] - { - new Drawable[] + globalRankDisplay = new ProfileValueDisplay(true) { - globalRankDisplay = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankGlobalSimple, - }, - countryRankDisplay = new ProfileValueDisplay(true) - { - Title = UsersStrings.ShowRankCountrySimple, - } + Title = UsersStrings.ShowRankGlobalSimple, + }, + countryRankDisplay = new ProfileValueDisplay(true) + { + Title = UsersStrings.ShowRankCountrySimple, } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a5d212bffe..935b759e4d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Library true 10 @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index bd6891f448..a4cd26a372 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 2d61b73125..19c0c610b5 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,9 +1,8 @@  - net6.0-ios + net8.0-ios 13.4 Exe - true 0.1.0 $(Version) $(Version) @@ -16,4 +15,7 @@ + + + diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e2e28c38ec..452f90ecea 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1042,4 +1042,5 @@ private void load() True True True + True True