diff --git a/.editorconfig b/.editorconfig
index b5333ad8e7..8cdb92d11c 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = false:none
-csharp_style_prefer_range_operator = false:none
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
diff --git a/.gitignore b/.gitignore
index e6b5db5904..732b171f69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -331,3 +331,6 @@ fastlane/report.xml
# inspectcode
inspectcodereport.xml
inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
new file mode 100644
index 0000000000..1815c271b4
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 5372b6f28a..a4154623b6 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index 45a94f37c0..080dc04001 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 1f09381e08..3de6a7e609 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index ba530f0ddf..da14c2a29e 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
index 89d5b45f67..45d1ce25e9 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
index e2628a1bb4..ba80f7c100 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
index f1d0957b8e..911c3ed9b7 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
index 23b49abcad..ec3c81f4cd 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5940df2191..6480612b2e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,18 +1,19 @@
{
"version": "0.2.0",
- "configurations": [{
+ "configurations": [
+ {
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -23,13 +24,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -40,29 +41,30 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
- }, {
+ },
+ {
"name": "osu! (Tests, Release)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -73,14 +75,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -91,14 +93,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -109,14 +111,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -127,18 +129,31 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
},
+ {
+ "name": "Benchmark",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll",
+ "args": [
+ "--filter",
+ "*"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build benchmarks",
+ "console": "internalConsole"
+ },
{
"name": "Cake: Debug Script",
"type": "coreclr",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 04ff7c1bea..e638dec767 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,7 +2,8 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
- "tasks": [{
+ "tasks": [
+ {
"label": "Build osu! (Debug)",
"type": "shell",
"command": "dotnet",
@@ -78,7 +79,8 @@
],
"group": "build",
"problemMatcher": "$msCompile"
- }, {
+ },
+ {
"label": "Build tournament tests (Release)",
"type": "shell",
"command": "dotnet",
@@ -95,7 +97,23 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Restore (netcoreapp3.0)",
+ "label": "Build benchmarks",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Benchmarks",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Restore (netcoreapp3.1)",
"type": "shell",
"command": "dotnet",
"args": [
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index 5d86b99fd7..a92191a439 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -1,5 +1,6 @@
M:System.Object.Equals(System.Object,System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead.
M:System.Object.Equals(System.Object)~System.Boolean;Don't use object.Equals. Use IEquatable or EqualityComparer.Default instead.
M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(Fallbacks to ValueType). Use IEquatable or EqualityComparer.Default instead.
+M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
new file mode 100644
index 0000000000..d497365f87
--- /dev/null
+++ b/CodeAnalysis/osu.ruleset
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index c8d441a78c..27a0bd0d48 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,13 @@
-
+
+
+
+ $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
+
true
$(NoWarn);CS1591
diff --git a/README.md b/README.md
index e2e854c755..67027bb9f3 100644
--- a/README.md
+++ b/README.md
@@ -21,9 +21,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
-- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
+- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
-- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
@@ -35,7 +35,7 @@ If you are not interested in developing the game, you can still consume our [bin
**Latest build:**
-| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place.
diff --git a/build/InspectCode.cake b/build/InspectCode.cake
index bd3fdf5f93..06c56dce87 100644
--- a/build/InspectCode.cake
+++ b/build/InspectCode.cake
@@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.0"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
diff --git a/global.json b/global.json
index 43bb34912a..6858d4044d 100644
--- a/global.json
+++ b/global.json
@@ -1,4 +1,9 @@
{
+ "sdk": {
+ "allowPrerelease": false,
+ "rollForward": "minor",
+ "version": "3.1.100"
+ },
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24"
}
diff --git a/osu.Android.props b/osu.Android.props
index efaf7241cb..f3838644d1 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -53,7 +53,7 @@
-
-
+
+
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
index e6b6446f72..d2c14d321a 100644
--- a/osu.Desktop.slnf
+++ b/osu.Desktop.slnf
@@ -3,6 +3,7 @@
"path": "osu.sln",
"projects": [
"osu.Desktop\\osu.Desktop.csproj",
+ "osu.Game.Benchmarks\\osu.Game.Benchmarks.csproj",
"osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj",
"osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
"osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj",
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
new file mode 100644
index 0000000000..08cc0e7f5f
--- /dev/null
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -0,0 +1,149 @@
+// 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.Text;
+using DiscordRPC;
+using DiscordRPC.Message;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Online.API;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using LogLevel = osu.Framework.Logging.LogLevel;
+using User = osu.Game.Users.User;
+
+namespace osu.Desktop
+{
+ internal class DiscordRichPresence : Component
+ {
+ private const string client_id = "367827983903490050";
+
+ private DiscordRpcClient client;
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
+ private Bindable user;
+
+ private readonly IBindable status = new Bindable();
+ private readonly IBindable activity = new Bindable();
+
+ private readonly RichPresence presence = new RichPresence
+ {
+ Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider provider)
+ {
+ client = new DiscordRpcClient(client_id)
+ {
+ SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
+ };
+
+ client.OnReady += onReady;
+
+ // safety measure for now, until we performance test / improve backoff for failed connections.
+ client.OnConnectionFailed += (_, __) => client.Deinitialize();
+
+ client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+
+ (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
+ {
+ status.UnbindBindings();
+ status.BindTo(u.NewValue.Status);
+
+ activity.UnbindBindings();
+ activity.BindTo(u.NewValue.Activity);
+ }, true);
+
+ ruleset.BindValueChanged(_ => updateStatus());
+ status.BindValueChanged(_ => updateStatus());
+ activity.BindValueChanged(_ => updateStatus());
+
+ client.Initialize();
+ }
+
+ private void onReady(object _, ReadyMessage __)
+ {
+ Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
+ updateStatus();
+ }
+
+ private void updateStatus()
+ {
+ if (!client.IsInitialized)
+ return;
+
+ if (status.Value is UserStatusOffline)
+ {
+ client.ClearPresence();
+ return;
+ }
+
+ if (status.Value is UserStatusOnline && activity.Value != null)
+ {
+ presence.State = truncate(activity.Value.Status);
+ presence.Details = truncate(getDetails(activity.Value));
+ }
+ else
+ {
+ presence.State = "Idle";
+ presence.Details = string.Empty;
+ }
+
+ // update user information
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+
+ // update ruleset
+ presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
+ presence.Assets.SmallImageText = ruleset.Value.Name;
+
+ client.SetPresence(presence);
+ }
+
+ private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
+
+ private string truncate(string str)
+ {
+ if (Encoding.UTF8.GetByteCount(str) <= 128)
+ return str;
+
+ ReadOnlyMemory strMem = str.AsMemory();
+
+ do
+ {
+ strMem = strMem[..^1];
+ } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
+
+ return string.Create(strMem.Length + 1, strMem, (span, mem) =>
+ {
+ mem.Span.CopyTo(span);
+ span[^1] = '…';
+ });
+ }
+
+ private string getDetails(UserActivity activity)
+ {
+ switch (activity)
+ {
+ case UserActivity.SoloGame solo:
+ return solo.Beatmap.ToString();
+
+ case UserActivity.Editing edit:
+ return edit.Beatmap.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ client.Dispose();
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 66e7bb381c..f70cc24159 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -60,6 +60,8 @@ namespace osu.Desktop
else
Add(new SimpleUpdateManager());
}
+
+ LoadComponentAsync(new DiscordRichPresence(), Add);
}
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
diff --git a/osu.Desktop/Properties/launchSettings.json b/osu.Desktop/Properties/launchSettings.json
new file mode 100644
index 0000000000..5e768ec9fa
--- /dev/null
+++ b/osu.Desktop/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "osu! Desktop": {
+ "commandName": "Project"
+ },
+ "osu! Tournament": {
+ "commandName": "Project",
+ "commandLineArgs": "--tournament"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest
new file mode 100644
index 0000000000..2e9127bf44
--- /dev/null
+++ b/osu.Desktop/app.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 453cf6f94d..da47ad8223 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,6 +1,6 @@
- netcoreapp3.0
+ netcoreapp3.1
WinExe
true
click the circles. to the beat.
@@ -8,6 +8,7 @@
osu!lazer
osu!lazer
lazer.ico
+ app.manifest
0.0.0
0.0.0
@@ -23,11 +24,12 @@
-
+
-
+
+
diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
new file mode 100644
index 0000000000..394fd75488
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.IO.Stores;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Resources;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkBeatmapParsing : BenchmarkTest
+ {
+ private readonly MemoryStream beatmapStream = new MemoryStream();
+
+ public override void SetUp()
+ {
+ using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
+ using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
+ using (var reader = new ZipArchiveReader(archive))
+ reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
+ }
+
+ [Benchmark]
+ public Beatmap BenchmarkBundledBeatmap()
+ {
+ beatmapStream.Seek(0, SeekOrigin.Begin);
+ var reader = new LineBufferedReader(beatmapStream); // no disposal
+
+ var decoder = Decoder.GetDecoder(reader);
+ return decoder.Decode(reader);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs
new file mode 100644
index 0000000000..34f5edd084
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkTest.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using NUnit.Framework;
+
+namespace osu.Game.Benchmarks
+{
+ [TestFixture]
+ [MemoryDiagnoser]
+ public abstract class BenchmarkTest
+ {
+ [GlobalSetup]
+ [OneTimeSetUp]
+ public virtual void SetUp()
+ {
+ }
+
+ [Test]
+ public void RunBenchmark() => BenchmarkRunner.Run(GetType());
+ }
+}
diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs
new file mode 100644
index 0000000000..c55075fea6
--- /dev/null
+++ b/osu.Game.Benchmarks/Program.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Running;
+
+namespace osu.Game.Benchmarks
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(args);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/Properties/launchSettings.json b/osu.Game.Benchmarks/Properties/launchSettings.json
new file mode 100644
index 0000000000..c1868088f9
--- /dev/null
+++ b/osu.Game.Benchmarks/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "All Benchmarks": {
+ "commandName": "Project",
+ "commandLineArgs": "--filter *"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
new file mode 100644
index 0000000000..f2e1c0ec3b
--- /dev/null
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp3.1
+ Exe
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
index beca477943..f7f07ef938 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Catch.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
index 4030d2d9e7..67d27c33eb 100644
--- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 493ac7ae39..f4749be370 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index ab3c040b4e..74a9c05bf9 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 0369b6db4e..1eb913e900 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Tests
RelativeSizeAxes = Axes.Both,
Children = new[]
{
- drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap, Array.Empty())
+ drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo))
}
});
@@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void addToPlayfield(DrawableCatchHitObject drawable)
{
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawableRuleset.Playfield.Add(drawable);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
index f6d26addaa..8c3dfef39c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
namespace osu.Game.Rulesets.Catch.Tests
@@ -12,9 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
- public TestSceneDrawableHitObjectsHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new CatchModHidden() };
- }
+ SelectedMods.Value = new[] { new CatchModHidden() };
+ });
}
}
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 1dbe9b39ee..9559d13328 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
@@ -9,7 +9,7 @@
WinExe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index b5497ea89f..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -4,7 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -14,12 +14,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapConverter : BeatmapConverter
{
- public CatchBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public CatchBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 71d68ace94..e5c3647f99 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -16,15 +16,22 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using System;
namespace osu.Game.Rulesets.Catch
{
- public class CatchRuleset : Ruleset
+ public class CatchRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits";
@@ -51,7 +58,9 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new CatchModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -98,10 +107,16 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new CatchModAutoplay(), new ModCinema()),
+ new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
new CatchModRelax(),
};
@@ -112,7 +127,7 @@ namespace osu.Game.Rulesets.Catch
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -120,19 +135,16 @@ namespace osu.Game.Rulesets.Catch
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Catching fruit";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 2;
+ public int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
-
- public CatchRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index 3c237c86be..a6283eb7c4 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -34,12 +34,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
mods = Score.Mods;
- var legacyScore = Score as LegacyScoreInfo;
-
- fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect];
- ticksHit = legacyScore?.Count100 ?? 0;
- tinyTicksHit = legacyScore?.Count50 ?? 0;
- tinyTicksMissed = legacyScore?.CountKatu ?? 0;
+ fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
+ ticksHit = Score?.GetCount100() ?? 0;
+ tinyTicksHit = Score?.GetCount50() ?? 0;
+ tinyTicksMissed = Score?.GetCountKatu() ?? 0;
misses = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
index 374dd50c11..fc030877f1 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 0;
case HitResult.Perfect:
- return 0.008;
+ return 0.01;
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
index f1399bb5c0..e87ecba749 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -18,17 +18,5 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 30;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return base.HealthIncreaseFor(result);
-
- case HitResult.Perfect:
- return 0.007;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index 8fd9ac92ba..2149ed9712 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -23,18 +23,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return -0.02;
-
- case HitResult.Perfect:
- return 0.01;
- }
- }
-
///
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
index 3829b5e94f..d607b49ea4 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 0;
case HitResult.Perfect:
- return 0.004;
+ return 0.02;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index c721ff862a..46e427e1b7 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
- private const uint y = 842502087;
- private const uint z = 3579807591;
- private const uint w = 273326509;
- private uint _x, _y = y, _z = z, _w = w;
+ private const uint y_initial = 842502087;
+ private const uint z_initial = 3579807591;
+ private const uint w_initial = 273326509;
+ private uint x, y = y_initial, z = z_initial, w = w_initial;
public FastRandom(int seed)
{
- _x = (uint)seed;
+ x = (uint)seed;
}
public FastRandom()
@@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = _x ^ (_x << 11);
- _x = _y;
- _y = _z;
- _z = _w;
- return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
+ uint t = x ^ (x << 11);
+ x = y;
+ y = z;
+ z = w;
+ return w = w ^ (w >> 19) ^ t ^ (t >> 8);
}
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
new file mode 100644
index 0000000000..3bc1ee5bf5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
+ Replay = new CatchAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
new file mode 100644
index 0000000000..8377b3786a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Fruit Size", "Override a beatmap's set CS.")]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.")]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, difficulty.ApproachRate);
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
index da2edcee44..c07087efaf 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModNightcore : ModNightcore
+ public class CatchModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.06;
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index 0454bc969d..a47efcc10a 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -1,12 +1,48 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
+using osu.Framework.Input;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using osuTK;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModRelax : ModRelax
+ public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset
{
public override string Description => @"Use the mouse to control the catcher.";
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
+ }
+
+ private class MouseInputHelper : Drawable, IKeyBindingHandler, IRequireHighFrequencyMousePosition
+ {
+ private readonly CatcherArea.Catcher catcher;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
+
+ public MouseInputHelper(CatchPlayfield playfield)
+ {
+ catcher = playfield.CatcherArea.MovableCatcher;
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ //disable keyboard controls
+ public bool OnPressed(CatchAction action) => true;
+ public bool OnReleased(CatchAction action) => true;
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ catcher.UpdatePosition(e.MousePosition.X / DrawSize.X);
+ return base.OnMouseMove(e);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 958cd19d50..53a018c9f4 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 33780427b6..a4ed966abb 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -116,7 +116,23 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Duration => EndTime - StartTime;
- public SliderPath Path { get; set; }
+ private readonly SliderPath path = new SliderPath();
+
+ public SliderPath Path
+ {
+ get => path;
+ set
+ {
+ path.ControlPoints.Clear();
+ path.ExpectedDistance.Value = null;
+
+ if (value != null)
+ {
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
+ path.ExpectedDistance.Value = value.ExpectedDistance.Value;
+ }
+ }
+ }
public double Distance => Path.Distance;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 6c8515eb90..4649dcae90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -3,7 +3,7 @@
using System;
using System.Linq;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 22532bc9ec..f122588a2b 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Input.StateChanges;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 18785d65ea..4c7bc4ab73 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,42 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
- public class CatchScoreProcessor : ScoreProcessor
+ public class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- {
- switch (result.Type)
- {
- case HitResult.Miss:
- return hpDrainRate;
-
- default:
- return 10.2 - hpDrainRate; // Award less HP as drain rate is increased
- }
- }
-
public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index b6d8cf9cbe..589503c35b 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
+using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
@@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea;
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos);
+
public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation)
{
Container explodingFruitContainer;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 49393a836f..0c8c483048 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -7,7 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
@@ -377,8 +377,7 @@ namespace osu.Game.Rulesets.Catch.UI
double dashModifier = Dashing ? 1 : 0.5;
double speed = BASE_SPEED * dashModifier * hyperDashModifier;
- Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
- X = (float)Math.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
+ UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
// Correct overshooting.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
@@ -452,6 +451,17 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.LifetimeStart = Time.Current;
fruit.Expire();
}
+
+ public void UpdatePosition(float position)
+ {
+ position = Math.Clamp(position, 0, 1);
+
+ if (position == X)
+ return;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
+ X = position;
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index e3c6c93d01..025fa9c56e 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
+ InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 6b7f00c5d0..fdd820b891 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -10,10 +10,8 @@ using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
-using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -25,15 +23,13 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
- public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
-
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
index 0362402320..c381ea585d 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Mania.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
index 779eb4f277..0811c2724c 100644
--- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 12865385b6..d0ff1fab43 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
new file mode 100644
index 0000000000..80b1b3df8e
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public abstract class SkinnableTestScene : OsuGridTestScene
+ {
+ private Skin defaultSkin;
+
+ protected SkinnableTestScene()
+ : base(1, 2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, SkinManager skinManager)
+ {
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = createProvider(null, creationFunction);
+ Cell(1).Child = createProvider(defaultSkin, creationFunction);
+ }
+
+ private Drawable createProvider(Skin skin, Func creationFunction)
+ {
+ var mainProvider = new SkinProvidingContainer(skin);
+
+ return mainProvider
+ .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ {
+ Child = creationFunction()
+ });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..eea1a36a19
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,37 @@
+// 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.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableManiaJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
new file mode 100644
index 0000000000..7b0cf40d45
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -0,0 +1,314 @@
+// 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 NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
+ {
+ private const double time_before_head = 250;
+ private const double time_head = 1500;
+ private const double time_during_hold_1 = 2500;
+ private const double time_tail = 4000;
+ private const double time_after_tail = 5250;
+
+ private List judgementResults;
+ private bool allJudgedFired;
+
+ ///
+ /// -----[ ]-----
+ /// o o
+ ///
+ [Test]
+ public void TestNoInput()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ assertNoteJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAtStartAndBreak()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_tail, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail + 10),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ private void assertHeadJudgement(HitResult result)
+ => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
+
+ private void assertTailJudgement(HitResult result)
+ => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result);
+
+ private void assertNoteJudgement(HitResult result)
+ => AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result);
+
+ private void assertTickJudgement(HitResult result)
+ => AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick
+
+ private ScoreAccessibleReplayPlayer currentPlayer;
+
+ private void performTest(List frames)
+ {
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects =
+ {
+ new HoldNote
+ {
+ StartTime = time_head,
+ Duration = time_tail - time_head,
+ Column = 0,
+ }
+ },
+ BeatmapInfo =
+ {
+ BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ });
+
+ Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ p.ScoreProcessor.AllJudged += () =>
+ {
+ if (currentPlayer == p) allJudgedFired = true;
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ allJudgedFired = false;
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for all judged", () => allJudgedFired);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
new file mode 100644
index 0000000000..cd25d162d0
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestScenePlayer : PlayerTestScene
+ {
+ public TestScenePlayer()
+ : base(new ManiaRuleset())
+ {
+ }
+ }
+}
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 8fc4dbfe72..dea6e6c0fb 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
@@ -9,7 +9,7 @@
WinExe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9069c09ae4..d904474815 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Linq;
using System.Collections.Generic;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private const int max_notes_for_density = 7;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
-
public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset;
@@ -37,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private ManiaBeatmap beatmap;
- public ManiaBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public ManiaBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
+ IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
@@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
}
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject);
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime);
- density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
+ density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
}
private double lastTime;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 9565ac8994..315ef96e49 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index b99bddee96..3f7a2baedd 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -37,12 +37,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
- countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
- countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
- countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
- countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
- countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
- countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+ countPerfect = Score.Statistics[HitResult.Perfect];
+ countGreat = Score.Statistics[HitResult.Great];
+ countGood = Score.Statistics[HitResult.Good];
+ countOk = Score.Statistics[HitResult.Ok];
+ countMeh = Score.Statistics[HitResult.Meh];
+ countMiss = Score.Statistics[HitResult.Miss];
if (mods.Any(m => !m.Ranked))
return 0;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
index 059cd39641..4f7ab87fad 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
@@ -5,7 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
try
{
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
index ed25173d38..bbbb93fd8b 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
@@ -4,7 +4,7 @@
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
double holdFactor = 1.0; // Factor in case something else is held
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
index acce41db6f..4e73883de0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
if (DrawableObject.IsLoaded)
{
- DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail;
+ DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
Anchor = note.Anchor;
Origin = note.Origin;
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index 97d8aaa052..445df79f6f 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 0bfe6f9517..1632b6a583 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit
public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns;
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
{
drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index b9c6e3a7f7..00b839f8ec 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -15,11 +15,11 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
switch (result)
{
- case HitResult.Miss:
+ default:
return 0;
- default:
- return 0.040;
+ case HitResult.Perfect:
+ return 0.01;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
index 0e4c811945..c2f8fb8678 100644
--- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
@@ -29,32 +29,5 @@ namespace osu.Game.Rulesets.Mania.Judgements
return 300;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- case HitResult.Miss:
- return -0.125;
-
- case HitResult.Meh:
- return 0.005;
-
- case HitResult.Ok:
- return 0.010;
-
- case HitResult.Good:
- return 0.035;
-
- case HitResult.Great:
- return 0.055;
-
- case HitResult.Perfect:
- return 0.065;
-
- default:
- return 0;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index c74a292331..02c2158383 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -25,20 +25,30 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
{
- public class ManiaRuleset : Ruleset
+ public class ManiaRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
+
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
public const string SHORT_NAME = "mania";
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
+
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
@@ -51,7 +61,9 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new ManiaModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -143,12 +155,13 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
+ new ManiaModDifficultyAdjust(),
};
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new ManiaModAutoplay(), new ModCinema()),
+ new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()),
};
case ModType.Fun:
@@ -158,7 +171,7 @@ namespace osu.Game.Rulesets.Mania
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -166,11 +179,13 @@ namespace osu.Game.Rulesets.Mania
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Smashing keys";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
- public override int? LegacyID => 3;
+ public int LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
@@ -178,11 +193,6 @@ namespace osu.Game.Rulesets.Mania
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
- public ManiaRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
-
public override IEnumerable AvailableVariants
{
get
@@ -268,7 +278,7 @@ namespace osu.Game.Rulesets.Mania
return stage1Bindings.Concat(stage2Bindings);
}
- return new KeyBinding[0];
+ return Array.Empty();
}
public override string GetVariantName(int variant)
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
new file mode 100644
index 0000000000..02c1fc1b79
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
+ Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
new file mode 100644
index 0000000000..0817f8f9fc
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index 2d94fb6af5..4cc712060c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModNightcore : ModNightcore
+ public class ManiaModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1;
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 9275371a61..b12d3a7a70 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -4,7 +4,7 @@
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 87b9633c80..155adb958b 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@@ -21,11 +20,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
- public DrawableNote Head => headContainer.Child;
- public DrawableNote Tail => tailContainer.Child;
+ public DrawableHoldNoteHead Head => headContainer.Child;
+ public DrawableHoldNoteTail Tail => tailContainer.Child;
- private readonly Container headContainer;
- private readonly Container tailContainer;
+ private readonly Container headContainer;
+ private readonly Container tailContainer;
private readonly Container tickContainer;
private readonly BodyPiece bodyPiece;
@@ -33,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
///
- private double? holdStartTime;
+ public double? HoldStartTime { get; private set; }
///
/// Whether the hold note has been released too early and shouldn't give full score for the release.
///
- private bool hasBroken;
+ public bool HasBroken { get; private set; }
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
@@ -49,8 +48,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
- headContainer = new Container { RelativeSizeAxes = Axes.Both },
- tailContainer = new Container { RelativeSizeAxes = Axes.Both },
+ headContainer = new Container { RelativeSizeAxes = Axes.Both },
+ tailContainer = new Container { RelativeSizeAxes = Axes.Both },
});
AccentColour.BindValueChanged(colour =>
@@ -65,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
- case DrawableHeadNote head:
+ case DrawableHoldNoteHead head:
headContainer.Child = head;
break;
- case DrawableTailNote tail:
+ case DrawableHoldNoteTail tail:
tailContainer.Child = tail;
break;
@@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
case TailNote _:
- return new DrawableTailNote(this)
+ return new DrawableHoldNoteTail(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
};
case Note _:
- return new DrawableHeadNote(this)
+ return new DrawableHoldNoteHead(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick)
{
- HoldStartTime = () => holdStartTime,
+ HoldStartTime = () => HoldStartTime,
AccentColour = { BindTarget = AccentColour }
};
}
@@ -125,12 +124,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- if (Tail.AllJudged)
- ApplyResult(r => r.Type = HitResult.Perfect);
- }
-
protected override void Update()
{
base.Update();
@@ -146,146 +139,64 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.UpdateStateTransforms(state);
}
- protected void BeginHold()
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
{
- holdStartTime = Time.Current;
- bodyPiece.Hitting = true;
- }
+ if (Tail.AllJudged)
+ ApplyResult(r => r.Type = HitResult.Perfect);
- protected void EndHold()
- {
- holdStartTime = null;
- bodyPiece.Hitting = false;
+ if (Tail.Result.Type == HitResult.Miss)
+ HasBroken = true;
}
public bool OnPressed(ManiaAction action)
{
- // Make sure the action happened within the body of the hold note
- if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
+ if (AllJudged)
return false;
if (action != Action.Value)
return false;
- // The user has pressed during the body of the hold note, after the head note and its hit windows have passed
- // and within the limited range of the above if-statement. This state will be managed by the head note if the
- // user has pressed during the hit windows of the head note.
- BeginHold();
+ beginHoldAt(Time.Current - Head.HitObject.StartTime);
+ Head.UpdateResult();
+
return true;
}
+ private void beginHoldAt(double timeOffset)
+ {
+ if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
+ return;
+
+ HoldStartTime = Time.Current;
+ bodyPiece.Hitting = true;
+ }
+
public bool OnReleased(ManiaAction action)
{
- // Make sure that the user started holding the key during the hold note
- if (!holdStartTime.HasValue)
+ if (AllJudged)
return false;
if (action != Action.Value)
return false;
- EndHold();
+ // Make sure a hold was started
+ if (HoldStartTime == null)
+ return false;
+
+ Tail.UpdateResult();
+ endHold();
// If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit)
- hasBroken = true;
+ HasBroken = true;
return true;
}
- ///
- /// The head note of a hold.
- ///
- private class DrawableHeadNote : DrawableNote
+ private void endHold()
{
- private readonly DrawableHoldNote holdNote;
-
- public DrawableHeadNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Head)
- {
- this.holdNote = holdNote;
- }
-
- public override bool OnPressed(ManiaAction action)
- {
- if (!base.OnPressed(action))
- return false;
-
- // If the key has been released too early, the user should not receive full score for the release
- if (Result.Type == HitResult.Miss)
- holdNote.hasBroken = true;
-
- // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
- // The body doesn't handle these early early hits, so we have to explicitly set the holding state here
- holdNote.BeginHold();
-
- return true;
- }
- }
-
- ///
- /// The tail note of a hold.
- ///
- private class DrawableTailNote : DrawableNote
- {
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
- ///
- private const double release_window_lenience = 1.5;
-
- private readonly DrawableHoldNote holdNote;
-
- public DrawableTailNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Tail)
- {
- this.holdNote = holdNote;
- }
-
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- Debug.Assert(HitObject.HitWindows != null);
-
- // Factor in the release lenience
- timeOffset /= release_window_lenience;
-
- if (!userTriggered)
- {
- if (!HitObject.HitWindows.CanBeHit(timeOffset))
- ApplyResult(r => r.Type = HitResult.Miss);
-
- return;
- }
-
- var result = HitObject.HitWindows.ResultFor(timeOffset);
- if (result == HitResult.None)
- return;
-
- ApplyResult(r =>
- {
- if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
- result = HitResult.Good;
-
- r.Type = result;
- });
- }
-
- public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
-
- public override bool OnReleased(ManiaAction action)
- {
- // Make sure that the user started holding the key during the hold note
- if (!holdNote.holdStartTime.HasValue)
- return false;
-
- if (action != Action.Value)
- return false;
-
- UpdateResult(true);
-
- // Handled by the hold note, which will set holding = false
- return false;
- }
+ HoldStartTime = null;
+ bodyPiece.Hitting = false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
new file mode 100644
index 0000000000..a5d03bf765
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// The head of a .
+ ///
+ public class DrawableHoldNoteHead : DrawableNote
+ {
+ public DrawableHoldNoteHead(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Head)
+ {
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
new file mode 100644
index 0000000000..a660144dd1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -0,0 +1,64 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// The tail of a .
+ ///
+ public class DrawableHoldNoteTail : DrawableNote
+ {
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ private const double release_window_lenience = 1.5;
+
+ private readonly DrawableHoldNote holdNote;
+
+ public DrawableHoldNoteTail(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Tail)
+ {
+ this.holdNote = holdNote;
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ Debug.Assert(HitObject.HitWindows != null);
+
+ // Factor in the release lenience
+ timeOffset /= release_window_lenience;
+
+ if (!userTriggered)
+ {
+ if (!HitObject.HitWindows.CanBeHit(timeOffset))
+ ApplyResult(r => r.Type = HitResult.Miss);
+
+ return;
+ }
+
+ var result = HitObject.HitWindows.ResultFor(timeOffset);
+ if (result == HitResult.None)
+ return;
+
+ ApplyResult(r =>
+ {
+ // If the head wasn't hit or the hold note was broken, cap the max score to Meh.
+ if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
+ result = HitResult.Meh;
+
+ r.Type = result;
+ });
+ }
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
deleted file mode 100644
index 1d25a0c966..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class GlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float glow_alpha = 0.7f;
- private const float glow_radius = 5;
-
- public GlowPiece()
- {
- RelativeSizeAxes = Axes.Both;
- Masking = true;
-
- InternalChild = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateGlow();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateGlow();
- }
- }
-
- private void updateGlow()
- {
- if (!IsLoaded)
- return;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = AccentColour.Opacity(glow_alpha),
- Radius = glow_radius,
- Hollow = true
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
deleted file mode 100644
index 48c7ea7b7f..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class LaneGlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float total_height = 100;
- private const float glow_height = 50;
- private const float glow_alpha = 0.4f;
- private const float edge_alpha = 0.3f;
-
- public LaneGlowPiece()
- {
- BypassAutoSizeAxes = Axes.Both;
- RelativeSizeAxes = Axes.X;
- Height = total_height;
-
- InternalChildren = new[]
- {
- new Container
- {
- Name = "Left edge",
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Right edge",
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Glow",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = glow_height,
- Children = createGradient(glow_alpha)
- }
- };
- }
-
- private Drawable[] createGradient(float alpha) => new Drawable[]
- {
- new Box
- {
- Name = "Top",
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha))
- },
- new Box
- {
- Name = "Bottom",
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent)
- }
- };
-
- public Color4 AccentColour
- {
- get => Colour;
- set => Colour = value;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 70ba5cd938..877a9ee410 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
// We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap);
+ var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 49894a644c..9b54b48de3 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -1,89 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Scoring
{
- internal class ManiaScoreProcessor : ScoreProcessor
+ internal class ManiaScoreProcessor : ScoreProcessor
{
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_min = 0.75;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_mid = 0.85;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_max = 1;
-
- ///
- /// The MISS HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_miss_min = 0.5;
-
- ///
- /// The MISS HP multiplier at OD = 5.
- ///
- private const double hp_multiplier_miss_mid = 0.75;
-
- ///
- /// The MISS HP multiplier at OD = 10.
- ///
- private const double hp_multiplier_miss_max = 1;
-
- ///
- /// The MISS HP multiplier. This is multiplied to the miss hp increase.
- ///
- private double hpMissMultiplier = 1;
-
- ///
- /// The HIT HP multiplier. This is multiplied to hit hp increases.
- ///
- private double hpMultiplier = 1;
-
- public ManiaScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
- hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
- }
-
- protected override void SimulateAutoplay(Beatmap beatmap)
- {
- while (true)
- {
- base.SimulateAutoplay(beatmap);
-
- if (!HasFailed)
- break;
-
- hpMultiplier *= 1.01;
- hpMissMultiplier *= 0.98;
-
- Reset(false);
- }
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
new file mode 100644
index 0000000000..f3739ce7c2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -0,0 +1,67 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class ManiaLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ public ManiaLegacySkinTransformer(ISkin source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ return getResult(resultComponent);
+ }
+
+ return null;
+ }
+
+ private Drawable getResult(GameplaySkinComponent resultComponent)
+ {
+ switch (resultComponent.Component)
+ {
+ case HitResult.Miss:
+ return this.GetAnimation("mania-hit0", true, true);
+
+ case HitResult.Meh:
+ return this.GetAnimation("mania-hit50", true, true);
+
+ case HitResult.Ok:
+ return this.GetAnimation("mania-hit100", true, true);
+
+ case HitResult.Good:
+ return this.GetAnimation("mania-hit200", true, true);
+
+ case HitResult.Great:
+ return this.GetAnimation("mania-hit300", true, true);
+
+ case HitResult.Perfect:
+ return this.GetAnimation("mania-hit300g", true, true);
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup) =>
+ source.GetConfig(lookup);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index 386bcbb724..90e78c3899 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -18,30 +18,17 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
- private const float hit_target_bar_height = 2;
-
private readonly IBindable direction = new Bindable();
- private readonly Container hitTargetLine;
- private readonly Drawable hitTargetBar;
+ private readonly Drawable hitTarget;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
InternalChildren = new[]
{
- hitTargetBar = new Box
+ hitTarget = new DefaultHitTarget
{
RelativeSizeAxes = Axes.X,
- Height = NotePiece.NOTE_HEIGHT,
- Alpha = 0.6f,
- Colour = Color4.Black
- },
- hitTargetLine = new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Child = new Box { RelativeSizeAxes = Axes.Both }
},
hitObjectContainer
};
@@ -55,17 +42,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
- hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
- hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ hitTarget.Anchor = hitTarget.Origin = anchor;
}, true);
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
- }
-
private Color4 accentColour;
public Color4 AccentColour
@@ -78,21 +58,88 @@ namespace osu.Game.Rulesets.Mania.UI.Components
accentColour = value;
- updateColours();
+ if (hitTarget is IHasAccentColour colouredHitTarget)
+ colouredHitTarget.AccentColour = accentColour;
}
}
- private void updateColours()
+ private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
{
- if (!IsLoaded)
- return;
+ private const float hit_target_bar_height = 2;
- hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ private readonly IBindable direction = new Bindable();
+
+ private readonly Container hitTargetLine;
+ private readonly Drawable hitTargetBar;
+
+ public DefaultHitTarget()
{
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(dir =>
+ {
+ Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+
+ hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
+ hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index d371c1f7a8..2c497541a8 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Bindable configDirection = new Bindable();
- public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator(Beatmap).BarLines;
@@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
- public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
-
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index ccbff226a9..35de47e208 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -5,7 +5,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
index 3718264a42..b36d0b5728 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Osu.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
index 67338b7bbe..94568e3852 100644
--- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 450f7de6d2..cd3daf18a9 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index 693faee3b7..85a41137d4 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
- [TestCase(6.931145117263422, "diffcalc-test")]
+ [TestCase(6.9311451172608853d, "diffcalc-test")]
[TestCase(1.0736587013228804d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png
new file mode 100644
index 0000000000..ff8b02ce80
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png
new file mode 100644
index 0000000000..2af0569bcb
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png
new file mode 100644
index 0000000000..e8b674d62a
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png
new file mode 100644
index 0000000000..dbf7bc73bc
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png
new file mode 100644
index 0000000000..43990c46e7
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png
new file mode 100644
index 0000000000..4564f6d8bf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png
new file mode 100644
index 0000000000..dcef35eb59
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png
new file mode 100644
index 0000000000..bfc0a01be5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png
new file mode 100644
index 0000000000..b9079ad5d5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png
new file mode 100644
index 0000000000..3a3d61b947
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png
new file mode 100644
index 0000000000..3e703cd1cf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png
new file mode 100644
index 0000000000..3c3ebbfd0b
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png
new file mode 100644
index 0000000000..9ecc302910
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png
new file mode 100644
index 0000000000..24945f7d92
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png
new file mode 100644
index 0000000000..d3f7eec5ee
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png
new file mode 100644
index 0000000000..a5a3545abf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png
new file mode 100644
index 0000000000..b4062b8c62
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
new file mode 100644
index 0000000000..5369de24e9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 1.0
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
index 38aac50df6..d4c3000d3f 100644
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -19,20 +19,22 @@ namespace osu.Game.Rulesets.Osu.Tests
private Skin metricsSkin;
private Skin defaultSkin;
private Skin specialSkin;
+ private Skin oldSkin;
protected SkinnableTestScene()
- : base(2, 2)
+ : base(2, 3)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
- var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
+ var dllStore = new DllResourceStore(typeof(SkinnableTestScene).Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
+ oldSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/old_skin"), audio, true);
}
public void SetContents(Func creationFunction)
@@ -41,6 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Cell(1).Child = createProvider(metricsSkin, creationFunction);
Cell(2).Child = createProvider(defaultSkin, creationFunction);
Cell(3).Child = createProvider(specialSkin, creationFunction);
+ Cell(4).Child = createProvider(oldSkin, creationFunction);
}
private Drawable createProvider(Skin skin, Func creationFunction)
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 64f353c4d9..098e277fff 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableHitCircle(circle, auto);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
return drawable;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
index 55c6b22146..21ebce8c23 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneHitCircleHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
new file mode 100644
index 0000000000..4676f14655
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
@@ -0,0 +1,155 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneLegacyBeatmapSkin : OsuTestScene
+ {
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapComboColours(bool customSkinColoursPresent)
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColours()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColoursSkinOverride()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
+ {
+ ExposedPlayer player;
+
+ Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
+ Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
+
+ return player;
+ }
+
+ private class ExposedPlayer : Player
+ {
+ private readonly bool userHasCustomColours;
+
+ public ExposedPlayer(bool userHasCustomColours)
+ : base(false, false)
+ {
+ this.userHasCustomColours = userHasCustomColours;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs(new TestSkin(userHasCustomColours));
+ return dependencies;
+ }
+
+ public IReadOnlyList UsableComboColours =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value;
+ }
+
+ private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly bool hasColours;
+
+ public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
+ : base(new Beatmap
+ {
+ BeatmapInfo =
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ }, null, null, audio)
+ {
+ this.hasColours = hasColours;
+ }
+
+ protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
+ }
+
+ private class TestBeatmapSkin : LegacyBeatmapSkin
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(50, 100, 150, 255),
+ new Color4(40, 80, 120, 255),
+ };
+
+ public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
+ : base(beatmap, new ResourceStore(), null)
+ {
+ if (hasColours)
+ Configuration.AddComboColours(Colours);
+ }
+ }
+
+ private class TestSkin : LegacySkin, ISkinSource
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(150, 100, 50, 255),
+ new Color4(20, 20, 20, 255),
+ };
+
+ public TestSkin(bool hasCustomColours)
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ if (hasCustomColours)
+ Configuration.AddComboColours(Colours);
+ }
+
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index eff4d919b0..4af4d5f966 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
@@ -33,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CircularDistanceSnapGrid)
};
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneOsuDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
}
[SetUp]
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
index 64e7632b1b..412effe176 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
+ SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
return base.CreatePlayer(ruleset);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 863d0eda09..d692be89b2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index a9d5c03517..e8386363be 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableSlider(slider);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnNewResult += onNewResult;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
index 2a9c1d167b..d0ee1bddb5 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSliderHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 5f75cbabec..b6fc9821a4 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -286,11 +286,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
- private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
+ private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
- private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
+ private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great;
- private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
+ private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss;
private ScoreAccessibleReplayPlayer currentPlayer;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
index dde2aa53e0..5dd2bd18a8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
@@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep($"move mouse to control point {index}", () =>
{
- Vector2 position = slider.Position + slider.Path.ControlPoints[index];
+ Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
});
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index 3ed3f3e981..f53b64c729 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Depth = depthIndex++
};
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
index a0ab1908d6..dd863deed2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSpinnerHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index d0ce0c33c2..5cf571d961 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -4,7 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
@@ -70,6 +70,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
}
+ [Test]
+ public void TestSpinPerMinuteOnRewind()
+ {
+ double estimatedSpm = 0;
+
+ addSeekStep(2500);
+ AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
+
+ addSeekStep(5000);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+
+ addSeekStep(2500);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+ }
+
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
@@ -84,10 +99,10 @@ namespace osu.Game.Rulesets.Osu.Tests
new Spinner
{
Position = new Vector2(256, 192),
- EndTime = 5000,
+ EndTime = 6000,
},
// placeholder object to avoid hitting the results screen
- new HitObject
+ new HitCircle
{
StartTime = 99999,
}
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 fddf176fd0..1d8c4708c1 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
@@ -4,12 +4,12 @@
-
+
WinExe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 2296030f81..147d74c929 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
public class OsuBeatmapConverter : BeatmapConverter
{
- public OsuBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public OsuBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 05c78cbc95..ce8ecf02ac 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -45,10 +45,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
- countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
- countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
- countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
- countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+ countGreat = Score.Statistics[HitResult.Great];
+ countGood = Score.Statistics[HitResult.Good];
+ countMeh = Score.Statistics[HitResult.Meh];
+ countMiss = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
new file mode 100644
index 0000000000..0fc441fec6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
@@ -0,0 +1,75 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Lines;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
+{
+ ///
+ /// A visualisation of the line between two s.
+ ///
+ public class PathControlPointConnectionPiece : CompositeDrawable
+ {
+ public PathControlPoint ControlPoint;
+
+ private readonly Path path;
+ private readonly Slider slider;
+
+ private IBindable sliderPosition;
+ private IBindable pathVersion;
+
+ public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint)
+ {
+ this.slider = slider;
+ ControlPoint = controlPoint;
+
+ Origin = Anchor.Centre;
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = path = new SmoothPath
+ {
+ Anchor = Anchor.Centre,
+ PathRadius = 1
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ sliderPosition = slider.PositionBindable.GetBoundCopy();
+ sliderPosition.BindValueChanged(_ => updateConnectingPath());
+
+ pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion.BindValueChanged(_ => updateConnectingPath());
+
+ updateConnectingPath();
+ }
+
+ ///
+ /// Updates the path connecting this control point to the next one.
+ ///
+ private void updateConnectingPath()
+ {
+ Position = slider.StackedPosition + ControlPoint.Position.Value;
+
+ path.ClearVertices();
+
+ int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1;
+
+ if (index == 0 || index == slider.Path.ControlPoints.Count)
+ return;
+
+ path.AddVertex(Vector2.Zero);
+ path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
+
+ path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index 0ccf020300..6a0730db91 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -6,11 +6,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
using osuTK.Graphics;
@@ -18,16 +18,18 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
+ ///
+ /// A visualisation of a single in a .
+ ///
public class PathControlPointPiece : BlueprintPiece
{
- public Action RequestSelection;
- public Action ControlPointsChanged;
+ public Action RequestSelection;
public readonly BindableBool IsSelected = new BindableBool();
- public readonly int Index;
+
+ public readonly PathControlPoint ControlPoint;
private readonly Slider slider;
- private readonly Path path;
private readonly Container marker;
private readonly Drawable markerRing;
@@ -37,21 +39,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved]
private OsuColour colours { get; set; }
- public PathControlPointPiece(Slider slider, int index)
+ private IBindable sliderPosition;
+ private IBindable controlPointPosition;
+
+ public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
{
this.slider = slider;
- Index = index;
+ ControlPoint = controlPoint;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
- path = new SmoothPath
- {
- Anchor = Anchor.Centre,
- PathRadius = 1
- },
marker = new Container
{
Anchor = Anchor.Centre,
@@ -86,48 +86,35 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
};
}
- protected override void Update()
+ protected override void LoadComplete()
{
- base.Update();
+ base.LoadComplete();
- Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
+ sliderPosition = slider.PositionBindable.GetBoundCopy();
+ sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
+
+ controlPointPosition = ControlPoint.Position.GetBoundCopy();
+ controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
+
+ IsSelected.BindValueChanged(_ => updateMarkerDisplay());
updateMarkerDisplay();
- updateConnectingPath();
- }
-
- ///
- /// Updates the state of the circular control point marker.
- ///
- private void updateMarkerDisplay()
- {
- markerRing.Alpha = IsSelected.Value ? 1 : 0;
-
- Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
- if (IsHovered || IsSelected.Value)
- colour = Color4.White;
- marker.Colour = colour;
- }
-
- ///
- /// Updates the path connecting this control point to the previous one.
- ///
- private void updateConnectingPath()
- {
- path.ClearVertices();
-
- if (Index != slider.Path.ControlPoints.Length - 1)
- {
- path.AddVertex(Vector2.Zero);
- path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
- }
-
- path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}
// The connecting path is excluded from positional input
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateMarkerDisplay();
+ return false;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateMarkerDisplay();
+ }
+
protected override bool OnMouseDown(MouseDownEvent e)
{
if (RequestSelection == null)
@@ -136,12 +123,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (e.Button)
{
case MouseButton.Left:
- RequestSelection.Invoke(Index, e);
+ RequestSelection.Invoke(this, e);
return true;
case MouseButton.Right:
if (!IsSelected.Value)
- RequestSelection.Invoke(Index, e);
+ RequestSelection.Invoke(this, e);
return false; // Allow context menu to show
}
@@ -156,9 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override bool OnDrag(DragEvent e)
{
- var newControlPoints = slider.Path.ControlPoints.ToArray();
-
- if (Index == 0)
+ if (ControlPoint == slider.Path.ControlPoints[0])
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
@@ -168,29 +153,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.StartTime = snappedTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
- for (int i = 1; i < newControlPoints.Length; i++)
- newControlPoints[i] -= movementDelta;
+ for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
+ slider.Path.ControlPoints[i].Position.Value -= movementDelta;
}
else
- newControlPoints[Index] += e.Delta;
-
- if (isSegmentSeparatorWithNext)
- newControlPoints[Index + 1] = newControlPoints[Index];
-
- if (isSegmentSeparatorWithPrevious)
- newControlPoints[Index - 1] = newControlPoints[Index];
-
- ControlPointsChanged?.Invoke(newControlPoints);
+ ControlPoint.Position.Value += e.Delta;
return true;
}
protected override bool OnDragEnd(DragEndEvent e) => true;
- private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
+ ///
+ /// Updates the state of the circular control point marker.
+ ///
+ private void updateMarkerDisplay()
+ {
+ Position = slider.StackedPosition + ControlPoint.Position.Value;
- private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
+ markerRing.Alpha = IsSelected.Value ? 1 : 0;
- private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
+ Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
+ if (IsHovered || IsSelected.Value)
+ colour = Color4.White;
+ marker.Colour = colour;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index cdca48490e..6f583d7983 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
-using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -14,25 +14,28 @@ using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit.Compose;
-using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
{
- public Action ControlPointsChanged;
-
internal readonly Container Pieces;
+
+ private readonly Container connections;
+
private readonly Slider slider;
+
private readonly bool allowSelection;
private InputManager inputManager;
- [Resolved(CanBeNull = true)]
- private IPlacementHandler placementHandler { get; set; }
+ private IBindableList controlPoints;
+
+ public Action> RemoveControlPointsRequested;
public PathControlPointVisualiser(Slider slider, bool allowSelection)
{
@@ -41,7 +44,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
RelativeSizeAxes = Axes.Both;
- InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both };
+ InternalChildren = new Drawable[]
+ {
+ connections = new Container { RelativeSizeAxes = Axes.Both },
+ Pieces = new Container { RelativeSizeAxes = Axes.Both }
+ };
}
protected override void LoadComplete()
@@ -49,33 +56,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
base.LoadComplete();
inputManager = GetContainingInputManager();
+
+ controlPoints = slider.Path.ControlPoints.GetBoundCopy();
+ controlPoints.ItemsAdded += addControlPoints;
+ controlPoints.ItemsRemoved += removeControlPoints;
+
+ addControlPoints(controlPoints);
}
- protected override void Update()
+ private void addControlPoints(IEnumerable controlPoints)
{
- base.Update();
-
- while (slider.Path.ControlPoints.Length > Pieces.Count)
+ foreach (var point in controlPoints)
{
- var piece = new PathControlPointPiece(slider, Pieces.Count)
+ Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
{
- ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
- };
+ if (allowSelection)
+ d.RequestSelection = selectPiece;
+ }));
- if (allowSelection)
- piece.RequestSelection = selectPiece;
-
- Pieces.Add(piece);
+ connections.Add(new PathControlPointConnectionPiece(slider, point));
}
+ }
- while (slider.Path.ControlPoints.Length < Pieces.Count)
- Pieces.Remove(Pieces[Pieces.Count - 1]);
+ private void removeControlPoints(IEnumerable controlPoints)
+ {
+ foreach (var point in controlPoints)
+ {
+ Pieces.RemoveAll(p => p.ControlPoint == point);
+ connections.RemoveAll(c => c.ControlPoint == point);
+ }
}
protected override bool OnClick(ClickEvent e)
{
foreach (var piece in Pieces)
+ {
piece.IsSelected.Value = false;
+ }
+
return false;
}
@@ -92,51 +110,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
- private void selectPiece(int index, MouseButtonEvent e)
+ private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
- Pieces[index].IsSelected.Toggle();
+ piece.IsSelected.Toggle();
else
{
- foreach (var piece in Pieces)
- piece.IsSelected.Value = piece.Index == index;
+ foreach (var p in Pieces)
+ p.IsSelected.Value = p == piece;
}
}
private bool deleteSelected()
{
- var newControlPoints = new List();
-
- foreach (var piece in Pieces)
- {
- if (!piece.IsSelected.Value)
- newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
- }
+ List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
// Ensure that there are any points to be deleted
- if (newControlPoints.Count == slider.Path.ControlPoints.Length)
+ if (toRemove.Count == 0)
return false;
- // If there are 0 remaining control points, treat the slider as being deleted
- if (newControlPoints.Count == 0)
- {
- placementHandler?.Delete(slider);
- return true;
- }
-
- // Make control points relative
- Vector2 first = newControlPoints[0];
- for (int i = 0; i < newControlPoints.Count; i++)
- newControlPoints[i] = newControlPoints[i] - first;
-
- // The slider's position defines the position of the first control point, and all further control points are relative to that point
- slider.Position += first;
+ RemoveControlPointsRequested?.Invoke(toRemove);
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
- ControlPointsChanged?.Invoke(newControlPoints.ToArray());
return true;
}
@@ -147,16 +145,63 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (!Pieces.Any(p => p.IsHovered))
return null;
- int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
+ var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
+ int count = selectedPieces.Count;
- if (selectedPoints == 0)
+ if (count == 0)
return null;
+ List
WinExe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
index aaf113f216..c31b07344d 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
@@ -26,10 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Audio
var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
- // todo: this is ugly
- centre.Namespace = "taiko";
- rim.Namespace = "taiko";
-
mappings[s.Time] = new DrumSample
{
Centre = addSound(centre),
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 10cc861b7e..cc9d6e4470 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -39,14 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) };
-
- public TaikoBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo);
+ isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
}
+ public override bool CanConvert() => true;
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index c3638253e4..3a0fb64622 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -31,10 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary categoryDifficulty = null)
{
mods = Score.Mods;
- countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
- countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
- countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
- countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
+ countGreat = Score.Statistics[HitResult.Great];
+ countGood = Score.Statistics[HitResult.Good];
+ countMeh = Score.Statistics[HitResult.Meh];
+ countMiss = Score.Statistics[HitResult.Miss];
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
new file mode 100644
index 0000000000..71aa007d3b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
+ Replay = new TaikoAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
new file mode 100644
index 0000000000..56a73ad7df
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
index e45081b6d6..5377eb1072 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
@@ -2,10 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
- public class TaikoModNightcore : ModNightcore
+ public class TaikoModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.12;
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 338fd9e20f..5806c90115 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -4,7 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 0db6498c12..b9d31ff906 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -105,19 +105,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
- where TaikoHitType : TaikoHitObject
+ public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
+ where TTaikoHit : TaikoHitObject
{
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
- public new TaikoHitType HitObject;
+ public new TTaikoHit HitObject;
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece;
private readonly Container strongHitContainer;
- protected DrawableTaikoHitObject(TaikoHitType hitObject)
+ protected DrawableTaikoHitObject(TTaikoHit hitObject)
: base(hitObject)
{
HitObject = hitObject;
@@ -166,8 +166,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "taiko";
-
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
///
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 6f4fbd0651..c41727557b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -3,7 +3,6 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Scoring;
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.CreateNestedHitObjects();
if (IsStrong)
- AddNested(new StrongHitObject { StartTime = (this as IHasEndTime)?.EndTime ?? StartTime });
+ AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
}
public override Judgement CreateJudgement() => new TaikoJudgement();
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index e61953aeb8..48eb33976e 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
@@ -39,9 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
TaikoHitObject h = Beatmap.HitObjects[i];
-
- IHasEndTime endTimeData = h as IHasEndTime;
- double endTime = endTimeData?.EndTime ?? h.StartTime;
+ double endTime = h.GetEndTime();
switch (h)
{
@@ -124,41 +121,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
-
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
-
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
- if (i < Beatmap.HitObjects.Count - 1)
- {
- double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
- if (waitTime > endTime)
- Frames.Add(new TaikoReplayFrame(waitTime));
- }
-
hitButton = !hitButton;
}
return Replay;
}
-
- protected override HitObject GetNextObject(int currentIndex)
- {
- Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
-
- for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
- {
- var currentObj = Beatmap.HitObjects[i];
-
- if (currentObj.GetType() == desiredType ||
- // Un-press all keys before a DrumRoll or Swell
- currentObj is DrumRoll || currentObj is Swell)
- {
- return Beatmap.HitObjects[i];
- }
- }
-
- return null;
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav
new file mode 100755
index 0000000000..9ea2be5855
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav
new file mode 100755
index 0000000000..af270ae12a
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav
new file mode 100755
index 0000000000..3d8024c6ae
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav
new file mode 100755
index 0000000000..16d254cc87
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav
new file mode 100755
index 0000000000..b4cfa26265
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav
new file mode 100755
index 0000000000..97804a5a61
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav
new file mode 100755
index 0000000000..67f02877a8
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav
new file mode 100755
index 0000000000..10206cd228
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
new file mode 100644
index 0000000000..edb089dbac
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ ///
+ /// A for the taiko ruleset.
+ /// Taiko fails if the player has not half-filled their health by the end of the map.
+ ///
+ public class TaikoHealthProcessor : AccumulatingHealthProcessor
+ {
+ ///
+ /// A value used for calculating .
+ ///
+ private const double object_count_factor = 3;
+
+ ///
+ /// HP multiplier for a successful .
+ ///
+ private double hpMultiplier;
+
+ ///
+ /// HP multiplier for a .
+ ///
+ private double hpMissMultiplier;
+
+ public TaikoHealthProcessor()
+ : base(0.5)
+ {
+ }
+
+ public override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
+ }
+
+ protected override double GetHealthIncreaseFor(JudgementResult result)
+ => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 75a27ff639..003d40af56 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -1,60 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Scoring
{
- internal class TaikoScoreProcessor : ScoreProcessor
+ internal class TaikoScoreProcessor : ScoreProcessor
{
- ///
- /// A value used for calculating .
- ///
- private const double object_count_factor = 3;
-
- ///
- /// Taiko fails at the end of the map if the player has not half-filled their HP bar.
- ///
- protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
-
- ///
- /// HP multiplier for a successful .
- ///
- private double hpMultiplier;
-
- ///
- /// HP multiplier for a .
- ///
- private double hpMissMultiplier;
-
- public TaikoScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
-
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
-
- Health.Value = 0;
- }
-
public override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
new file mode 100644
index 0000000000..381cd14cd4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -0,0 +1,55 @@
+// 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 osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class TaikoLegacySkinTransformer : ISkin
+ {
+ private readonly ISkinSource source;
+
+ public TaikoLegacySkinTransformer(ISkinSource source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
+
+ public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+
+ private class LegacyTaikoSampleInfo : ISampleInfo
+ {
+ private readonly ISampleInfo source;
+
+ public LegacyTaikoSampleInfo(ISampleInfo source)
+ {
+ this.source = source;
+ }
+
+ public IEnumerable LookupNames
+ {
+ get
+ {
+ foreach (var name in source.LookupNames)
+ yield return $"taiko-{name}";
+
+ foreach (var name in source.LookupNames)
+ yield return name;
+ }
+ }
+
+ public int Volume => source.Volume;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index b2655f592c..fc79e59864 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -15,16 +15,28 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty;
+using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
+using System;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko
{
- public class TaikoRuleset : Ruleset
+ public class TaikoRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();
+
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
+
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source);
public const string SHORT_NAME = "taiko";
@@ -50,7 +62,9 @@ namespace osu.Game.Rulesets.Taiko
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new TaikoModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -97,10 +111,16 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new TaikoModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new TaikoModAutoplay(), new ModCinema()),
+ new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()),
new TaikoModRelax(),
};
@@ -111,7 +131,7 @@ namespace osu.Game.Rulesets.Taiko
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -119,19 +139,16 @@ namespace osu.Game.Rulesets.Taiko
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Bashing drums";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 1;
+ public int LegacyID => 1;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
-
- public TaikoRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index fc109bf6a6..0c7495aa52 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,10 +5,8 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Input;
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
@@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI
new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
- public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
-
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs
index d96a3e27a4..9533b90131 100644
--- a/osu.Game.Tests.iOS/Application.cs
+++ b/osu.Game.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
index 98e630abd2..12d729d09f 100644
--- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestHitObjectAddEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h;
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps
public void HitObjectRemoveEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h;
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestInitialHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddedHitObjectStartTimeChangeEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddHitObjectInMiddle()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestResortWhenStartTimeChanged()
{
var hitCircle = new HitCircle { StartTime = 1000 };
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 26e70f19e4..33f484a9aa 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
+using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -313,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new OsuBeatmapConverter(beatmap).Convert();
+ var converted = new OsuBeatmapConverter(beatmap, new OsuRuleset()).Convert();
new OsuBeatmapProcessor(converted).PreProcess();
new OsuBeatmapProcessor(converted).PostProcess();
@@ -336,7 +337,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new CatchBeatmapConverter(beatmap).Convert();
+ var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert();
new CatchBeatmapProcessor(converted).PreProcess();
new CatchBeatmapProcessor(converted).PostProcess();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
new file mode 100644
index 0000000000..f2b3a16f68
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.IO.Serialization;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Beatmaps.Formats
+{
+ [TestFixture]
+ public class LegacyBeatmapEncoderTest
+ {
+ private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
+
+ private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
+
+ [TestCaseSource(nameof(allBeatmaps))]
+ public void TestDecodeEncodedBeatmap(string name)
+ {
+ var decoded = decode(normal, out var encoded);
+
+ Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count));
+ Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
+ }
+
+ private Beatmap decode(string filename, out Beatmap encoded)
+ {
+ using (var stream = TestResources.OpenResource(filename))
+ using (var sr = new LineBufferedReader(stream))
+ {
+ var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
+
+ using (var ms = new MemoryStream())
+ using (var sw = new StreamWriter(ms))
+ using (var sr2 = new LineBufferedReader(ms))
+ {
+ new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
+ sw.Flush();
+
+ ms.Position = 0;
+
+ encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2);
+ return legacyDecoded;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 66d53d7e7b..96ff6b81e3 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
- Assert.AreEqual("SB/black.jpg", sprite.Path);
+ Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
var animation = background.Elements.OfType().First();
Assert.NotNull(animation);
diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
index fe3cc375ea..2d336bd19c 100644
--- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
@@ -17,6 +18,9 @@ namespace osu.Game.Tests.Editor
{
private TestHitObjectComposer composer;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+
[SetUp]
public void Setup() => Schedule(() =>
{
@@ -183,7 +187,7 @@ namespace osu.Game.Tests.Editor
private class TestHitObjectComposer : OsuHitObjectComposer
{
- public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
+ public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
public TestHitObjectComposer()
: base(new OsuRuleset())
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
new file mode 100644
index 0000000000..244e37f017
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
@@ -0,0 +1,159 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneDrainingHealthProcessor : OsuTestScene
+ {
+ private Bindable breakTime;
+ private HealthProcessor processor;
+ private ManualClock clock;
+
+ [Test]
+ public void TestInitialHealthStartsAtOne()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedBeforeGameplayStart()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ setTime(100);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedAfterGameplayEnd()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+ setTime(2001); // After the hitobjects
+ setHealth(1); // Reset the current health for assertions to take place
+
+ setTime(2100);
+ assertHealthEqualTo(1);
+ setTime(3000);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedDuringBreak()
+ {
+ createProcessor(createBeatmap(0, 2000));
+ setBreak(true);
+
+ setTime(700);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthDrainedDuringGameplay()
+ {
+ createProcessor(createBeatmap(0, 1000));
+
+ setTime(500);
+ assertHealthNotEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedAfterRewind()
+ {
+ createProcessor(createBeatmap(0, 1000));
+ setTime(500);
+
+ setTime(0);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedOnHit()
+ {
+ Beatmap beatmap = createBeatmap(0, 1000);
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ assertHealthNotEqualTo(1);
+
+ AddStep("apply hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthRemovedOnRevert()
+ {
+ var beatmap = createBeatmap(0, 1000);
+ JudgementResult result = null;
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ AddStep("apply hit result", () => processor.ApplyResult(result = new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+
+ AddStep("revert hit result", () => processor.RevertResult(result));
+ assertHealthNotEqualTo(1);
+ }
+
+ private Beatmap createBeatmap(double startTime, double endTime)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
+ };
+
+ for (double time = startTime; time <= endTime; time += 100)
+ beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
+
+ return beatmap;
+ }
+
+ private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
+ {
+ breakTime = new Bindable();
+
+ Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
+ {
+ d.RelativeSizeAxes = Axes.Both;
+ d.Clock = new FramedClock(clock = new ManualClock());
+ });
+
+ processor.IsBreakTime.BindTo(breakTime);
+ processor.ApplyBeatmap(beatmap);
+ });
+
+ private void setTime(double time) => AddStep($"set time = {time}", () => clock.CurrentTime = time);
+
+ private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
+
+ private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
+
+ private void assertHealthEqualTo(double value)
+ => AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private void assertHealthNotEqualTo(double value)
+ => AddAssert($"health != {value}", () => !Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private class JudgeableHitObject : HitObject
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 6d7159a825..c6d1f9da29 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Gameplay
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(ComboColours));
+ return SkinUtils.As(new Bindable>(ComboColours));
}
break;
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs
new file mode 100644
index 0000000000..f2bfccb6de
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs
@@ -0,0 +1,75 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneHitObjectContainer : OsuTestScene
+ {
+ private HitObjectContainer container;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = container = new HitObjectContainer();
+ });
+
+ [Test]
+ public void TestLateHitObjectIsAddedEarlierInList()
+ {
+ DrawableHitObject hitObject = null;
+
+ AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
+
+ AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })));
+
+ AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0);
+ }
+
+ [Test]
+ public void TestEarlyHitObjectIsAddedLaterInList()
+ {
+ DrawableHitObject hitObject = null;
+
+ AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
+
+ AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject())));
+
+ AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1);
+ }
+
+ [Test]
+ public void TestHitObjectsResortedAfterStartTimeChange()
+ {
+ DrawableHitObject firstObject = null;
+ DrawableHitObject secondObject = null;
+
+ AddStep("setup", () =>
+ {
+ container.Add(firstObject = new TestDrawableHitObject(new HitObject()));
+ container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }));
+ });
+
+ AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000);
+
+ AddAssert("first object index is 1", () => container.IndexOf(firstObject) == 0);
+ AddAssert("second object index is 0", () => container.IndexOf(secondObject) == 1);
+ }
+
+ private class TestDrawableHitObject : DrawableHitObject
+ {
+ public TestDrawableHitObject([NotNull] HitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
new file mode 100644
index 0000000000..84506739ab
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -0,0 +1,76 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneStoryboardSamples : OsuTestScene
+ {
+ [Test]
+ public void TestRetrieveTopLevelSample()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ [Test]
+ public void TestRetrieveSampleInSubFolder()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ private class TestSkin : LegacySkin
+ {
+ public TestSkin(string resourceName, AudioManager audioManager)
+ : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
+ {
+ }
+ }
+
+ private class TestResourceStore : IResourceStore
+ {
+ private readonly string resourceName;
+
+ public TestResourceStore(string resourceName)
+ {
+ this.resourceName = resourceName;
+ }
+
+ public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null;
+
+ public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null;
+
+ public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null;
+
+ public IEnumerable GetAvailableResources() => new[] { resourceName };
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index a51b90851c..2782e902fe 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
@@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual
{
var cpi = new ControlPointInfo();
- cpi.Add(0, new SampleControlPoint()); // is redundant
+ cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point
cpi.Add(1000, new SampleControlPoint()); // is redundant
- Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
- Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
-
- cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
-
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
+
+ cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.SamplePoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new EffectControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
index 1c78b63499..d5ac38008e 100644
--- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
+++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{
Assert.AreEqual(0, stack.Count);
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[0];
});
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs
new file mode 100644
index 0000000000..0ae0186770
--- /dev/null
+++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs
@@ -0,0 +1,51 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Online
+{
+ [HeadlessTest]
+ public class TestSceneBeatmapManager : OsuTestScene
+ {
+ private BeatmapManager beatmaps;
+ private ProgressNotification recentNotification;
+
+ private static readonly BeatmapSetInfo test_model = new BeatmapSetInfo { OnlineBeatmapSetID = 1 };
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ this.beatmaps = beatmaps;
+
+ beatmaps.PostNotification = n => recentNotification = n as ProgressNotification;
+ }
+
+ [Test]
+ public void TestCancelDownloadFromRequest()
+ {
+ AddStep("download beatmap", () => beatmaps.Download(test_model));
+
+ AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_model).Cancel());
+
+ AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
+ AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
+ }
+
+ [Test]
+ public void TestCancelDownloadFromNotification()
+ {
+ AddStep("download beatmap", () => beatmaps.Download(test_model));
+
+ AddStep("cancel download from notification", () => recentNotification.Close());
+
+ AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
+ AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz
new file mode 100644
index 0000000000..987dbea6db
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz
new file mode 100644
index 0000000000..8a92423d35
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 66084a3204..8b892fbb2f 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -9,9 +9,11 @@ namespace osu.Game.Tests.Resources
{
public static class TestResources
{
- public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
+ public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
- public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
+ public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
+
+ public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
diff --git a/osu.Game.Tests/Resources/test-sample.mp3 b/osu.Game.Tests/Resources/test-sample.mp3
new file mode 100644
index 0000000000..f7c344f39a
Binary files /dev/null and b/osu.Game.Tests/Resources/test-sample.mp3 differ
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 89b5db9e1b..a139c3a8c2 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
+using osu.Game.IO.Archives;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
+ [Test]
+ public async Task TestOnlineScoreIsAvailableLocally()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
+ {
+ try
+ {
+ var osu = await loadOsu(host);
+
+ await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
+
+ var scoreManager = osu.Dependencies.Get();
+
+ // Note: A new score reference is used here since the import process mutates the original object to set an ID
+ Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
var beatmapManager = osu.Dependencies.Get();
@@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score);
+ await scoreManager.Import(score, archive);
return scoreManager.GetAllUsableScores().FirstOrDefault();
}
@@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
+
+ private class TestArchiveReader : ArchiveReader
+ {
+ public TestArchiveReader()
+ : base("test_archive")
+ {
+ }
+
+ public override Stream GetStream(string name) => new MemoryStream();
+
+ public override void Dispose()
+ {
+ }
+
+ public override IEnumerable Filenames => new[] { "test_file.osr" };
+
+ public override Stream GetUnderlyingStream() => new MemoryStream();
+ }
}
}
diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
new file mode 100644
index 0000000000..d1374eb6e5
--- /dev/null
+++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Scoring;
+
+namespace osu.Game.Tests.Scores.IO
+{
+ [TestFixture]
+ public class TestScoreEquality
+ {
+ [Test]
+ public void TestNonMatchingByReference()
+ {
+ ScoreInfo score1 = new ScoreInfo();
+ ScoreInfo score2 = new ScoreInfo();
+
+ Assert.That(score1, Is.Not.EqualTo(score2));
+ }
+
+ [Test]
+ public void TestMatchingByReference()
+ {
+ ScoreInfo score = new ScoreInfo();
+
+ Assert.That(score, Is.EqualTo(score));
+ }
+
+ [Test]
+ public void TestNonMatchingByPrimaryKey()
+ {
+ ScoreInfo score1 = new ScoreInfo { ID = 1 };
+ ScoreInfo score2 = new ScoreInfo { ID = 2 };
+
+ Assert.That(score1, Is.Not.EqualTo(score2));
+ }
+
+ [Test]
+ public void TestMatchingByPrimaryKey()
+ {
+ ScoreInfo score1 = new ScoreInfo { ID = 1 };
+ ScoreInfo score2 = new ScoreInfo { ID = 1 };
+
+ Assert.That(score1, Is.EqualTo(score2));
+ }
+
+ [Test]
+ public void TestNonMatchingByHash()
+ {
+ ScoreInfo score1 = new ScoreInfo { Hash = "a" };
+ ScoreInfo score2 = new ScoreInfo { Hash = "b" };
+
+ Assert.That(score1, Is.Not.EqualTo(score2));
+ }
+
+ [Test]
+ public void TestMatchingByHash()
+ {
+ ScoreInfo score1 = new ScoreInfo { Hash = "a" };
+ ScoreInfo score2 = new ScoreInfo { Hash = "a" };
+
+ Assert.That(score1, Is.EqualTo(score2));
+ }
+
+ [Test]
+ public void TestNonMatchingByNull()
+ {
+ ScoreInfo score = new ScoreInfo();
+
+ Assert.That(score, Is.Not.EqualTo(null));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index f68d49dd3e..cef38bbbb8 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture]
public class LegacySkinDecoderTest
{
- [TestCase(true)]
- [TestCase(false)]
- public void TestDecodeSkinColours(bool hasColours)
+ [Test]
+ public void TestDecodeSkinColours()
{
var decoder = new LegacySkinDecoder();
- using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
+ using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
-
- List expectedColors;
-
- if (hasColours)
+ var expectedColors = new List
{
- expectedColors = new List
- {
- new Color4(142, 199, 255, 255),
- new Color4(255, 128, 128, 255),
- new Color4(128, 255, 255, 255),
- new Color4(100, 100, 100, 100),
- };
- }
- else
- expectedColors = new DefaultSkin().Configuration.ComboColours;
+ new Color4(142, 199, 255, 255),
+ new Color4(255, 128, 128, 255),
+ new Color4(128, 255, 255, 255),
+ new Color4(100, 100, 100, 100),
+ };
Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++)
@@ -45,6 +36,37 @@ namespace osu.Game.Tests.Skins
}
}
+ [Test]
+ public void TestDecodeEmptySkinColours()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var comboColors = decoder.Decode(stream).ComboColours;
+ var expectedColors = SkinConfiguration.DefaultComboColours;
+
+ Assert.AreEqual(expectedColors.Count, comboColors.Count);
+ for (int i = 0; i < expectedColors.Count; i++)
+ Assert.AreEqual(expectedColors[i], comboColors[i]);
+ }
+ }
+
+ [Test]
+ public void TestDecodeEmptySkinColoursNoFallback()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var skinConfiguration = decoder.Decode(stream);
+ skinConfiguration.AllowDefaultComboColoursFallback = false;
+ Assert.IsNull(skinConfiguration.ComboColours);
+ }
+ }
+
[Test]
public void TestDecodeGeneral()
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 8b9c648442..ed54cc982d 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
- private LegacySkin source1;
- private LegacySkin source2;
+ private SkinSource source1;
+ private SkinSource source2;
private SkinRequester requester;
[SetUp]
@@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestGlobalLookup()
{
- AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
}
[Test]
@@ -116,6 +117,28 @@ namespace osu.Game.Tests.Skins
});
}
+ [Test]
+ public void TestEmptyComboColours()
+ {
+ AddAssert("Check retrieved combo colours is skin default colours", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(SkinConfiguration.DefaultComboColours) ?? false);
+ }
+
+ [Test]
+ public void TestEmptyComboColoursNoFallback()
+ {
+ AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
+ new Color4(100, 150, 200, 255),
+ new Color4(55, 110, 166, 255),
+ new Color4(75, 125, 175, 255)
+ ));
+
+ AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
+
+ AddAssert("Check retrieved combo colours from source1", () =>
+ requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
+ }
+
[Test]
public void TestLegacyVersionLookup()
{
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
new file mode 100644
index 0000000000..589ec7e8aa
--- /dev/null
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -0,0 +1,427 @@
+// 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.Linq;
+using System.Threading;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Platform;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Scoring;
+using osu.Game.Screens;
+using osu.Game.Screens.Backgrounds;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.PlayerSettings;
+using osu.Game.Screens.Select;
+using osu.Game.Tests.Resources;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Background
+{
+ [TestFixture]
+ public class TestSceneUserDimBackgrounds : ManualInputManagerTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ScreenWithBeatmapBackground),
+ typeof(PlayerLoader),
+ typeof(Player),
+ typeof(UserDimContainer),
+ typeof(OsuScreen)
+ };
+
+ private DummySongSelect songSelect;
+ private TestPlayerLoader playerLoader;
+ private TestPlayer player;
+ private BeatmapManager manager;
+ private RulesetStore rulesets;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio)
+ {
+ Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
+ Dependencies.Cache(new OsuConfigManager(LocalStorage));
+
+ manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+
+ Beatmap.SetDefault();
+ }
+
+ [SetUp]
+ public virtual void SetUp() => Schedule(() =>
+ {
+ Child = new OsuScreenStack(songSelect = new DummySongSelect())
+ {
+ RelativeSizeAxes = Axes.Both
+ };
+ });
+
+ ///
+ /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
+ ///
+ [Test]
+ public void PlayerLoaderSettingsHoverTest()
+ {
+ setupUserSettings();
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ AddStep("Trigger background preview", () =>
+ {
+ InputManager.MoveMouseTo(playerLoader.ScreenPos);
+ InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
+ });
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ ///
+ /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
+ /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
+ /// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
+ ///
+ [Test]
+ public void PlayerLoaderTransitionTest()
+ {
+ performFullSetup();
+ AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
+ ///
+ [Test]
+ public void StoryboardBackgroundVisibilityTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Enable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ waitForDim();
+ AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
+ AddStep("Disable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = false;
+ player.StoryboardEnabled.Value = false;
+ });
+ waitForDim();
+ AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
+ }
+
+ ///
+ /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
+ ///
+ [Test]
+ public void StoryboardTransitionTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
+ }
+
+ ///
+ /// Ensure is properly accepting user-defined visual changes for a background.
+ ///
+ [Test]
+ public void DisableUserDimBackgroundTest()
+ {
+ performFullSetup();
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
+ AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Ensure is properly accepting user-defined visual changes for a storyboard.
+ ///
+ [Test]
+ public void DisableUserDimStoryboardTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Enable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
+ AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
+ waitForDim();
+ AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
+ AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
+ waitForDim();
+ AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
+ }
+
+ ///
+ /// Check if the visual settings container retains dim and blur when pausing
+ ///
+ [Test]
+ public void PauseTest()
+ {
+ performFullSetup(true);
+ AddStep("Pause", () => player.Pause());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Unpause", () => player.Resume());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Check if the visual settings container removes user dim when suspending for
+ ///
+ [Test]
+ public void TransitionTest()
+ {
+ performFullSetup();
+ FadeAccessibleResults results = null;
+ AddStep("Transition to Results", () => player.Push(results =
+ new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
+ AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
+ waitForDim();
+ AddAssert("Screen is undimmed, original background retained", () =>
+ songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if background gets undimmed and unblurred when leaving for
+ ///
+ [Test]
+ public void TransitionOutTest()
+ {
+ performFullSetup();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
+ ///
+ [Test]
+ public void ResumeFromPlayerTest()
+ {
+ performFullSetup();
+ AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
+ AddStep("Resume PlayerLoader", () => player.Restart());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ private void waitForDim() => AddWaitStep("Wait for dim", 5);
+
+ private void createFakeStoryboard() => AddStep("Create storyboard", () =>
+ {
+ player.StoryboardEnabled.Value = false;
+ player.ReplacesBackground.Value = false;
+ player.DimmableStoryboard.Add(new OsuSpriteText
+ {
+ Size = new Vector2(500, 50),
+ Alpha = 1,
+ Colour = Color4.White,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "THIS IS A STORYBOARD",
+ Font = new FontUsage(size: 50)
+ });
+ });
+
+ private void performFullSetup(bool allowPause = false)
+ {
+ setupUserSettings();
+
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
+
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ AddUntilStep("Wait for player to load", () => player.IsLoaded);
+ }
+
+ private void setupUserSettings()
+ {
+ AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddStep("Set default user settings", () =>
+ {
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
+ songSelect.DimLevel.Value = 0.7f;
+ songSelect.BlurLevel.Value = 0.4f;
+ });
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ rulesets?.Dispose();
+ }
+
+ private class DummySongSelect : PlaySongSelect
+ {
+ protected override BackgroundScreen CreateBackground()
+ {
+ FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
+ DimEnabled.BindTo(background.EnableUserDim);
+ return background;
+ }
+
+ public readonly Bindable DimEnabled = new Bindable();
+ public readonly Bindable DimLevel = new Bindable();
+ public readonly Bindable BlurLevel = new Bindable();
+
+ public new BeatmapCarousel Carousel => base.Carousel;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.DimLevel, DimLevel);
+ config.BindWith(OsuSetting.BlurLevel, BlurLevel);
+ }
+
+ public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
+
+ public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
+
+ public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
+
+ public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
+
+ public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
+
+ public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ ///
+ /// Make sure every time a screen gets pushed, the background doesn't get replaced
+ ///
+ /// Whether or not the original background (The one created in DummySongSelect) is still the current background
+ public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
+ }
+
+ private class FadeAccessibleResults : SoloResults
+ {
+ public FadeAccessibleResults(ScoreInfo score)
+ : base(score)
+ {
+ }
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ }
+
+ private class TestPlayer : Visual.TestPlayer
+ {
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
+
+ // Whether or not the player should be allowed to load.
+ public bool BlockLoad;
+
+ public Bindable StoryboardEnabled;
+ public readonly Bindable ReplacesBackground = new Bindable();
+ public readonly Bindable IsPaused = new Bindable();
+
+ public TestPlayer(bool allowPause = true)
+ : base(allowPause)
+ {
+ }
+
+ public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, CancellationToken token)
+ {
+ while (BlockLoad && !token.IsCancellationRequested)
+ Thread.Sleep(1);
+
+ StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
+ ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
+ DrawableRuleset.IsPaused.BindTo(IsPaused);
+ }
+ }
+
+ private class TestPlayerLoader : PlayerLoader
+ {
+ public VisualSettings VisualSettingsPos => VisualSettings;
+ public BackgroundScreen ScreenPos => Background;
+
+ public TestPlayerLoader(Player player)
+ : base(() => player)
+ {
+ }
+
+ public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ }
+
+ private class FadeAccessibleBackground : BackgroundScreenBeatmap
+ {
+ protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
+
+ public Color4 CurrentColour => dimmable.CurrentColour;
+
+ public float CurrentAlpha => dimmable.CurrentAlpha;
+
+ public float CurrentDim => dimmable.DimLevel;
+
+ public Vector2 CurrentBlur => Background.BlurSigma;
+
+ private TestDimmableBackground dimmable;
+
+ public FadeAccessibleBackground(WorkingBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+ }
+
+ private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
+ {
+ public Color4 CurrentColour => Content.Colour;
+ public float CurrentAlpha => Content.Alpha;
+
+ public new float DimLevel => base.DimLevel;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
index f858174ff2..fede99f450 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
@@ -1,422 +1,112 @@
// 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.Linq;
-using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input.Events;
-using osu.Framework.Input.States;
-using osu.Framework.Platform;
-using osu.Framework.Screens;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Scoring;
-using osu.Game.Screens;
-using osu.Game.Screens.Backgrounds;
-using osu.Game.Screens.Play;
-using osu.Game.Screens.Play.PlayerSettings;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Resources;
-using osu.Game.Users;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
- [TestFixture]
- public class TestSceneUserDimContainer : ManualInputManagerTestScene
+ public class TestSceneUserDimContainer : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ScreenWithBeatmapBackground),
- typeof(PlayerLoader),
- typeof(Player),
- typeof(UserDimContainer),
- typeof(OsuScreen)
- };
+ private TestUserDimContainer userDimContainer;
- private DummySongSelect songSelect;
- private TestPlayerLoader playerLoader;
- private TestPlayer player;
- private BeatmapManager manager;
- private RulesetStore rulesets;
+ private readonly BindableBool isBreakTime = new BindableBool();
+
+ private Bindable lightenDuringBreaks = new Bindable();
[BackgroundDependencyLoader]
- private void load(GameHost host, AudioManager audio)
+ private void load(OsuConfigManager config)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
- Dependencies.Cache(new OsuConfigManager(LocalStorage));
-
- manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
-
- Beatmap.SetDefault();
+ lightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks);
}
[SetUp]
- public virtual void SetUp() => Schedule(() =>
+ public void SetUp() => Schedule(() =>
{
- Child = new OsuScreenStack(songSelect = new DummySongSelect())
+ Child = userDimContainer = new TestUserDimContainer
{
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
+ Child = new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ },
};
+
+ userDimContainer.IsBreakTime.BindTo(isBreakTime);
+ isBreakTime.Value = false;
+
+ lightenDuringBreaks.Value = false;
});
- ///
- /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
- ///
+ private const float test_user_dim = 0.6f;
+ private const float test_user_dim_lightened = test_user_dim - UserDimContainer.BREAK_LIGHTEN_AMOUNT;
+
+ [TestCase(test_user_dim, test_user_dim_lightened)]
+ [TestCase(0.2f, 0.0f)]
+ [TestCase(0.0f, 0.0f)]
+ public void TestBreakLightening(float userDim, float expectedBreakDim)
+ {
+ AddStep($"set dim level {userDim}", () => userDimContainer.UserDimLevel.Value = userDim);
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(expectedBreakDim));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(userDim));
+ }
+
[Test]
- public void PlayerLoaderSettingsHoverTest()
+ public void TestEnableSettingDuringBreak()
{
- setupUserSettings();
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
- AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
- AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
- AddStep("Trigger background preview", () =>
- {
- InputManager.MoveMouseTo(playerLoader.ScreenPos);
- InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
- });
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
}
- ///
- /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
- /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
- /// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
- ///
[Test]
- public void PlayerLoaderTransitionTest()
+ public void TestDisableSettingDuringBreak()
{
- performFullSetup();
- AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
- AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
+ AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
}
- ///
- /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
- ///
[Test]
- public void StoryboardBackgroundVisibilityTest()
+ public void TestIgnoreUserSettings()
{
- performFullSetup();
- createFakeStoryboard();
- AddStep("Enable Storyboard", () =>
- {
- player.ReplacesBackground.Value = true;
- player.StoryboardEnabled.Value = true;
- });
- waitForDim();
- AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
- AddStep("Disable Storyboard", () =>
- {
- player.ReplacesBackground.Value = false;
- player.StoryboardEnabled.Value = false;
- });
- waitForDim();
- AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+ AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim));
+
+ AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true);
+ AddUntilStep("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
}
- ///
- /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
- ///
- [Test]
- public void StoryboardTransitionTest()
+ private class TestUserDimContainer : UserDimContainer
{
- performFullSetup();
- createFakeStoryboard();
- AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
- }
+ public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);
- ///
- /// Ensure is properly accepting user-defined visual changes for a background.
- ///
- [Test]
- public void DisableUserDimBackgroundTest()
- {
- performFullSetup();
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
- AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- }
-
- ///
- /// Ensure is properly accepting user-defined visual changes for a storyboard.
- ///
- [Test]
- public void DisableUserDimStoryboardTest()
- {
- performFullSetup();
- createFakeStoryboard();
- AddStep("Enable Storyboard", () =>
- {
- player.ReplacesBackground.Value = true;
- player.StoryboardEnabled.Value = true;
- });
- AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
- AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
- waitForDim();
- AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
- AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
- waitForDim();
- AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
- }
-
- ///
- /// Check if the visual settings container retains dim and blur when pausing
- ///
- [Test]
- public void PauseTest()
- {
- performFullSetup(true);
- AddStep("Pause", () => player.Pause());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Unpause", () => player.Resume());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- }
-
- ///
- /// Check if the visual settings container removes user dim when suspending for
- ///
- [Test]
- public void TransitionTest()
- {
- performFullSetup();
- var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } });
- AddStep("Transition to Results", () => player.Push(results));
- AddUntilStep("Wait for results is current", results.IsCurrentScreen);
- waitForDim();
- AddAssert("Screen is undimmed, original background retained", () =>
- songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
- }
-
- ///
- /// Check if background gets undimmed and unblurred when leaving for
- ///
- [Test]
- public void TransitionOutTest()
- {
- performFullSetup();
- AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
- }
-
- ///
- /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
- ///
- [Test]
- public void ResumeFromPlayerTest()
- {
- performFullSetup();
- AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
- AddStep("Resume PlayerLoader", () => player.Restart());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
- }
-
- private void waitForDim() => AddWaitStep("Wait for dim", 5);
-
- private void createFakeStoryboard() => AddStep("Create storyboard", () =>
- {
- player.StoryboardEnabled.Value = false;
- player.ReplacesBackground.Value = false;
- player.DimmableStoryboard.Add(new OsuSpriteText
- {
- Size = new Vector2(500, 50),
- Alpha = 1,
- Colour = Color4.White,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = "THIS IS A STORYBOARD",
- Font = new FontUsage(size: 50)
- });
- });
-
- private void performFullSetup(bool allowPause = false)
- {
- setupUserSettings();
-
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
-
- AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
- AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Wait for player to load", () => player.IsLoaded);
- }
-
- private void setupUserSettings()
- {
- AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
- AddStep("Set default user settings", () =>
- {
- Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
- songSelect.DimLevel.Value = 0.7f;
- songSelect.BlurLevel.Value = 0.4f;
- });
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- rulesets?.Dispose();
- }
-
- private class DummySongSelect : PlaySongSelect
- {
- protected override BackgroundScreen CreateBackground()
- {
- FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
- DimEnabled.BindTo(background.EnableUserDim);
- return background;
- }
-
- public readonly Bindable DimEnabled = new Bindable();
- public readonly Bindable DimLevel = new Bindable();
- public readonly Bindable BlurLevel = new Bindable();
-
- public new BeatmapCarousel Carousel => base.Carousel;
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
- {
- config.BindWith(OsuSetting.DimLevel, DimLevel);
- config.BindWith(OsuSetting.BlurLevel, BlurLevel);
- }
-
- public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value);
-
- public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
-
- public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
-
- public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
-
- public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
-
- public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
-
- ///
- /// Make sure every time a screen gets pushed, the background doesn't get replaced
- ///
- /// Whether or not the original background (The one created in DummySongSelect) is still the current background
- public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
- }
-
- private class FadeAccessibleResults : SoloResults
- {
- public FadeAccessibleResults(ScoreInfo score)
- : base(score)
- {
- }
-
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
- }
-
- private class TestPlayer : Visual.TestPlayer
- {
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
-
- public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
-
- // Whether or not the player should be allowed to load.
- public bool BlockLoad;
-
- public Bindable StoryboardEnabled;
- public readonly Bindable ReplacesBackground = new Bindable();
- public readonly Bindable IsPaused = new Bindable();
-
- public TestPlayer(bool allowPause = true)
- : base(allowPause)
- {
- }
-
- public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config, CancellationToken token)
- {
- while (BlockLoad && !token.IsCancellationRequested)
- Thread.Sleep(1);
-
- StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
- ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
- DrawableRuleset.IsPaused.BindTo(IsPaused);
- }
- }
-
- private class TestPlayerLoader : PlayerLoader
- {
- public VisualSettings VisualSettingsPos => VisualSettings;
- public BackgroundScreen ScreenPos => Background;
-
- public TestPlayerLoader(Player player)
- : base(() => player)
- {
- }
-
- public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
-
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
- }
-
- private class FadeAccessibleBackground : BackgroundScreenBeatmap
- {
- protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
-
- public Color4 CurrentColour => dimmable.CurrentColour;
-
- public float CurrentAlpha => dimmable.CurrentAlpha;
-
- public Vector2 CurrentBlur => Background.BlurSigma;
-
- private TestDimmableBackground dimmable;
-
- public FadeAccessibleBackground(WorkingBeatmap beatmap)
- : base(beatmap)
- {
- }
- }
-
- private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
- {
- public Color4 CurrentColour => Content.Colour;
- public float CurrentAlpha => Content.Alpha;
+ public new Bindable UserDimLevel => base.UserDimLevel;
}
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
index 9f16e1d781..3562689482 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
@@ -11,10 +13,21 @@ namespace osu.Game.Tests.Visual.Editor
[TestFixture]
public class TestSceneComposeScreen : EditorClockTestScene
{
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap =
+ new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
+ });
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
+
Child = new ComposeScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
index e4c987923c..847d168e51 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
@@ -7,9 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -22,15 +20,15 @@ namespace osu.Game.Tests.Visual.Editor
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
}
@@ -44,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
- new TestDistanceSnapGrid(new HitObject(), grid_position)
+ new TestDistanceSnapGrid()
};
});
@@ -73,7 +71,7 @@ namespace osu.Game.Tests.Visual.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
- new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
+ new TestDistanceSnapGrid(100)
};
});
}
@@ -82,68 +80,68 @@ namespace osu.Game.Tests.Visual.Editor
{
public new float DistanceSpacing => base.DistanceSpacing;
- public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
- : base(hitObject, nextHitObject, centrePosition)
+ public TestDistanceSnapGrid(double? endTime = null)
+ : base(grid_position, 0, endTime)
{
}
- protected override void CreateContent(Vector2 centrePosition)
+ protected override void CreateContent(Vector2 startPosition)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
- Position = centrePosition
+ Position = startPosition
});
int beatIndex = 0;
- for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
+ for (float s = startPosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
- Position = new Vector2(s, centrePosition.Y),
+ Position = new Vector2(s, startPosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
- for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
+ for (float s = startPosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
- Position = new Vector2(s, centrePosition.Y),
+ Position = new Vector2(s, startPosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
- for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
+ for (float s = startPosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
- Position = new Vector2(centrePosition.X, s),
+ Position = new Vector2(startPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
- for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
+ for (float s = startPosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
- Position = new Vector2(centrePosition.X, s),
+ Position = new Vector2(startPosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
index 6e5b3b93e9..29575cb42e 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
@@ -13,6 +13,8 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
@@ -25,6 +27,7 @@ namespace osu.Game.Tests.Visual.Editor
public override IReadOnlyList RequiredTypes => new[]
{
typeof(TimelineArea),
+ typeof(TimelineHitObjectDisplay),
typeof(Timeline),
typeof(TimelineButton),
typeof(CentreMarker)
@@ -35,6 +38,8 @@ namespace osu.Game.Tests.Visual.Editor
{
Beatmap.Value = new WaveformTestBeatmap(audio);
+ var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap);
+
Children = new Drawable[]
{
new FillFlowContainer
@@ -50,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editor
},
new TimelineArea
{
+ Child = new TimelineHitObjectDisplay(editorBeatmap),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
@@ -62,8 +68,11 @@ namespace osu.Game.Tests.Visual.Editor
{
private readonly Drawable marker;
- private readonly IBindable beatmap = new Bindable();
- private IAdjustableClock adjustableClock;
+ [Resolved]
+ private IBindable beatmap { get; set; }
+
+ [Resolved]
+ private IAdjustableClock adjustableClock { get; set; }
public AudioVisualiser()
{
@@ -85,13 +94,6 @@ namespace osu.Game.Tests.Visual.Editor
};
}
- [BackgroundDependencyLoader]
- private void load(IAdjustableClock adjustableClock, IBindable beatmap)
- {
- this.adjustableClock = adjustableClock;
- this.beatmap.BindTo(beatmap);
- }
-
protected override void Update()
{
base.Update();
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
index b7c7028b52..c001c83877 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -59,9 +60,12 @@ namespace osu.Game.Tests.Visual.Editor
},
});
+ var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
+
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs(clock);
Dependencies.CacheAs(clock);
+ Dependencies.CacheAs(editorBeatmap);
Child = new OsuHitObjectComposer(new OsuRuleset());
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
index 121853d8d0..adfed9a299 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
@@ -5,7 +5,8 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editor
@@ -25,10 +26,13 @@ namespace osu.Game.Tests.Visual.Editor
typeof(RowAttribute)
};
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new TimingScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
index da8702209c..fd248abbc9 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
index 5ee109e3dd..069b965d9b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new ScoreAccessiblePlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
index e3688c276f..72fc6d8bd2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
@@ -6,7 +6,7 @@ using osu.Game.Rulesets.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index 684e79b3f5..46f62b9541 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -10,7 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var ruleset = new TestScrollingRuleset();
- drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty());
+ drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo));
drawableRuleset.FrameStablePlayback = false;
overrideAction?.Invoke(drawableRuleset);
@@ -194,16 +194,11 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestScrollingRuleset : Ruleset
{
- public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
-
public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException();
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
@@ -222,7 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public new Bindable TimeRange => base.TimeRange;
- public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public TestDrawableScrollingRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
TimeRange.Value = time_range;
@@ -273,12 +268,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestBeatmapConverter : BeatmapConverter
{
- public TestBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes => new[] { typeof(HitObject) };
+ public override bool CanConvert() => true;
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
index f4e8a68819..81050b1637 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Array.Empty();
+ SelectedMods.Value = Array.Empty();
return new FailPlayer();
}
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- ScoreProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, __) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
index cca6301b02..2045072c79 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Array.Empty();
+ SelectedMods.Value = Array.Empty();
return new FailPlayer();
}
@@ -22,12 +22,12 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
- AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1);
+ AddAssert("total judgements == 1", () => ((FailPlayer)Player).HealthProcessor.JudgedHits >= 1);
}
private class FailPlayer : TestPlayer
{
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new HealthProcessor HealthProcessor => base.HealthProcessor;
public FailPlayer()
: base(false, false)
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete()
{
base.LoadComplete();
- ScoreProcessor.FailConditions += (_, __) => true;
+ HealthProcessor.FailConditions += (_, __) => true;
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index b2b58a63fb..78c3b22fb9 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -7,7 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
@@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new RulesetExposingPlayer();
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
new file mode 100644
index 0000000000..ee58219cd3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -0,0 +1,81 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneHUDOverlay : ManualInputManagerTestScene
+ {
+ private HUDOverlay hudOverlay;
+
+ private Drawable hideTarget => hudOverlay.KeyCounter; // best way of checking hideTargets without exposing.
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Test]
+ public void TestShownByDefault()
+ {
+ createNew();
+
+ AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
+
+ AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
+ AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestFadesInOnLoadComplete()
+ {
+ float? initialAlpha = null;
+
+ createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
+ AddUntilStep("wait for load", () => hudOverlay.IsAlive);
+ AddAssert("initial alpha was less than 1", () => initialAlpha != null && initialAlpha < 1);
+ }
+
+ [Test]
+ public void TestHideExternally()
+ {
+ createNew();
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+ AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
+ }
+
+ [Test]
+ public void TestExternalHideDoesntAffectConfig()
+ {
+ bool originalConfigValue = false;
+
+ createNew();
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+ }
+
+ private void createNew(Action action = null)
+ {
+ AddStep("create overlay", () =>
+ {
+ Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
+
+ action?.Invoke(hudOverlay);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
index ad747e88e1..e7b3e007fc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osuTK.Input;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
index 0150c6ea74..563d6be0da 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs
@@ -5,7 +5,7 @@ using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Osu;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
new file mode 100644
index 0000000000..3473b03eaf
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneNightcoreBeatContainer.cs
@@ -0,0 +1,38 @@
+// 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.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Beatmaps.Timing;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Visual.UserInterface;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneNightcoreBeatContainer : TestSceneBeatSyncedContainer
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ModNightcore<>)
+ };
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+
+ Beatmap.Value.Track.Start();
+ Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000);
+
+ Add(new ModNightcore.NightcoreBeatContainer());
+
+ AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
+ AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index 6e8975f11b..1a83e35e4f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestResumeWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resume();
@@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseWithResumeOverlay()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("move cursor to button", () =>
InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType().First().ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1);
+ AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm();
resumeAndConfirm();
@@ -115,8 +115,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestExitTooSoon()
{
- pauseAndConfirm();
+ AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
+ pauseAndConfirm();
resume();
AddStep("exit too soon", () => Player.Exit());
@@ -176,7 +177,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestExitFromGameplay()
{
AddStep("exit", () => Player.Exit());
+ confirmPaused();
+ AddStep("exit", () => Player.Exit());
confirmExited();
}
@@ -214,6 +217,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestRestartAfterResume()
{
+ AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000));
+
pauseAndConfirm();
resumeAndConfirm();
restart();
@@ -280,9 +285,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class PausePlayer : TestPlayer
{
- public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
-
- public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
new file mode 100644
index 0000000000..3513b6c25a
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs
@@ -0,0 +1,51 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state.
+ public class TestScenePauseWhenInactive : PlayerTestScene
+ {
+ protected new TestPlayer Player => (TestPlayer)base.Player;
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = (Beatmap)base.CreateBeatmap(ruleset);
+
+ beatmap.HitObjects.RemoveAll(h => h.StartTime < 30000);
+
+ return beatmap;
+ }
+
+ [Resolved]
+ private GameHost host { get; set; }
+
+ public TestScenePauseWhenInactive()
+ : base(new OsuRuleset())
+ {
+ }
+
+ [Test]
+ public void TestDoesntPauseDuringIntro()
+ {
+ AddStep("set inactive", () => ((Bindable)host.IsActive).Value = false);
+
+ AddStep("resume player", () => Player.GameplayClockContainer.Start());
+ AddAssert("ensure not paused", () => !Player.GameplayClockContainer.IsPaused.Value);
+ AddUntilStep("wait for pause", () => Player.GameplayClockContainer.IsPaused.Value);
+ AddAssert("time of pause is after gameplay start time", () => Player.GameplayClockContainer.GameplayClock.CurrentTime >= Player.DrawableRuleset.GameplayStartTime);
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new TestPlayer(true, true, true);
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 74ae641bfe..ad5950d9fc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -5,13 +5,14 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
+using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@@ -19,6 +20,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
@@ -55,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ foreach (var mod in SelectedMods.Value.OfType())
+ mod.ApplyToTrack(Beatmap.Value.Track);
+
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
@@ -63,6 +68,24 @@ namespace osu.Game.Tests.Visual.Gameplay
}));
}
+ ///
+ /// When exits early, it has to wait for the player load task
+ /// to complete before running disposal on player. This previously caused an issue where mod
+ /// speed adjustments were undone too late, causing cross-screen pollution.
+ ///
+ [Test]
+ public void TestEarlyExit()
+ {
+ AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+ AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
+ AddStep("exit loader", () => loader.Exit());
+ AddUntilStep("wait for not current", () => !loader.IsCurrentScreen());
+ AddAssert("player did not load", () => !player.IsLoaded);
+ AddUntilStep("player disposed", () => loader.DisposalTask?.IsCompleted == true);
+ AddAssert("mod rate still applied", () => Beatmap.Value.Track.Rate != 1);
+ }
+
[Test]
public void TestBlockLoadViaMouseMovement()
{
@@ -100,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
TestMod playerMod1 = null;
TestMod playerMod2 = null;
- AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
+ AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
@@ -123,6 +146,18 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied);
}
+ [Test]
+ public void TestModDisplayChanges()
+ {
+ var testMod = new TestMod();
+
+ AddStep("load player", () => ResetPlayer(true));
+
+ AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
+ AddStep("set test mod in loader", () => loader.Mods.Value = new[] { testMod });
+ AddAssert("test mod is displayed", () => (TestMod)loader.DisplayedMods.Single() == testMod);
+ }
+
[Test]
public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault);
@@ -196,6 +231,10 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public new VisualSettings VisualSettings => base.VisualSettings;
+ public new Task DisposalTask => base.DisposalTask;
+
+ public IReadOnlyList DisplayedMods => MetadataInfo.Mods.Value;
+
public TestPlayerLoader(Func createPlayer)
: base(createPlayer)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 7b22fedbd5..8cb44de8cb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -5,11 +5,12 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
-using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Users;
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Game.Rulesets;
using osu.Game.Screens.Ranking.Pages;
namespace osu.Game.Tests.Visual.Gameplay
@@ -17,6 +18,9 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneReplayDownloadButton : OsuTestScene
{
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
public override IReadOnlyList RequiredTypes => new[]
{
typeof(ReplayDownloadButton)
@@ -49,16 +53,15 @@ namespace osu.Game.Tests.Visual.Gameplay
{
return new APILegacyScoreInfo
{
- ID = 1,
OnlineScoreID = 2553163309,
- Ruleset = new OsuRuleset().RulesetInfo,
+ OnlineRulesetID = 0,
Replay = replayAvailable,
User = new User
{
Id = 39828,
Username = @"WubWoofWolf",
}
- };
+ }.CreateScoreInfo(rulesets);
}
private class TestReplayDownloadButton : ReplayDownloadButton
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
index 080a287b48..ffd6f55b53 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs
@@ -4,7 +4,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index aa80819694..8629522dc2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -28,12 +30,16 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
+ private const int spawn_interval = 5000;
+
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
+ private ScheduledDelegate hitObjectSpawnDelegate;
- public TestSceneScrollingHitObjects()
+ [SetUp]
+ public void Setup() => Schedule(() =>
{
- Add(new GridContainer
+ Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
@@ -43,48 +49,66 @@ namespace osu.Game.Tests.Visual.Gameplay
scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[0] = new TestPlayfield()
+ Child = playfields[0] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
- scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[1] = new TestPlayfield()
+ Child = playfields[1] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
},
new Drawable[]
{
- scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Left)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[2] = new TestPlayfield()
+ Child = playfields[2] = new TestPlayfield(),
+ TimeRange = spawn_interval
},
- scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up)
+ scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{
RelativeSizeAxes = Axes.Both,
- Child = playfields[3] = new TestPlayfield()
+ Child = playfields[3] = new TestPlayfield(),
+ TimeRange = spawn_interval
}
}
}
- });
+ };
+ setUpHitObjects();
+ });
+
+ private void setUpHitObjects()
+ {
+ scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
+
+ for (int i = 0; i <= spawn_interval; i += 1000)
+ addHitObject(Time.Current + i);
+
+ hitObjectSpawnDelegate?.Cancel();
+ hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true);
+ }
+
+ [Test]
+ public void TestScrollAlgorithms()
+ {
AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
- AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v));
- AddStep("Add control point", () => addControlPoint(Time.Current + 5000));
+ AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
+ AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval));
}
- protected override void LoadComplete()
+ [Test]
+ public void TestScrollLifetime()
{
- base.LoadComplete();
-
- scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
-
- for (int i = 0; i <= 5000; i += 1000)
- addHitObject(Time.Current + i);
-
- Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true);
+ AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
+ // scroll container time range must be less than the rate of spawning hitobjects
+ // otherwise the hitobjects will spawn already partly visible on screen and look wrong
+ AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0));
}
private void addHitObject(double time)
@@ -207,7 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time })
{
- Origin = Anchor.Centre;
+ Origin = Anchor.Custom;
+ OriginPosition = new Vector2(75 / 4.0f);
+
AutoSizeAxes = Axes.Both;
AddInternal(new Box { Size = new Vector2(75) });
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 875e7b9758..4c5c18f38a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
@@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
requestCount = 0;
increment = skip_time;
- Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new Mod[] { }, 0)
+ Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs
new file mode 100644
index 0000000000..606395c289
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs
@@ -0,0 +1,193 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Lines;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSliderPath : OsuTestScene
+ {
+ private readonly SmoothPath drawablePath;
+ private SliderPath path;
+
+ public TestSceneSliderPath()
+ {
+ Child = drawablePath = new SmoothPath
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ }
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ path = new SliderPath();
+ });
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (path != null)
+ {
+ List vertices = new List();
+ path.GetPathToProgress(vertices, 0, 1);
+
+ drawablePath.Vertices = vertices;
+ }
+ }
+
+ [Test]
+ public void TestEmptyPath()
+ {
+ }
+
+ [TestCase(PathType.Linear)]
+ [TestCase(PathType.Bezier)]
+ [TestCase(PathType.Catmull)]
+ [TestCase(PathType.PerfectCurve)]
+ public void TestSingleSegment(PathType type)
+ => AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+
+ [TestCase(PathType.Linear)]
+ [TestCase(PathType.Bezier)]
+ [TestCase(PathType.Catmull)]
+ [TestCase(PathType.PerfectCurve)]
+ public void TestMultipleSegment(PathType type)
+ {
+ AddStep("create path", () =>
+ {
+ path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero));
+ path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero));
+ });
+ }
+
+ [Test]
+ public void TestAddControlPoint()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100))));
+ AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = { Value = new Vector2(100) } }));
+ }
+
+ [Test]
+ public void TestInsertControlPoint()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100))));
+ AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = { Value = new Vector2(0, 100) } }));
+ }
+
+ [Test]
+ public void TestRemoveControlPoint()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
+ }
+
+ [Test]
+ public void TestChangePathType()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("change type to bezier", () => path.ControlPoints[0].Type.Value = PathType.Bezier);
+ }
+
+ [Test]
+ public void TestAddSegmentByChangingType()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))));
+ AddStep("change second point type to bezier", () => path.ControlPoints[1].Type.Value = PathType.Bezier);
+ }
+
+ [Test]
+ public void TestRemoveSegmentByChangingType()
+ {
+ AddStep("create path", () =>
+ {
+ path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
+ path.ControlPoints[1].Type.Value = PathType.Bezier;
+ });
+
+ AddStep("change second point type to null", () => path.ControlPoints[1].Type.Value = null);
+ }
+
+ [Test]
+ public void TestRemoveSegmentByRemovingControlPoint()
+ {
+ AddStep("create path", () =>
+ {
+ path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
+ path.ControlPoints[1].Type.Value = PathType.Bezier;
+ });
+
+ AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
+ }
+
+ [TestCase(2)]
+ [TestCase(4)]
+ public void TestPerfectCurveFallbackScenarios(int points)
+ {
+ AddStep("create path", () =>
+ {
+ switch (points)
+ {
+ case 2:
+ path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100)));
+ break;
+
+ case 4:
+ path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
+ break;
+ }
+ });
+ }
+
+ [Test]
+ public void TestLengthenLastSegment()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300);
+ }
+
+ [Test]
+ public void TestShortenLastSegment()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150);
+ }
+
+ [Test]
+ public void TestShortenFirstSegment()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50);
+ }
+
+ [Test]
+ public void TestShortenToZeroLength()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0);
+ }
+
+ [Test]
+ public void TestShortenToNegativeLength()
+ {
+ AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
+ AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
+ }
+
+ private List createSegment(PathType type, params Vector2[] controlPoints)
+ {
+ var points = controlPoints.Select(p => new PathControlPoint { Position = { Value = p } }).ToList();
+ points[0].Type.Value = type;
+ return points;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
index af21007efe..9a217ae416 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Rulesets.Objects;
using osu.Game.Screens.Play;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
index 68ad0b42b4..1e3e06ce7a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapPanel.cs
@@ -7,7 +7,7 @@ using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Framework.Allocation;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
index 50df4022dc..1ac914e27d 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
- AddStep(@"clear users", () => Room.Participants.Value = new User[] { });
+ AddStep(@"clear users", () => Room.Participants.Value = System.Array.Empty());
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs
index 7915a981dd..58e9240026 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
{
- protected override APIRequest FetchScores(Action> scoresCallback)
+ protected override APIRequest FetchScores(Action> scoresCallback)
{
var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
@@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
return null;
}
- private APIRoomScoreInfo createRoomScore(int id) => new APIRoomScoreInfo
+ private APIUserScoreAggregate createRoomScore(int id) => new APIUserScoreAggregate
{
User = new User { Id = id, Username = $"User {id}" },
Accuracy = 0.98,
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
index 2a45e68c0a..990e0a166b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
@@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
- BeatmapSetInfo createSet() => new BeatmapSetInfo
+ static BeatmapSetInfo createSet() => new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
Beatmaps = new List
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
index 80fad44593..2b572c1f6c 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
@@ -8,7 +8,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Screens.Select.Details;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
index 16e47c5df9..c98b65ded7 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Overlays.Chat.Tabs;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
index 546f6ac182..d47c972564 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneNewsOverlay.cs
@@ -1,23 +1,66 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Framework.Graphics;
using osu.Game.Overlays;
+using osu.Game.Overlays.News;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneNewsOverlay : OsuTestScene
{
- private NewsOverlay news;
+ private TestNewsOverlay news;
protected override void LoadComplete()
{
base.LoadComplete();
- Add(news = new NewsOverlay());
+ Add(news = new TestNewsOverlay());
AddStep(@"Show", news.Show);
AddStep(@"Hide", news.Hide);
AddStep(@"Show front page", () => news.ShowFrontPage());
AddStep(@"Custom article", () => news.Current.Value = "Test Article 101");
+
+ AddStep(@"Article covers", () => news.LoadAndShowContent(new NewsCoverTest()));
+ }
+
+ private class TestNewsOverlay : NewsOverlay
+ {
+ public new void LoadAndShowContent(NewsContent content) => base.LoadAndShowContent(content);
+ }
+
+ private class NewsCoverTest : NewsContent
+ {
+ public NewsCoverTest()
+ {
+ Spacing = new osuTK.Vector2(0, 10);
+
+ var article = new NewsArticleCover.ArticleInfo
+ {
+ Author = "Ephemeral",
+ CoverUrl = "https://assets.ppy.sh/artists/58/header.jpg",
+ Time = new DateTime(2019, 12, 4),
+ Title = "New Featured Artist: Kurokotei"
+ };
+
+ Children = new Drawable[]
+ {
+ new NewsArticleCover(article)
+ {
+ Height = 200
+ },
+ new NewsArticleCover(article)
+ {
+ Height = 120
+ },
+ new NewsArticleCover(article)
+ {
+ RelativeSizeAxes = Axes.None,
+ Size = new osuTK.Vector2(400, 200),
+ }
+ };
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs
new file mode 100644
index 0000000000..468239cf08
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneProfileCounterPill.cs
@@ -0,0 +1,42 @@
+// 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 NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Profile.Sections;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneProfileCounterPill : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CounterPill)
+ };
+
+ private readonly CounterPill pill;
+ private readonly BindableInt value = new BindableInt();
+
+ public TestSceneProfileCounterPill()
+ {
+ Child = pill = new CounterPill
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = value }
+ };
+ }
+
+ [Test]
+ public void TestVisibility()
+ {
+ AddStep("Set value to 0", () => value.Value = 0);
+ AddAssert("Check hidden", () => !pill.IsPresent);
+ AddStep("Set value to 10", () => value.Value = 10);
+ AddAssert("Check visible", () => pill.IsPresent);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
index c0da605cdb..e708934bc3 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs
@@ -68,9 +68,7 @@ namespace osu.Game.Tests.Visual.Online
};
AddStep("Set country", () => countryBindable.Value = country);
- AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
- AddAssert("Check country is Null", () => countryBindable.Value == null);
AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry);
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
index 849ca2defc..0edf104da0 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeaderTitle.cs
@@ -43,11 +43,6 @@ namespace osu.Game.Tests.Visual.Online
FullName = "United States"
};
- AddStep("Set country", () => countryBindable.Value = countryA);
- AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
- AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
- AddAssert("Check country is Null", () => countryBindable.Value == null);
-
AddStep("Set country 1", () => countryBindable.Value = countryA);
AddStep("Set country 2", () => countryBindable.Value = countryB);
AddStep("Set null country", () => countryBindable.Value = null);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs
new file mode 100644
index 0000000000..568e36df4c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs
@@ -0,0 +1,86 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Overlays.Rankings.Tables;
+using osu.Framework.Allocation;
+using osu.Game.Overlays;
+using NUnit.Framework;
+using osu.Game.Users;
+using osu.Framework.Bindables;
+using osu.Game.Overlays.Rankings;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsOverlay : OsuTestScene
+ {
+ protected override bool UseOnlineAPI => true;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PerformanceTable),
+ typeof(ScoresTable),
+ typeof(CountriesTable),
+ typeof(TableRowBackground),
+ typeof(UserBasedTable),
+ typeof(RankingsTable<>),
+ typeof(RankingsOverlay)
+ };
+
+ [Cached]
+ private RankingsOverlay rankingsOverlay;
+
+ private readonly Bindable countryBindable = new Bindable();
+ private readonly Bindable scope = new Bindable();
+
+ public TestSceneRankingsOverlay()
+ {
+ Add(rankingsOverlay = new TestRankingsOverlay
+ {
+ Country = { BindTarget = countryBindable },
+ Scope = { BindTarget = scope },
+ });
+ }
+
+ [Test]
+ public void TestShow()
+ {
+ AddStep("Show", rankingsOverlay.Show);
+ }
+
+ [Test]
+ public void TestFlagScopeDependency()
+ {
+ AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score);
+ AddAssert("Check country is Null", () => countryBindable.Value == null);
+ AddStep("Set country", () => countryBindable.Value = us_country);
+ AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance);
+ }
+
+ [Test]
+ public void TestShowCountry()
+ {
+ AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country));
+ }
+
+ [Test]
+ public void TestHide()
+ {
+ AddStep("Hide", rankingsOverlay.Hide);
+ }
+
+ private static readonly Country us_country = new Country
+ {
+ FlagName = "US",
+ FullName = "United States"
+ };
+
+ private class TestRankingsOverlay : RankingsOverlay
+ {
+ public new Bindable Country => base.Country;
+
+ public new Bindable Scope => base.Scope;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs
new file mode 100644
index 0000000000..93da2a439e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs
@@ -0,0 +1,129 @@
+// 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 osu.Framework.Graphics.Containers;
+using osu.Game.Overlays.Rankings.Tables;
+using osu.Framework.Graphics;
+using osu.Game.Online.API.Requests;
+using osu.Game.Rulesets;
+using osu.Game.Graphics.UserInterface;
+using System.Threading;
+using osu.Game.Online.API;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Rulesets.Catch;
+using osu.Framework.Allocation;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneRankingsTables : OsuTestScene
+ {
+ protected override bool UseOnlineAPI => true;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PerformanceTable),
+ typeof(ScoresTable),
+ typeof(CountriesTable),
+ typeof(TableRowBackground),
+ typeof(UserBasedTable),
+ typeof(RankingsTable<>)
+ };
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly BasicScrollContainer scrollFlow;
+ private readonly DimmedLoadingLayer loading;
+ private CancellationTokenSource cancellationToken;
+ private APIRequest request;
+
+ public TestSceneRankingsTables()
+ {
+ Children = new Drawable[]
+ {
+ scrollFlow = new BasicScrollContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Width = 0.8f,
+ },
+ loading = new DimmedLoadingLayer(),
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null));
+ AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo));
+ AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo));
+ AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10));
+ }
+
+ private void createCountryTable(RulesetInfo ruleset, int page = 1)
+ {
+ onLoadStarted();
+
+ request = new GetCountryRankingsRequest(ruleset, page);
+ ((GetCountryRankingsRequest)request).Success += rankings => Schedule(() =>
+ {
+ var table = new CountriesTable(page, rankings.Countries);
+ loadTable(table);
+ });
+
+ api.Queue(request);
+ }
+
+ private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1)
+ {
+ onLoadStarted();
+
+ request = new GetUserRankingsRequest(ruleset, country: country, page: page);
+ ((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
+ {
+ var table = new PerformanceTable(page, rankings.Users);
+ loadTable(table);
+ });
+
+ api.Queue(request);
+ }
+
+ private void createScoreTable(RulesetInfo ruleset, int page = 1)
+ {
+ onLoadStarted();
+
+ request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page);
+ ((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
+ {
+ var table = new ScoresTable(page, rankings.Users);
+ loadTable(table);
+ });
+
+ api.Queue(request);
+ }
+
+ private void onLoadStarted()
+ {
+ loading.Show();
+ request?.Cancel();
+ cancellationToken?.Cancel();
+ cancellationToken = new CancellationTokenSource();
+ }
+
+ private void loadTable(Drawable table)
+ {
+ LoadComponentAsync(table, t =>
+ {
+ scrollFlow.Clear();
+ scrollFlow.Add(t);
+ loading.Hide();
+ }, cancellationToken.Token);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
index b26de1984a..1b136d9e41 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs
@@ -1,4 +1,4 @@
-// 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 System;
@@ -6,12 +6,10 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Scores;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Users;
using osuTK.Graphics;
@@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"ES",
},
},
- Mods = new Mod[]
+ Mods = new[]
{
- new OsuModDoubleTime(),
- new OsuModHidden(),
- new OsuModFlashlight(),
- new OsuModHardRock(),
+ new OsuModDoubleTime().Acronym,
+ new OsuModHidden().Acronym,
+ new OsuModFlashlight().Acronym,
+ new OsuModHardRock().Acronym,
},
Rank = ScoreRank.XH,
PP = 200,
@@ -91,11 +89,11 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"BR",
},
},
- Mods = new Mod[]
+ Mods = new[]
{
- new OsuModDoubleTime(),
- new OsuModHidden(),
- new OsuModFlashlight(),
+ new OsuModDoubleTime().Acronym,
+ new OsuModHidden().Acronym,
+ new OsuModFlashlight().Acronym,
},
Rank = ScoreRank.S,
PP = 190,
@@ -115,10 +113,10 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"JP",
},
},
- Mods = new Mod[]
+ Mods = new[]
{
- new OsuModDoubleTime(),
- new OsuModHidden(),
+ new OsuModDoubleTime().Acronym,
+ new OsuModHidden().Acronym,
},
Rank = ScoreRank.B,
PP = 180,
@@ -138,9 +136,9 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"CA",
},
},
- Mods = new Mod[]
+ Mods = new[]
{
- new OsuModDoubleTime(),
+ new OsuModDoubleTime().Acronym,
},
Rank = ScoreRank.C,
PP = 170,
@@ -208,12 +206,12 @@ namespace osu.Game.Tests.Visual.Online
FlagName = @"ES",
},
},
- Mods = new Mod[]
+ Mods = new[]
{
- new OsuModDoubleTime(),
- new OsuModHidden(),
- new OsuModFlashlight(),
- new OsuModHardRock(),
+ new OsuModDoubleTime().Acronym,
+ new OsuModHidden().Acronym,
+ new OsuModFlashlight().Acronym,
+ new OsuModHardRock().Acronym,
},
Rank = ScoreRank.XH,
PP = 200,
@@ -226,10 +224,13 @@ namespace osu.Game.Tests.Visual.Online
foreach (var s in allScores.Scores)
{
- s.Statistics.Add(HitResult.Great, RNG.Next(2000));
- s.Statistics.Add(HitResult.Good, RNG.Next(2000));
- s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
- s.Statistics.Add(HitResult.Miss, RNG.Next(2000));
+ s.Statistics = new Dictionary
+ {
+ { "count_300", RNG.Next(2000) },
+ { "count_100", RNG.Next(2000) },
+ { "count_50", RNG.Next(2000) },
+ { "count_miss", RNG.Next(2000) }
+ };
}
AddStep("Load all scores", () =>
diff --git a/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs
index 4702d24125..f14c75084f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneTotalCommentsCounter.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Overlays.Comments;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
namespace osu.Game.Tests.Visual.Online
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
index 63b8acb234..63b46c991f 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileHeader),
typeof(RankGraph),
typeof(LineGraph),
- typeof(OverlayHeaderTabControl),
+ typeof(TabControlOverlayHeader.OverlayHeaderTabControl),
typeof(CentreHeaderContainer),
typeof(BottomHeaderContainer),
typeof(DetailHeaderContainer),
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 98da63508b..15f9c9a013 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
},
Title = "osu!volunteer",
Colour = "ff0000",
- Achievements = new User.UserAchievement[0],
+ Achievements = Array.Empty(),
};
public TestSceneUserProfileOverlay()
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
index d09a50b12c..048a1950fd 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
- new User { PreviousUsernames = new string[0] },
+ new User { PreviousUsernames = Array.Empty() },
null
};
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs
new file mode 100644
index 0000000000..e3dae9c27e
--- /dev/null
+++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Configuration;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Settings
+{
+ [TestFixture]
+ public class TestSceneSettingsSource : OsuTestScene
+ {
+ public TestSceneSettingsSource()
+ {
+ Children = new Drawable[]
+ {
+ new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(20),
+ Width = 0.5f,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Padding = new MarginPadding(50),
+ ChildrenEnumerable = new TestTargetClass().CreateSettingsControls()
+ },
+ };
+ }
+
+ private class TestTargetClass
+ {
+ [SettingSource("Sample bool", "Clicking this changes a setting")]
+ public BindableBool TickBindable { get; } = new BindableBool();
+
+ [SettingSource("Sample float", "Change something for a mod")]
+ public BindableFloat SliderBindable { get; } = new BindableFloat
+ {
+ MinValue = 0,
+ MaxValue = 10,
+ Default = 5,
+ Value = 7
+ };
+
+ [SettingSource("Sample enum", "Change something for a mod")]
+ public Bindable EnumBindable { get; } = new Bindable
+ {
+ Default = TestEnum.Value1,
+ Value = TestEnum.Value2
+ };
+
+ [SettingSource("Sample string", "Change something for a mod")]
+ public Bindable StringBindable { get; } = new Bindable();
+ }
+
+ private enum TestEnum
+ {
+ Value1,
+ Value2
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
deleted file mode 100644
index ed9e01a67e..0000000000
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Beatmaps;
-using osuTK;
-
-namespace osu.Game.Tests.Visual.SongSelect
-{
- [TestFixture]
- [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
- public class TestSceneBeatmapDetailArea : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) };
-
- [BackgroundDependencyLoader]
- private void load(OsuGameBase game)
- {
- BeatmapDetailArea detailsArea;
- Add(detailsArea = new BeatmapDetailArea
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- });
-
- AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "All Metrics",
- Metadata = new BeatmapMetadata
- {
- Tags = "this beatmap has all the metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 7,
- DrainRate = 1,
- OverallDifficulty = 5.7f,
- ApproachRate = 3.5f,
- },
- StarDifficulty = 5.3f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- BeatmapSet = new BeatmapSetInfo
- {
- Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
- },
- Version = "Only Ratings",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has ratings metrics but not retries or fails",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 6,
- DrainRate = 9,
- OverallDifficulty = 6,
- ApproachRate = 6,
- },
- StarDifficulty = 4.8f
- }
- }));
-
- AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "Only Retries and Fails",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has retries and fails but no ratings",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 3.7f,
- DrainRate = 6,
- OverallDifficulty = 6,
- ApproachRate = 7,
- },
- StarDifficulty = 2.91f,
- Metrics = new BeatmapMetrics
- {
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
- },
- }
- }));
-
- AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
- {
- BeatmapInfo =
- {
- Version = "No Metrics",
- Metadata = new BeatmapMetadata
- {
- Source = "osu!lazer",
- Tags = "this beatmap has no metrics",
- },
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 5,
- DrainRate = 5,
- OverallDifficulty = 5.5f,
- ApproachRate = 6.5f,
- },
- StarDifficulty = 1.97f,
- }
- }));
-
- AddStep("null beatmap", () => detailsArea.Beatmap = null);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
index acf037198f..6aa5a76490 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
@@ -3,8 +3,14 @@
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect
@@ -174,5 +180,27 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = 162,
});
}
+
+ [Resolved]
+ private RulesetStore rulesets { get; set; }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [Test]
+ public void TestModAdjustments()
+ {
+ TestAllMetrics();
+
+ Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
+
+ AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) });
+
+ AddAssert("first bar coloured blue", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.BlueDark);
+
+ AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) });
+
+ AddAssert("first bar coloured red", () => details.ChildrenOfType().Skip(1).First().AccentColour == colours.Red);
+ }
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index fb27ec7654..3eff75b020 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -3,11 +3,13 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
-using osu.Game.Rulesets.Mods;
+using osu.Game.Overlays;
+using osu.Game.Online.Placeholders;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
@@ -29,8 +31,16 @@ namespace osu.Game.Tests.Visual.SongSelect
private readonly FailableLeaderboard leaderboard;
+ [Cached]
+ private readonly DialogOverlay dialogOverlay;
+
public TestSceneBeatmapLeaderboard()
{
+ Add(dialogOverlay = new DialogOverlay
+ {
+ Depth = -1
+ });
+
Add(leaderboard = new FailableLeaderboard
{
Origin = Anchor.Centre,
@@ -62,7 +72,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
- Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
User = new User
{
Id = 6602580,
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index a4b8d1a24a..eb812f5d5a 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -11,7 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
@@ -95,6 +95,42 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
}
+ [Test]
+ public void TestNoFilterOnSimpleResume()
+ {
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+
+ createSongSelect();
+
+ AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
+ AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
+
+ AddStep("return", () => songSelect.MakeCurrent());
+ AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
+ AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
+ }
+
+ [Test]
+ public void TestFilterOnResumeAfterChange()
+ {
+ addRulesetImportStep(0);
+ addRulesetImportStep(0);
+
+ AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
+
+ createSongSelect();
+
+ AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
+ AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
+
+ AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
+
+ AddStep("return", () => songSelect.MakeCurrent());
+ AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
+ AddAssert("filter count is 2", () => songSelect.FilterCount == 2);
+ }
+
[Test]
public void TestAudioResuming()
{
@@ -220,17 +256,17 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("change ruleset", () =>
{
- Mods.ValueChanged += onModChange;
+ SelectedMods.ValueChanged += onModChange;
songSelect.Ruleset.ValueChanged += onRulesetChange;
Ruleset.Value = new TaikoRuleset().RulesetInfo;
- Mods.ValueChanged -= onModChange;
+ SelectedMods.ValueChanged -= onModChange;
songSelect.Ruleset.ValueChanged -= onRulesetChange;
});
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
- AddAssert("empty mods", () => !Mods.Value.Any());
+ AddAssert("empty mods", () => !SelectedMods.Value.Any());
void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++;
void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++;
@@ -239,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestModsRetainedBetweenSongSelect()
{
- AddAssert("empty mods", () => !Mods.Value.Any());
+ AddAssert("empty mods", () => !SelectedMods.Value.Any());
createSongSelect();
@@ -249,7 +285,7 @@ namespace osu.Game.Tests.Visual.SongSelect
createSongSelect();
- AddAssert("mods retained", () => Mods.Value.Any());
+ AddAssert("mods retained", () => SelectedMods.Value.Any());
}
[Test]
@@ -296,7 +332,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
- private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods);
+ private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods);
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
index 7fac45e0f1..0598324110 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
@@ -1,13 +1,14 @@
// 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.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays;
using osu.Game.Scoring;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
@@ -16,10 +17,18 @@ namespace osu.Game.Tests.Visual.SongSelect
{
public class TestSceneUserTopScoreContainer : OsuTestScene
{
+ [Cached]
+ private readonly DialogOverlay dialogOverlay;
+
public TestSceneUserTopScoreContainer()
{
UserTopScoreContainer topScoreContainer;
+ Add(dialogOverlay = new DialogOverlay
+ {
+ Depth = -1
+ });
+
Add(new Container
{
Origin = Anchor.BottomCentre,
@@ -52,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
- Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
User = new User
{
Id = 6602580,
diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
index e495b2a95a..492494ada3 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameDependencies)
{
if (game.Dependencies.Get(type) == null)
- throw new Exception($"{type} has not been cached");
+ throw new InvalidOperationException($"{type} has not been cached");
}
return true;
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual
foreach (var type in requiredGameBaseDependencies)
{
if (gameBase.Dependencies.Get(type) == null)
- throw new Exception($"{type} has not been cached");
+ throw new InvalidOperationException($"{type} has not been cached");
}
return true;
diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
index a68fd0ef40..c55988d1bb 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
+++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Parallax is off", () => stack.ParallaxAmount == 0);
}
- private class TestScreen : ScreenWithBeatmapBackground
+ public class TestScreen : ScreenWithBeatmapBackground
{
private readonly string screenText;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index ed44d82bce..b0b673d6a4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.UserInterface
private TimingControlPoint getNextTimingPoint(TimingControlPoint current)
{
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return current;
int index = timingPoints.IndexOf(current); // -1 means that this is a "default beat"
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
if (timingPoints.Count == 0) return 0;
- if (timingPoints[timingPoints.Count - 1] == current)
+ if (timingPoints[^1] == current)
return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
similarity index 92%
rename from osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs
rename to osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
index 554696765e..19eebc89b6 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbControl.cs
@@ -10,11 +10,11 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
- public class TestSceneBreadcrumbs : OsuTestScene
+ public class TestSceneBreadcrumbControl : OsuTestScene
{
private readonly BreadcrumbControl breadcrumbs;
- public TestSceneBreadcrumbs()
+ public TestSceneBreadcrumbControl()
{
Add(breadcrumbs = new BreadcrumbControl
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
index e95f4c09c6..d1dde4664a 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
using osuTK;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
new file mode 100644
index 0000000000..1e5e26e4c5
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -0,0 +1,181 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Cursor;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Leaderboards;
+using osu.Game.Online.Placeholders;
+using osu.Game.Overlays;
+using osu.Game.Rulesets;
+using osu.Game.Scoring;
+using osu.Game.Screens.Select.Leaderboards;
+using osu.Game.Tests.Resources;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneDeleteLocalScore : ManualInputManagerTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Placeholder),
+ typeof(MessagePlaceholder),
+ typeof(RetrievalFailurePlaceholder),
+ typeof(UserTopScoreContainer),
+ typeof(Leaderboard),
+ typeof(LeaderboardScore),
+ };
+
+ private readonly ContextMenuContainer contextMenuContainer;
+ private readonly BeatmapLeaderboard leaderboard;
+
+ private RulesetStore rulesetStore;
+ private BeatmapManager beatmapManager;
+ private ScoreManager scoreManager;
+
+ private readonly List scores = new List();
+ private BeatmapInfo beatmap;
+
+ [Cached]
+ private readonly DialogOverlay dialogOverlay;
+
+ public TestSceneDeleteLocalScore()
+ {
+ Children = new Drawable[]
+ {
+ contextMenuContainer = new OsuContextMenuContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = leaderboard = new BeatmapLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = BeatmapLeaderboardScope.Local,
+ Beatmap = new BeatmapInfo
+ {
+ ID = 1,
+ Metadata = new BeatmapMetadata
+ {
+ ID = 1,
+ Title = "TestSong",
+ Artist = "TestArtist",
+ Author = new User
+ {
+ Username = "TestAuthor"
+ },
+ },
+ Version = "Insane"
+ },
+ }
+ },
+ dialogOverlay = new DialogOverlay()
+ };
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
+ dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get(), Beatmap.Default));
+ dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
+
+ beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0];
+
+ for (int i = 0; i < 50; i++)
+ {
+ var score = new ScoreInfo
+ {
+ OnlineScoreID = i,
+ Beatmap = beatmap,
+ BeatmapInfoID = beatmap.ID,
+ Accuracy = RNG.NextDouble(),
+ TotalScore = RNG.Next(1, 1000000),
+ MaxCombo = RNG.Next(1, 1000),
+ Rank = ScoreRank.XH,
+ User = new User { Username = "TestUser" },
+ };
+
+ scores.Add(scoreManager.Import(score).Result);
+ }
+
+ scores.Sort(Comparer.Create((s1, s2) => s2.TotalScore.CompareTo(s1.TotalScore)));
+
+ return dependencies;
+ }
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ // Due to soft deletions, we can re-use deleted scores between test runs
+ scoreManager.Undelete(scoreManager.QueryScores(s => s.DeletePending).ToList());
+
+ leaderboard.Scores = null;
+ leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables
+
+ leaderboard.Beatmap = beatmap;
+ leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed
+ });
+
+ [SetUpSteps]
+ public void SetupSteps()
+ {
+ // Ensure the leaderboard has finished async-loading drawables
+ AddUntilStep("wait for drawables", () => leaderboard.ChildrenOfType().Any());
+
+ // Ensure the leaderboard items have finished showing up
+ AddStep("finish transforms", () => leaderboard.FinishTransforms(true));
+ }
+
+ [Test]
+ public void TestDeleteViaRightClick()
+ {
+ AddStep("open menu for top score", () =>
+ {
+ InputManager.MoveMouseTo(leaderboard.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Right);
+ });
+
+ // Ensure the context menu has finished showing
+ AddStep("finish transforms", () => contextMenuContainer.FinishTransforms(true));
+
+ AddStep("click delete option", () =>
+ {
+ InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToLowerInvariant() == "delete"));
+ InputManager.Click(MouseButton.Left);
+ });
+
+ // Ensure the dialog has finished showing
+ AddStep("finish transforms", () => dialogOverlay.FinishTransforms(true));
+
+ AddStep("click delete button", () =>
+ {
+ InputManager.MoveMouseTo(dialogOverlay.ChildrenOfType().First());
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
+ }
+
+ [Test]
+ public void TestDeleteViaDatabase()
+ {
+ AddStep("delete top score", () => scoreManager.Delete(scores[0]));
+ AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scores[0].OnlineScoreID));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs
new file mode 100644
index 0000000000..6eb621ca3b
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFooterButtonMods.cs
@@ -0,0 +1,92 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Select;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneFooterButtonMods : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(FooterButtonMods)
+ };
+
+ private readonly TestFooterButtonMods footerButtonMods;
+
+ public TestSceneFooterButtonMods()
+ {
+ Add(footerButtonMods = new TestFooterButtonMods());
+ }
+
+ [Test]
+ public void TestIncrementMultiplier()
+ {
+ var hiddenMod = new Mod[] { new OsuModHidden() };
+ AddStep(@"Add Hidden", () => changeMods(hiddenMod));
+ AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod));
+
+ var hardRockMod = new Mod[] { new OsuModHardRock() };
+ AddStep(@"Add HardRock", () => changeMods(hardRockMod));
+ AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod));
+
+ var doubleTimeMod = new Mod[] { new OsuModDoubleTime() };
+ AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
+ AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod));
+
+ var mutlipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
+ AddStep(@"Add multiple Mods", () => changeMods(mutlipleIncrementMods));
+ AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(mutlipleIncrementMods));
+ }
+
+ [Test]
+ public void TestDecrementMultiplier()
+ {
+ var easyMod = new Mod[] { new OsuModEasy() };
+ AddStep(@"Add Easy", () => changeMods(easyMod));
+ AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod));
+
+ var noFailMod = new Mod[] { new OsuModNoFail() };
+ AddStep(@"Add NoFail", () => changeMods(noFailMod));
+ AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod));
+
+ var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() };
+ AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods));
+ AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods));
+ }
+
+ [Test]
+ public void TestClearMultiplier()
+ {
+ var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() };
+ AddStep(@"Add mods", () => changeMods(multipleMods));
+ AddStep(@"Clear selected mod", () => changeMods(Array.Empty()));
+ AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty()));
+ }
+
+ private void changeMods(IReadOnlyList mods)
+ {
+ footerButtonMods.Current.Value = mods;
+ }
+
+ private bool assertModsMultiplier(IEnumerable mods)
+ {
+ var multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
+ var expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
+
+ return expectedValue == footerButtonMods.MultiplierText.Text;
+ }
+
+ private class TestFooterButtonMods : FooterButtonMods
+ {
+ public new OsuSpriteText MultiplierText => base.MultiplierText;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs
index 54876dbbda..4e394b5ed8 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs
@@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Menu;
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
index be50200e1c..12ee4ceb2e 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs
@@ -8,13 +8,16 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Mods.Sections;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play.HUD;
@@ -48,42 +51,48 @@ namespace osu.Game.Tests.Visual.UserInterface
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
+ }
- Add(modSelect = new TestModSelectOverlay
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.X,
- Origin = Anchor.BottomCentre,
- Anchor = Anchor.BottomCentre,
- });
+ modSelect = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ },
- Add(modDisplay = new ModDisplay
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- AutoSizeAxes = Axes.Both,
- Position = new Vector2(0, 25),
- });
+ modDisplay = new ModDisplay
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Position = new Vector2(0, 25),
+ Current = { BindTarget = modSelect.SelectedMods }
+ }
+ };
+ });
- modDisplay.Current.UnbindBindings();
- modDisplay.Current.BindTo(modSelect.SelectedMods);
-
- AddStep("Show", modSelect.Show);
- AddStep("Toggle", modSelect.ToggleVisibility);
- AddStep("Toggle", modSelect.ToggleVisibility);
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("show", () => modSelect.Show());
}
[Test]
public void TestOsuMods()
{
- var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0);
- changeRuleset(ruleset);
+ changeRuleset(0);
- var instance = ruleset.CreateInstance();
+ var osu = new OsuRuleset();
- var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
- var harderMods = instance.GetModsFor(ModType.DifficultyIncrease);
+ var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
+ var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
- var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
+ var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
@@ -97,8 +106,8 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiMod(doubleTimeMod);
testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
- testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
- testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
+ testMultiplierTextColour(noFailMod, () => modSelect.LowMultiplierColour);
+ testMultiplierTextColour(hiddenMod, () => modSelect.HighMultiplierColour);
testUnimplementedMod(spunOutMod);
}
@@ -106,37 +115,31 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestManiaMods()
{
- var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3);
- changeRuleset(ruleset);
+ changeRuleset(3);
- testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom));
+ testRankedText(new ManiaRuleset().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom));
}
[Test]
public void TestRulesetChanges()
{
- var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0);
- var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3);
+ changeRuleset(0);
- changeRuleset(null);
+ var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
- var instance = rulesetOsu.CreateInstance();
- var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
- var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
+ AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; });
- AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; });
-
- changeRuleset(rulesetOsu);
+ changeRuleset(0);
AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null);
- changeRuleset(rulesetMania);
+ changeRuleset(3);
- AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail));
+ AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
- changeRuleset(rulesetOsu);
+ changeRuleset(0);
- AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any());
+ AddAssert("ensure mods not selected", () => modDisplay.Current.Value.Count == 0);
}
private void testSingleMod(Mod mod)
@@ -198,19 +201,19 @@ namespace osu.Game.Tests.Visual.UserInterface
selectNext(mod);
AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any());
- AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke);
+ AddStep("deselect all", () => modSelect.DeselectAllButton.Action.Invoke());
AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any());
}
- private void testMultiplierTextColour(Mod mod, Color4 colour)
+ private void testMultiplierTextColour(Mod mod, Func getCorrectColour)
{
- checkLabelColor(Color4.White);
+ checkLabelColor(() => Color4.White);
selectNext(mod);
AddWaitStep("wait for changing colour", 1);
- checkLabelColor(colour);
+ checkLabelColor(getCorrectColour);
selectPrevious(mod);
AddWaitStep("wait for changing colour", 1);
- checkLabelColor(Color4.White);
+ checkLabelColor(() => Color4.White);
}
private void testRankedText(Mod mod)
@@ -235,9 +238,9 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
- private void changeRuleset(RulesetInfo ruleset)
+ private void changeRuleset(int? id)
{
- AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; });
+ AddStep($"change ruleset to {(id?.ToString() ?? "none")}", () => { Ruleset.Value = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == id); });
waitForLoad();
}
@@ -253,7 +256,7 @@ namespace osu.Game.Tests.Visual.UserInterface
});
}
- private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color);
+ private void checkLabelColor(Func getColour) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == getColour());
private class TestModSelectOverlay : ModSelectOverlay
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
new file mode 100644
index 0000000000..8dcb7dcbf8
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSettings.cs
@@ -0,0 +1,152 @@
+// 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.Linq;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays.Mods;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneModSettings : OsuTestScene
+ {
+ private TestModSelectOverlay modSelect;
+
+ private readonly Mod testCustomisableMod = new TestModCustomisable1();
+
+ [Test]
+ public void TestButtonShowsOnCustomisableMod()
+ {
+ createModSelect();
+
+ AddStep("open", () => modSelect.Show());
+ AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value);
+ AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
+ AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod));
+ AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
+ AddStep("open Customisation", () => modSelect.CustomiseButton.Click());
+ AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod));
+ AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0);
+ }
+
+ [Test]
+ public void TestButtonShowsOnModAlreadyAdded()
+ {
+ AddStep("set active mods", () => SelectedMods.Value = new List { testCustomisableMod });
+
+ createModSelect();
+
+ AddAssert("mods still active", () => SelectedMods.Value.Count == 1);
+
+ AddStep("open", () => modSelect.Show());
+ AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
+ }
+
+ private void createModSelect()
+ {
+ AddStep("create mod select", () =>
+ {
+ Ruleset.Value = new TestRulesetInfo();
+
+ Child = modSelect = new TestModSelectOverlay
+ {
+ RelativeSizeAxes = Axes.X,
+ Origin = Anchor.BottomCentre,
+ Anchor = Anchor.BottomCentre,
+ };
+ });
+ }
+
+ private class TestModSelectOverlay : ModSelectOverlay
+ {
+ public new Container ModSettingsContainer => base.ModSettingsContainer;
+ public new TriangleButton CustomiseButton => base.CustomiseButton;
+
+ public bool ButtonsLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
+
+ public void SelectMod(Mod mod) =>
+ ModSectionsContainer.Children.Single(s => s.ModType == mod.Type)
+ .ButtonsContainer.OfType().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1);
+ }
+
+ public class TestRulesetInfo : RulesetInfo
+ {
+ public override Ruleset CreateInstance() => new TestCustomisableModRuleset();
+
+ public TestRulesetInfo()
+ {
+ Available = true;
+ }
+
+ public class TestCustomisableModRuleset : Ruleset
+ {
+ public override IEnumerable GetModsFor(ModType type)
+ {
+ if (type == ModType.Conversion)
+ {
+ return new Mod[]
+ {
+ new TestModCustomisable1(),
+ new TestModCustomisable2()
+ };
+ }
+
+ return Array.Empty();
+ }
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
+
+ public override string Description { get; } = "test";
+ public override string ShortName { get; } = "tst";
+ }
+ }
+
+ private class TestModCustomisable1 : TestModCustomisable
+ {
+ public override string Name => "Customisable Mod 1";
+
+ public override string Acronym => "CM1";
+ }
+
+ private class TestModCustomisable2 : TestModCustomisable
+ {
+ public override string Name => "Customisable Mod 2";
+
+ public override string Acronym => "CM2";
+ }
+
+ private abstract class TestModCustomisable : Mod, IApplicableMod
+ {
+ public override double ScoreMultiplier => 1.0;
+
+ public override ModType Type => ModType.Conversion;
+
+ [SettingSource("Sample float", "Change something for a mod")]
+ public BindableFloat SliderBindable { get; } = new BindableFloat
+ {
+ MinValue = 0,
+ MaxValue = 10,
+ Default = 5,
+ Value = 7
+ };
+
+ [SettingSource("Sample bool", "Clicking this changes a setting")]
+ public BindableBool TickBindable { get; } = new BindableBool();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
index d8a4514df1..f8ace73168 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs
@@ -8,7 +8,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
@@ -67,9 +67,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRepeatStep(@"add many simple", sendManyNotifications, 3);
- AddWaitStep("wait some", 5);
-
- checkProgressingCount(0);
+ waitForCompletion();
AddStep(@"progress #3", sendUploadProgress);
@@ -77,9 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
checkDisplayedCount(33);
- AddWaitStep("wait some", 10);
-
- checkProgressingCount(0);
+ waitForCompletion();
}
[Test]
@@ -109,9 +105,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep(@"background progress #1", sendBackgroundUploadProgress);
- AddWaitStep("wait some", 5);
+ checkProgressingCount(1);
- checkProgressingCount(0);
+ waitForCompletion();
checkDisplayedCount(2);
@@ -190,6 +186,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
+ private void waitForCompletion() => AddUntilStep("wait for notification progress completion", () => progressingNotifications.Count == 0);
+
private void sendBarrage()
{
switch (RNG.Next(0, 4))
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
index e3daa9c279..2ea9aec50a 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
@@ -4,8 +4,10 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Timing;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -15,22 +17,48 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private MusicController musicController = new MusicController();
- public TestSceneNowPlayingOverlay()
- {
- Clock = new FramedClock();
+ private WorkingBeatmap currentBeatmap;
- var np = new NowPlayingOverlay
+ private NowPlayingOverlay nowPlayingOverlay;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+
+ nowPlayingOverlay = new NowPlayingOverlay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre
};
Add(musicController);
- Add(np);
+ Add(nowPlayingOverlay);
+ }
- AddStep(@"show", () => np.Show());
+ [Test]
+ public void TestShowHideDisable()
+ {
+ AddStep(@"show", () => nowPlayingOverlay.Show());
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
- AddStep(@"show", () => np.Hide());
+ AddStep(@"hide", () => nowPlayingOverlay.Hide());
+ }
+
+ [Test]
+ public void TestPrevTrackBehavior()
+ {
+ AddStep(@"Play track", () =>
+ {
+ musicController.NextTrack();
+ currentBeatmap = Beatmap.Value;
+ });
+
+ AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000));
+ AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000);
+ AddAssert(@"Check action is restart track", () => musicController.PreviousTrack() == PreviousTrackResult.Restart);
+ AddUntilStep("Wait for current time to update", () => Precision.AlmostEquals(currentBeatmap.Track.CurrentTime, 0));
+ AddAssert(@"Check track didn't change", () => currentBeatmap == Beatmap.Value);
+ AddAssert(@"Check action is not restart", () => musicController.PreviousTrack() != PreviousTrackResult.Restart);
}
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
index 3d39bb7003..7207506ccd 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs
@@ -1,9 +1,12 @@
// Copyright (c) ppy Pty Ltd