diff --git a/.gitignore b/.gitignore
index 0e2850a01c..e60058ab35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -198,6 +198,7 @@ ClientBin/
*.publishsettings
node_modules/
orleans.codegen.cs
+Resource.designer.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 2eff16cc91..6463dd6ea5 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index cae9754560..0b63b2d966 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
rename to .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 49ec93e1b3..750ece648b 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
similarity index 73%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index d0964c6f68..7b359a1ca0 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,18 +1,21 @@
-
+
+
-
+
-
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml
new file mode 100644
index 0000000000..3722f3dc04
--- /dev/null
+++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml
new file mode 100644
index 0000000000..e2628a1bb4
--- /dev/null
+++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
index 2735f4ceb3..7ac6bb745d 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
@@ -1,5 +1,5 @@
-
+
diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
similarity index 71%
rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
rename to .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
index 95cb4c0e62..7fcb7c15ea 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
@@ -1,17 +1,20 @@
-
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index ce353d9b27..0000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-language: csharp
-solution: osu.sln
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index c3306c2db7..57ff3e6b43 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,41 +1,6 @@
{
"version": "0.2.0",
- "configurations": [
- {
- "name": "VisualTests (Debug)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Debug)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
- {
- "name": "VisualTests (Release)",
- "type": "coreclr",
- "request": "launch",
- "program": "dotnet",
- "args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
- ],
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build tests (Release)",
- "linux": {
- "env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
- }
- },
- "console": "internalConsole"
- },
- {
+ "configurations": [{
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
@@ -69,6 +34,111 @@
},
"console": "internalConsole"
},
+ {
+ "name": "osu! (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ }, {
+ "name": "osu! (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/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/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/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/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
{
"name": "Cake: Debug Script",
"type": "coreclr",
@@ -84,4 +154,4 @@
"externalConsole": false
}
]
-}
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index de799a7c03..aba590f466 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,8 +2,7 @@
// 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",
@@ -65,6 +64,36 @@
"group": "build",
"problemMatcher": "$msCompile"
},
+ {
+ "label": "Build tournament tests (Debug)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ }, {
+ "label": "Build tournament tests (Release)",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Tournament.Tests",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
{
"label": "Restore (netcoreapp2.2)",
"type": "shell",
diff --git a/README.md b/README.md
index abddb1faa1..c4d676f4be 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# osu!
[](https://ci.appveyor.com/project/peppy/osu) [](https://www.codefactor.io/repository/github/ppy/osu) [](https://discord.gg/ppy)
@@ -10,6 +14,8 @@ This project is still heavily under development, but is in a state where users a
We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below.
+Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog).
+
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
@@ -20,17 +26,22 @@ We are accepting bug reports (please report with as much detail as possible). Fe
### Releases
-If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled.
+If you are not interested in developing the game, you can still consume our [binary releases](https://github.com/ppy/osu/releases).
-- Windows (x64) users should download and run `install.exe`.
-- macOS users (10.12 "Sierra" and higher) should download and run `osu.app.zip`.
-- iOS users can join the [TestFlight beta program](https://t.co/xQJmHkfC18).
+**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) |
+| ------------- | ------------- |
+
+- **Linux** users are recommended to self-compile until we have official deployment in place.
+- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
+- **Android** users can self-compile, and expect a public beta soon.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
### Downloading the source code
-Clone the repository **including submodules**:
+Clone the repository:
```shell
git clone https://github.com/ppy/osu
@@ -45,7 +56,7 @@ git pull
### Building
-Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided below.
+Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing).
> Visual Studio Code users must run the `Restore` task before any build attempt.
@@ -63,7 +74,7 @@ If the build fails, try to restore nuget packages with `dotnet restore`.
On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`.
-`$NETCORE_VERSION` is the version of .NET Core SDK. You can have it with `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`.
+`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`.
For example, you can run osu! with the following command:
@@ -83,11 +94,13 @@ Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is cu
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted.
-Please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
+If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) label).
-Contributions can be made via pull requests to this repository. We hope to credit and reward larger contributions via a [bounty system](https://www.bountysource.com/teams/ppy). If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues).
+Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
-Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible.
+Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as pain-free as possible.
+
+For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via paypal or osu! supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
## Licence
diff --git a/assets/lazer.png b/assets/lazer.png
new file mode 100644
index 0000000000..1e40e844cc
Binary files /dev/null and b/assets/lazer.png differ
diff --git a/build/cakebuild.csproj b/build/cakebuild.csproj
index 8ccce35e26..d5a6d44740 100644
--- a/build/cakebuild.csproj
+++ b/build/cakebuild.csproj
@@ -5,7 +5,7 @@
netcoreapp2.0
-
-
+
+
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
new file mode 100644
index 0000000000..3b2e6574ac
--- /dev/null
+++ b/osu.Android.props
@@ -0,0 +1,68 @@
+
+
+ bin\$(Configuration)
+ 4
+ 2.0
+ false
+ false
+ default
+ Library
+ 512
+ Off
+ True
+ Xamarin.Android.Net.AndroidClientHandler
+ v9.0
+ false
+
+
+ True
+ portable
+ False
+ DEBUG;TRACE
+ prompt
+ false
+ false
+ SdkOnly
+ true
+ false
+ cjk,mideast,other,rare,west
+ true
+ armeabi-v7a;x86;arm64-v8a
+ true
+
+
+ false
+ None
+ True
+ prompt
+ true
+ false
+ SdkOnly
+ False
+ true
+ cjk,mideast,other,rare,west
+ true
+ armeabi-v7a;x86;arm64-v8a
+ true
+
+
+
+ osu.licenseheader
+
+
+ Always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Android.sln b/osu.Android.sln
new file mode 100644
index 0000000000..ebf2c55cb4
--- /dev/null
+++ b/osu.Android.sln
@@ -0,0 +1,126 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28516.95
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Android", "osu.Android\osu.Android.csproj", "{D1D5F9A8-B40B-40E6-B02F-482D03346D3D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.Android", "osu.Game.Rulesets.Catch.Tests.Android\osu.Game.Rulesets.Catch.Tests.Android.csproj", "{C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.Android", "osu.Game.Rulesets.Mania.Tests.Android\osu.Game.Rulesets.Mania.Tests.Android.csproj", "{531F1092-DB27-445D-AA33-2A77C7187C99}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.Android", "osu.Game.Rulesets.Osu.Tests.Android\osu.Game.Rulesets.Osu.Tests.Android.csproj", "{90CAB706-39CB-4B93-9629-3218A6FF8E9B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.Android", "osu.Game.Rulesets.Taiko.Tests.Android\osu.Game.Rulesets.Taiko.Tests.Android.csproj", "{3701A0A1-8476-42C6-B5C4-D24129B4A484}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.Android", "osu.Game.Tests.Android\osu.Game.Tests.Android.csproj", "{5CC222DC-5716-4499-B897-DCBDDA4A5CF9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU
+ {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Build.0 = Release|Any CPU
+ {531F1092-DB27-445D-AA33-2A77C7187C99}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}.Release|Any CPU.Deploy.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+EndGlobal
diff --git a/osu.Android.sln.DotSettings b/osu.Android.sln.DotSettings
new file mode 100644
index 0000000000..3f5bd9d34d
--- /dev/null
+++ b/osu.Android.sln.DotSettings
@@ -0,0 +1,815 @@
+
+ True
+ True
+ True
+ True
+ ExplicitlyExcluded
+ ExplicitlyExcluded
+ SOLUTION
+ HINT
+ WARNING
+
+ True
+ WARNING
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ SUGGESTION
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ HINT
+ HINT
+ HINT
+ ERROR
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+
+ WARNING
+ WARNING
+ WARNING
+ ERROR
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile>
+ Code Cleanup (peppy)
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ NEXT_LINE
+ True
+ NEVER
+ NEVER
+ False
+ NEVER
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ CHOP_IF_LONG
+ True
+ 200
+ CHOP_IF_LONG
+ False
+ False
+ AABB
+ API
+ BPM
+ GC
+ GL
+ GLSL
+ HID
+ HUD
+ ID
+ IP
+ IPC
+ LTRB
+ MD5
+ NS
+ OS
+ RGB
+ RNG
+ SHA
+ SRGB
+ TK
+ SS
+ PP
+ GMT
+ QAT
+ BNG
+ UI
+ HINT
+ <?xml version="1.0" encoding="utf-16"?>
+<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns">
+ <TypePattern DisplayName="COM interfaces or structs">
+ <TypePattern.Match>
+ <Or>
+ <And>
+ <Kind Is="Interface" />
+ <Or>
+ <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" />
+ <HasAttribute Name="System.Runtime.InteropServices.ComImport" />
+ </Or>
+ </And>
+ <Kind Is="Struct" />
+ </Or>
+ </TypePattern.Match>
+ </TypePattern>
+ <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All">
+ <TypePattern.Match>
+ <And>
+ <Kind Is="Class" />
+ <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" />
+ </And>
+ </TypePattern.Match>
+ <Entry DisplayName="Setup/Teardown Methods">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <Or>
+ <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" />
+ <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" />
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="All other members" />
+ <Entry Priority="100" DisplayName="Test Methods">
+ <Entry.Match>
+ <And>
+ <Kind Is="Method" />
+ <HasAttribute Name="NUnit.Framework.TestAttribute" />
+ </And>
+ </Entry.Match>
+ <Entry.SortBy>
+ <Name />
+ </Entry.SortBy>
+ </Entry>
+ </TypePattern>
+ <TypePattern DisplayName="Default Pattern">
+ <Group DisplayName="Fields/Properties">
+ <Group DisplayName="Public Fields">
+ <Entry DisplayName="Constant Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Or>
+ <Kind Is="Constant" />
+ <Readonly />
+ <And>
+ <Static />
+ <Readonly />
+ </And>
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Static Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Static />
+ <Not>
+ <Readonly />
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Normal Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Not>
+ <Or>
+ <Static />
+ <Readonly />
+ </Or>
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Entry DisplayName="Public Properties">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Kind Is="Property" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Group DisplayName="Internal Fields">
+ <Entry DisplayName="Constant Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Or>
+ <Kind Is="Constant" />
+ <Readonly />
+ <And>
+ <Static />
+ <Readonly />
+ </And>
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Static Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Static />
+ <Not>
+ <Readonly />
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Normal Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Not>
+ <Or>
+ <Static />
+ <Readonly />
+ </Or>
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Entry DisplayName="Internal Properties">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Kind Is="Property" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Group DisplayName="Protected Fields">
+ <Entry DisplayName="Constant Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Or>
+ <Kind Is="Constant" />
+ <Readonly />
+ <And>
+ <Static />
+ <Readonly />
+ </And>
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Static Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Static />
+ <Not>
+ <Readonly />
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Normal Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Not>
+ <Or>
+ <Static />
+ <Readonly />
+ </Or>
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Entry DisplayName="Protected Properties">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Kind Is="Property" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Group DisplayName="Private Fields">
+ <Entry DisplayName="Constant Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Or>
+ <Kind Is="Constant" />
+ <Readonly />
+ <And>
+ <Static />
+ <Readonly />
+ </And>
+ </Or>
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Static Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Static />
+ <Not>
+ <Readonly />
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Normal Fields">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Not>
+ <Or>
+ <Static />
+ <Readonly />
+ </Or>
+ </Not>
+ <Kind Is="Field" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Entry DisplayName="Private Properties">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Kind Is="Property" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Group DisplayName="Constructor/Destructor">
+ <Entry DisplayName="Ctor">
+ <Entry.Match>
+ <Kind Is="Constructor" />
+ </Entry.Match>
+ </Entry>
+ <Region Name="Disposal">
+ <Entry DisplayName="Dtor">
+ <Entry.Match>
+ <Kind Is="Destructor" />
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Dispose()">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Kind Is="Method" />
+ <Name Is="Dispose" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Dispose(true)">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Or>
+ <Virtual />
+ <Override />
+ </Or>
+ <Kind Is="Method" />
+ <Name Is="Dispose" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Region>
+ </Group>
+ <Group DisplayName="Methods">
+ <Group DisplayName="Public">
+ <Entry DisplayName="Static Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Static />
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Public" />
+ <Not>
+ <Static />
+ </Not>
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Group DisplayName="Internal">
+ <Entry DisplayName="Static Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Static />
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Internal" />
+ <Not>
+ <Static />
+ </Not>
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Group DisplayName="Protected">
+ <Entry DisplayName="Static Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Static />
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Protected" />
+ <Not>
+ <Static />
+ </Not>
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ <Group DisplayName="Private">
+ <Entry DisplayName="Static Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Static />
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ <Entry DisplayName="Methods">
+ <Entry.Match>
+ <And>
+ <Access Is="Private" />
+ <Not>
+ <Static />
+ </Not>
+ <Kind Is="Method" />
+ </And>
+ </Entry.Match>
+ </Entry>
+ </Group>
+ </Group>
+ </TypePattern>
+</Patterns>
+ Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+See the LICENCE file in the repository root for full licence text.
+
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
+ <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy>
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy>
+ <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy>
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ o!f – Object Initializer: Anchor&Origin
+ True
+ constant("Centre")
+ 0
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofao
+ True
+ Anchor = Anchor.$anchor$,
+Origin = Anchor.$anchor$,
+ True
+ True
+ o!f – InternalChildren = []
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofic
+ True
+ InternalChildren = new Drawable[]
+{
+ $END$
+};
+ True
+ True
+ o!f – new GridContainer { .. }
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofgc
+ True
+ new GridContainer
+{
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[] { $END$ },
+ new Drawable[] { }
+ }
+};
+ True
+ True
+ o!f – new FillFlowContainer { .. }
+ True
+ True
+ 2.0
+ InCSharpFile
+ offf
+ True
+ new FillFlowContainer
+{
+ RelativeSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ $END$
+ }
+},
+ True
+ True
+ o!f – new Container { .. }
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofcont
+ True
+ new Container
+{
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ $END$
+ }
+},
+ True
+ True
+ o!f – BackgroundDependencyLoader load()
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofbdl
+ True
+ [BackgroundDependencyLoader]
+private void load()
+{
+ $END$
+}
+ True
+ True
+ o!f – new Box { .. }
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofbox
+ True
+ new Box
+{
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+},
+ True
+ True
+ o!f – Children = []
+ True
+ True
+ 2.0
+ InCSharpFile
+ ofc
+ True
+ Children = new Drawable[]
+{
+ $END$
+};
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
new file mode 100644
index 0000000000..762a9c418d
--- /dev/null
+++ b/osu.Android/OsuGameActivity.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+using osu.Framework.Android;
+
+namespace osu.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
+ public class OsuGameActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuGameAndroid();
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ Window.AddFlags(WindowManagerFlags.Fullscreen);
+ Window.AddFlags(WindowManagerFlags.KeepScreenOn);
+ }
+ }
+}
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
new file mode 100644
index 0000000000..d9bdd9c0c2
--- /dev/null
+++ b/osu.Android/OsuGameAndroid.cs
@@ -0,0 +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 System;
+using Android.App;
+using osu.Game;
+
+namespace osu.Android
+{
+ public class OsuGameAndroid : OsuGame
+ {
+ public override Version AssemblyVersion => new Version(Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionName);
+ }
+}
diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..acd21f9587
--- /dev/null
+++ b/osu.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Android/Resources/drawable/lazer.png b/osu.Android/Resources/drawable/lazer.png
new file mode 100644
index 0000000000..fc7aa8a092
Binary files /dev/null and b/osu.Android/Resources/drawable/lazer.png differ
diff --git a/osu.Android/lib/arm64-v8a/libbass.so b/osu.Android/lib/arm64-v8a/libbass.so
new file mode 100644
index 0000000000..d5c24b3e4b
Binary files /dev/null and b/osu.Android/lib/arm64-v8a/libbass.so differ
diff --git a/osu.Android/lib/arm64-v8a/libbass_fx.so b/osu.Android/lib/arm64-v8a/libbass_fx.so
new file mode 100644
index 0000000000..e498b10117
Binary files /dev/null and b/osu.Android/lib/arm64-v8a/libbass_fx.so differ
diff --git a/osu.Android/lib/armeabi-v7a/libbass.so b/osu.Android/lib/armeabi-v7a/libbass.so
new file mode 100644
index 0000000000..f7d23b9052
Binary files /dev/null and b/osu.Android/lib/armeabi-v7a/libbass.so differ
diff --git a/osu.Android/lib/armeabi-v7a/libbass_fx.so b/osu.Android/lib/armeabi-v7a/libbass_fx.so
new file mode 100644
index 0000000000..006e2feb30
Binary files /dev/null and b/osu.Android/lib/armeabi-v7a/libbass_fx.so differ
diff --git a/osu.Android/lib/x86/libbass.so b/osu.Android/lib/x86/libbass.so
new file mode 100644
index 0000000000..b0f758a42b
Binary files /dev/null and b/osu.Android/lib/x86/libbass.so differ
diff --git a/osu.Android/lib/x86/libbass_fx.so b/osu.Android/lib/x86/libbass_fx.so
new file mode 100644
index 0000000000..526dca39ca
Binary files /dev/null and b/osu.Android/lib/x86/libbass_fx.so differ
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
new file mode 100644
index 0000000000..ac3905a372
--- /dev/null
+++ b/osu.Android/osu.Android.csproj
@@ -0,0 +1,55 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {D1D5F9A8-B40B-40E6-B02F-482D03346D3D}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Android
+ osu.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+ cjk;mideast;other;rare;west
+ d8
+ r8
+
+
+
+
+
+
+
+
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {48f4582b-7687-4621-9cbe-5c24197cb536}
+ osu.Game.Rulesets.Mania
+
+
+ {c92a607b-1fdd-4954-9f92-03ff547d9080}
+ osu.Game.Rulesets.Osu
+
+
+ {f167e17a-7de6-4af5-b920-a5112296c695}
+ osu.Game.Rulesets.Taiko
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 00cabbadf7..761f52f961 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.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;
@@ -7,7 +7,6 @@ using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using osu.Desktop.Overlays;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Game;
using osuTK.Input;
@@ -39,9 +38,9 @@ namespace osu.Desktop
if (Host is DesktopGameHost desktopHost)
return new StableStorage(desktopHost);
}
- catch (Exception e)
+ catch (Exception)
{
- Logger.Error(e, "Error while searching for stable install");
+ Logger.Log("Could not find a stable install", LoggingTarget.Runtime, LogLevel.Important);
}
return null;
@@ -53,11 +52,7 @@ namespace osu.Desktop
if (!noVersionOverlay)
{
- LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v =>
- {
- Add(v);
- v.State = Visibility.Visible;
- });
+ LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
Add(new SquirrelUpdateManager());
@@ -72,15 +67,13 @@ namespace osu.Desktop
switch (newScreen)
{
- case Intro _:
+ case IntroScreen _:
case MainMenu _:
- if (versionManager != null)
- versionManager.State = Visibility.Visible;
+ versionManager?.Show();
break;
default:
- if (versionManager != null)
- versionManager.State = Visibility.Hidden;
+ versionManager?.Hide();
break;
}
}
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index d2aad99f41..1f1d2cea5f 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -12,7 +13,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
-using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
@@ -61,7 +61,7 @@ namespace osu.Desktop.Overlays
},
new OsuSpriteText
{
- Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
+ Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
Text = game.Version
},
}
@@ -120,7 +120,7 @@ namespace osu.Desktop.Overlays
Activated = delegate
{
- changelog.ShowBuild("lazer", version);
+ changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
return true;
};
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 29554df64c..141b2cdbbc 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -11,6 +11,7 @@ using osu.Framework.Development;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.IPC;
+using osu.Game.Tournament;
namespace osu.Desktop
{
@@ -28,25 +29,36 @@ namespace osu.Desktop
if (!host.IsPrimaryInstance)
{
- var importer = new ArchiveImportIPCChannel(host);
- // Restore the cwd so relative paths given at the command line work correctly
- Directory.SetCurrentDirectory(cwd);
+ if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
+ {
+ var importer = new ArchiveImportIPCChannel(host);
+ // Restore the cwd so relative paths given at the command line work correctly
+ Directory.SetCurrentDirectory(cwd);
- foreach (var file in args)
- {
- Console.WriteLine(@"Importing {0}", file);
- if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
- throw new TimeoutException(@"IPC took too long to send");
+ foreach (var file in args)
+ {
+ Console.WriteLine(@"Importing {0}", file);
+ if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
+ throw new TimeoutException(@"IPC took too long to send");
+ }
+
+ return 0;
}
+
+ // we want to allow multiple instances to be started when in debug.
+ if (!DebugUtils.IsDebugBuild)
+ return 0;
}
- else
+
+ switch (args.FirstOrDefault() ?? string.Empty)
{
- switch (args.FirstOrDefault() ?? string.Empty)
- {
- default:
- host.Run(new OsuGameDesktop(args));
- break;
- }
+ default:
+ host.Run(new OsuGameDesktop(args));
+ break;
+
+ case "--tournament":
+ host.Run(new TournamentGame());
+ break;
}
return 0;
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index e2c7a5e892..fa41c061b5 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.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;
@@ -25,11 +25,9 @@ namespace osu.Desktop.Updater
private UpdateManager updateManager;
private NotificationOverlay notificationOverlay;
- public void PrepareUpdate()
- {
- // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here
- UpdateManager.RestartAppWhenExited().Wait();
- }
+ public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited();
+
+ private static readonly Logger logger = Logger.GetLogger("updater");
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game)
@@ -46,7 +44,7 @@ namespace osu.Desktop.Updater
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
//should we schedule a retry on completion of this check?
- bool scheduleRetry = true;
+ bool scheduleRecheck = true;
try
{
@@ -81,15 +79,16 @@ namespace osu.Desktop.Updater
{
if (useDeltaPatching)
{
- Logger.Error(e, @"delta patching failed!");
+ logger.Add(@"delta patching failed; will attempt full download!");
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas.
checkForUpdateAsync(false, notification);
- scheduleRetry = false;
+ scheduleRecheck = false;
}
else
{
+ notification.State = ProgressNotificationState.Cancelled;
Logger.Error(e, @"update failed!");
}
}
@@ -100,11 +99,8 @@ namespace osu.Desktop.Updater
}
finally
{
- if (scheduleRetry)
+ if (scheduleRecheck)
{
- if (notification != null)
- notification.State = ProgressNotificationState.Cancelled;
-
//check again in 30 minutes.
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
}
@@ -134,8 +130,8 @@ namespace osu.Desktop.Updater
Text = @"Update ready to install. Click to restart!",
Activated = () =>
{
- updateManager.PrepareUpdate();
- game.GracefullyExit();
+ updateManager.PrepareUpdateAsync()
+ .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
return true;
}
};
@@ -169,16 +165,11 @@ namespace osu.Desktop.Updater
{
public LogLevel Level { get; set; } = LogLevel.Info;
- private Logger logger;
-
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level)
return;
- if (logger == null)
- logger = Logger.GetLogger("updater");
-
logger.Add(message);
}
diff --git a/osu.Desktop/lazer.ico b/osu.Desktop/lazer.ico
old mode 100644
new mode 100755
index 0c894dca41..a6aa8abb9f
Binary files a/osu.Desktop/lazer.ico and b/osu.Desktop/lazer.ico differ
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index aa8848c55f..538aaf2d7a 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -17,6 +17,7 @@
osu.Desktop.Program
+
@@ -27,8 +28,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..d918305f3d
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/MainActivity.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Catch.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..db95e18f13
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
new file mode 100644
index 0000000000..88b420ffad
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Rulesets.Catch.Tests
+ osu.Game.Rulesets.Catch.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
index 44817c1304..beca477943 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, null, "AppDelegate");
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index e45ed8c6f4..493ac7ae39 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -22,10 +23,10 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("spinner")]
[TestCase("spinner-and-circles")]
[TestCase("slider")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ [TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
+ [TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
+ public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
new file mode 100644
index 0000000000..33f93cdb4a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -0,0 +1,105 @@
+// 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.Graphics.Containers;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Tests.Visual;
+using System;
+using System.Collections.Generic;
+using osu.Game.Skinning;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osuTK.Graphics;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatcher : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherSprite),
+ };
+
+ private readonly Container container;
+
+ public TestSceneCatcher()
+ {
+ Child = container = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddStep("show default catcher implementation", () => { container.Child = new CatcherSprite(); });
+
+ AddStep("show custom catcher implementation", () =>
+ {
+ container.Child = new CatchCustomSkinSourceContainer
+ {
+ Child = new CatcherSprite()
+ };
+ });
+ }
+
+ private class CatcherCustomSkin : Container
+ {
+ public CatcherCustomSkin()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Blue
+ },
+ new SpriteText
+ {
+ Text = "custom"
+ }
+ };
+ }
+ }
+
+ [Cached(typeof(ISkinSource))]
+ private class CatchCustomSkinSourceContainer : Container, ISkinSource
+ {
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+
+ public Drawable GetDrawableComponent(string componentName)
+ {
+ switch (componentName)
+ {
+ case "Play/Catch/fruit-catcher-idle":
+ return new CatcherCustomSkin();
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(string sampleName) =>
+ throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName) =>
+ throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration =>
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
new file mode 100644
index 0000000000..7a9b61c60c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -0,0 +1,160 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneDrawableHitObjects : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CatcherArea.Catcher),
+ typeof(DrawableCatchRuleset),
+ typeof(DrawableFruit),
+ typeof(DrawableJuiceStream),
+ typeof(DrawableBanana)
+ };
+
+ private DrawableCatchRuleset drawableRuleset;
+ private double playfieldTime => drawableRuleset.Playfield.Time.Current;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var controlPointInfo = new ControlPointInfo();
+ controlPointInfo.TimingPoints.Add(new TimingControlPoint());
+
+ WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects = new List { new Fruit() },
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty(),
+ Metadata = new BeatmapMetadata
+ {
+ Artist = @"Unknown",
+ Title = @"You're breathtaking",
+ AuthorString = @"Everyone",
+ },
+ Ruleset = new CatchRuleset().RulesetInfo
+ },
+ ControlPointInfo = controlPointInfo
+ });
+
+ Add(new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap, Array.Empty())
+ }
+ });
+
+ AddStep("miss fruits", () => spawnFruits());
+ AddStep("hit fruits", () => spawnFruits(true));
+ AddStep("miss juicestream", () => spawnJuiceStream());
+ AddStep("hit juicestream", () => spawnJuiceStream(true));
+ AddStep("miss bananas", () => spawnBananas());
+ AddStep("hit bananas", () => spawnBananas(true));
+ }
+
+ private void spawnFruits(bool hit = false)
+ {
+ for (int i = 1; i <= 4; i++)
+ {
+ var fruit = new Fruit
+ {
+ X = getXCoords(hit),
+ LastInCombo = i % 4 == 0,
+ StartTime = playfieldTime + 800 + (200 * i)
+ };
+
+ fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ addToPlayfield(new DrawableFruit(fruit));
+ }
+ }
+
+ private void spawnJuiceStream(bool hit = false)
+ {
+ var xCoords = getXCoords(hit);
+
+ var juice = new JuiceStream
+ {
+ X = xCoords,
+ StartTime = playfieldTime + 1000,
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(0, 200)
+ })
+ };
+
+ juice.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ if (juice.NestedHitObjects.Last() is CatchHitObject tail)
+ tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
+
+ addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
+ }
+
+ private void spawnBananas(bool hit = false)
+ {
+ for (int i = 1; i <= 4; i++)
+ {
+ var banana = new Banana
+ {
+ X = getXCoords(hit),
+ LastInCombo = i % 4 == 0,
+ StartTime = playfieldTime + 800 + (200 * i)
+ };
+
+ banana.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ addToPlayfield(new DrawableBanana(banana));
+ }
+ }
+
+ private float getXCoords(bool hit)
+ {
+ const float x_offset = 0.2f;
+ float xCoords = drawableRuleset.Playfield.Width / 2;
+
+ if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
+ catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
+
+ if (hit)
+ xCoords -= x_offset;
+ else
+ xCoords += x_offset;
+
+ return xCoords;
+ }
+
+ private void addToPlayfield(DrawableCatchHitObject drawable)
+ {
+ foreach (var mod in Mods.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
new file mode 100644
index 0000000000..f6d26addaa
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -0,0 +1,20 @@
+// 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.Game.Rulesets.Catch.Mods;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
+ {
+ public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
+
+ public TestSceneDrawableHitObjectsHidden()
+ {
+ Mods.Value = new[] { new CatchModHidden() };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 9cbff8c5d3..a603d96201 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Tests.Visual;
@@ -17,8 +16,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- [BackgroundDependencyLoader]
- private void load()
+ [Test]
+ public void TestHyperDash()
{
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
}
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 265ecb7688..4100404da6 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
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 645cb5701a..5ab47c1611 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
using osu.Game.Rulesets.Catch.MathUtils;
+using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
base.PostProcess();
- applyPositionOffsets();
+ ApplyPositionOffsets(Beatmap);
initialiseHyperDash((List)Beatmap.HitObjects);
@@ -40,19 +41,29 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
- private void applyPositionOffsets()
+ public static void ApplyPositionOffsets(IBeatmap beatmap, params Mod[] mods)
{
var rng = new FastRandom(RNG_SEED);
- // todo: HardRock displacement should be applied here
- foreach (var obj in Beatmap.HitObjects)
+ bool shouldApplyHardRockOffset = mods.Any(m => m is ModHardRock);
+ float? lastPosition = null;
+ double lastStartTime = 0;
+
+ foreach (var obj in beatmap.HitObjects.OfType())
{
+ obj.XOffset = 0;
+
switch (obj)
{
+ case Fruit fruit:
+ if (shouldApplyHardRockOffset)
+ applyHardRockOffset(fruit, ref lastPosition, ref lastStartTime, rng);
+ break;
+
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType())
{
- banana.X = (float)rng.NextDouble();
+ banana.XOffset = (float)rng.NextDouble();
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
@@ -63,12 +74,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream:
foreach (var nested in juiceStream.NestedHitObjects)
{
- var hitObject = (CatchHitObject)nested;
- if (hitObject is TinyDroplet)
- hitObject.X += rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH;
- else if (hitObject is Droplet)
+ var catchObject = (CatchHitObject)nested;
+ catchObject.XOffset = 0;
+
+ if (catchObject is TinyDroplet)
+ catchObject.XOffset = MathHelper.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
+ else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
- hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1);
}
break;
@@ -76,6 +88,105 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
}
}
+ private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
+ {
+ if (hitObject is JuiceStream stream)
+ {
+ lastPosition = stream.EndX;
+ lastStartTime = stream.EndTime;
+ return;
+ }
+
+ if (!(hitObject is Fruit))
+ return;
+
+ float offsetPosition = hitObject.X;
+ double startTime = hitObject.StartTime;
+
+ if (lastPosition == null)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+
+ return;
+ }
+
+ float positionDiff = offsetPosition - lastPosition.Value;
+ double timeDiff = startTime - lastStartTime;
+
+ if (timeDiff > 1000)
+ {
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ return;
+ }
+
+ if (positionDiff == 0)
+ {
+ applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
+ hitObject.XOffset = offsetPosition - hitObject.X;
+ return;
+ }
+
+ if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
+ applyOffset(ref offsetPosition, positionDiff);
+
+ hitObject.XOffset = offsetPosition - hitObject.X;
+
+ lastPosition = offsetPosition;
+ lastStartTime = startTime;
+ }
+
+ ///
+ /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The maximum offset, cannot exceed 20px.
+ /// The random number generator.
+ private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
+ {
+ bool right = rng.NextBool();
+ float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
+
+ if (right)
+ {
+ // Clamp to the right bound
+ if (position + rand <= 1)
+ position += rand;
+ else
+ position -= rand;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position - rand >= 0)
+ position -= rand;
+ else
+ position += rand;
+ }
+ }
+
+ ///
+ /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
+ ///
+ /// The position which the offset should be applied to.
+ /// The amount to offset by.
+ private static void applyOffset(ref float position, float amount)
+ {
+ if (amount > 0)
+ {
+ // Clamp to the right bound
+ if (position + amount < 1)
+ position += amount;
+ }
+ else
+ {
+ // Clamp to the left bound
+ if (position + amount > 0)
+ position += amount;
+ }
+ }
+
private void initialiseHyperDash(List objects)
{
List objectWithDroplets = new List();
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index ea9f225cc1..6f1a7873ec 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -11,7 +11,6 @@ using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
-using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -108,7 +107,7 @@ namespace osu.Game.Rulesets.Catch
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index d6a1ed632b..44e1a8e5cc 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
- return new CatchDifficultyAttributes { Mods = mods };
+ return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
// this is the same as osu!, so there's potential to share the implementation... maybe
double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
@@ -41,7 +41,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor,
Mods = mods,
ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0,
- MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet))
+ MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)),
+ Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index b3605b013b..c721ff862a 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -61,6 +61,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+ ///
+ /// Generates a random integer value within the range [, ).
+ ///
+ /// The lower bound of the range.
+ /// The upper bound of the range.
+ /// The random value.
+ public int Next(double lowerBound, double upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
+
///
/// Generates a random double value within the range [0, 1).
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index 060e51e31d..ced1900ba9 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -1,121 +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 osu.Framework.MathUtils;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
-using System;
-using osu.Game.Rulesets.Objects;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModHardRock : ModHardRock, IApplicableToHitObject
+ public class CatchModHardRock : ModHardRock, IApplicableToBeatmap
{
public override double ScoreMultiplier => 1.12;
public override bool Ranked => true;
- private float? lastPosition;
- private double lastStartTime;
-
- public void ApplyToHitObject(HitObject hitObject)
- {
- if (hitObject is JuiceStream stream)
- {
- lastPosition = stream.EndX;
- lastStartTime = stream.EndTime;
- return;
- }
-
- if (!(hitObject is Fruit))
- return;
-
- var catchObject = (CatchHitObject)hitObject;
-
- float position = catchObject.X;
- double startTime = hitObject.StartTime;
-
- if (lastPosition == null)
- {
- lastPosition = position;
- lastStartTime = startTime;
-
- return;
- }
-
- float positionDiff = position - lastPosition.Value;
- double timeDiff = startTime - lastStartTime;
-
- if (timeDiff > 1000)
- {
- lastPosition = position;
- lastStartTime = startTime;
- return;
- }
-
- if (positionDiff == 0)
- {
- applyRandomOffset(ref position, timeDiff / 4d);
- catchObject.X = position;
- return;
- }
-
- if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d)
- applyOffset(ref position, positionDiff);
-
- catchObject.X = position;
-
- lastPosition = position;
- lastStartTime = startTime;
- }
-
- ///
- /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield.
- ///
- /// The position which the offset should be applied to.
- /// The maximum offset, cannot exceed 20px.
- private void applyRandomOffset(ref float position, double maxOffset)
- {
- bool right = RNG.NextBool();
- float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
-
- if (right)
- {
- // Clamp to the right bound
- if (position + rand <= 1)
- position += rand;
- else
- position -= rand;
- }
- else
- {
- // Clamp to the left bound
- if (position - rand >= 0)
- position -= rand;
- else
- position += rand;
- }
- }
-
- ///
- /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield.
- ///
- /// The position which the offset should be applied to.
- /// The amount to offset by.
- private void applyOffset(ref float position, float amount)
- {
- if (amount > 0)
- {
- // Clamp to the right bound
- if (position + amount < 1)
- position += amount;
- }
- else
- {
- // Clamp to the left bound
- if (position + amount > 0)
- position += amount;
- }
- }
+ public void ApplyToBeatmap(IBeatmap beatmap) => CatchBeatmapProcessor.ApplyPositionOffsets(beatmap, this);
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index 9990b01427..606a935229 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
@@ -1,7 +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 System.Linq;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Mods
{
@@ -9,5 +13,36 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override string Description => @"Play with fading fruits.";
public override double ScoreMultiplier => 1.06;
+
+ private const double fade_out_offset_multiplier = 0.6;
+ private const double fade_out_duration_multiplier = 0.44;
+
+ protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
+ {
+ if (!(drawable is DrawableCatchHitObject catchDrawable))
+ return;
+
+ if (catchDrawable.NestedHitObjects.Any())
+ {
+ foreach (var nestedDrawable in catchDrawable.NestedHitObjects)
+ {
+ if (nestedDrawable is DrawableCatchHitObject nestedCatchDrawable)
+ fadeOutHitObject(nestedCatchDrawable);
+ }
+ }
+ else
+ fadeOutHitObject(catchDrawable);
+ }
+
+ private void fadeOutHitObject(DrawableCatchHitObject drawable)
+ {
+ var hitObject = drawable.HitObject;
+
+ var offset = hitObject.TimePreempt * fade_out_offset_multiplier;
+ var duration = offset - hitObject.TimePreempt * fade_out_duration_multiplier;
+
+ using (drawable.BeginAbsoluteSequence(hitObject.StartTime - offset, true))
+ drawable.FadeOut(duration);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 2153b8dc85..19a1b59752 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -3,6 +3,7 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -12,7 +13,20 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const double OBJECT_RADIUS = 44;
- public float X { get; set; }
+ private float x;
+
+ public float X
+ {
+ get => x + XOffset;
+ set => x = value;
+ }
+
+ ///
+ /// A random offset applied to , set by the .
+ ///
+ internal float XOffset { get; set; }
+
+ public double TimePreempt = 1000;
public int IndexInBeatmap { get; set; }
@@ -54,6 +68,8 @@ namespace osu.Game.Rulesets.Catch.Objects
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
+ TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
+
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 2f8ccec48b..a1279e8443 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -3,12 +3,10 @@
using System;
using osuTK;
-using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -60,19 +58,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss);
}
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- base.SkinChanged(skin, allowFallback);
-
- if (HitObject is IHasComboInformation combo)
- AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
- }
-
- private const float preempt = 1000;
+ protected override bool UseTransformStateManagement => false;
protected override void UpdateState(ArmedState state)
{
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
+ // TODO: update to use new state management.
+ using (BeginAbsoluteSequence(HitObject.StartTime - HitObject.TimePreempt))
this.FadeIn(200);
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index 9cabdc3dd9..059310d671 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -27,16 +26,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private void load()
{
AddInternal(pulp = new Pulp { Size = Size });
- }
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
- {
- base.AccentColour = value;
- pulp.AccentColour = AccentColour;
- }
+ AccentColour.BindValueChanged(colour => { pulp.AccentColour = colour.NewValue; }, true);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 77407def54..ce2daebbf1 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private void load()
{
// todo: this should come from the skin.
- AccentColour = colourForRepresentation(HitObject.VisualRepresentation);
+ AccentColour.Value = colourForRepresentation(HitObject.VisualRepresentation);
AddRangeInternal(new[]
{
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow,
Radius = 4 * radius_adjust,
- Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
+ Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Value.Darken(1).Opacity(0.6f)
},
Size = new Vector2(Height),
Anchor = Anchor.Centre,
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
new Box
{
AlwaysPresent = true,
- Colour = AccentColour,
+ Colour = AccentColour.Value,
Alpha = 0,
RelativeSizeAxes = Axes.Both
}
@@ -115,32 +115,32 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.34f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(0, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(90, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(180, distance_from_centre_4),
},
new Pulp
{
Size = new Vector2(large_pulp_4),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(270, distance_from_centre_4),
},
}
@@ -154,32 +154,32 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.3f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(45, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(135, distance_from_centre_4),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4),
Position = positionAt(225, distance_from_centre_4),
},
new Pulp
{
Size = new Vector2(large_pulp_4),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(315, distance_from_centre_4),
},
}
@@ -193,26 +193,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.33f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(60, distance_from_centre_3),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(180, distance_from_centre_3),
},
new Pulp
{
Size = new Vector2(large_pulp_3),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(300, distance_from_centre_3),
},
}
@@ -226,26 +226,26 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.25f,
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(0, distance_from_centre_3),
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_3),
Position = positionAt(120, distance_from_centre_3),
},
new Pulp
{
Size = new Vector2(large_pulp_3),
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Position = positionAt(240, distance_from_centre_3),
},
}
@@ -259,13 +259,13 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(small_pulp),
Y = -0.3f
},
new Pulp
{
- AccentColour = AccentColour,
+ AccentColour = AccentColour.Value,
Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
Y = 0.05f,
},
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index a9fd34455a..0952e8981a 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
base.CreateNestedHitObjects();
- var tickSamples = Samples.Select(s => new SampleInfo
+ var tickSamples = Samples.Select(s => new HitSampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Distance => Path.Distance;
- public List> NodeSamples { get; set; } = new List>();
+ public List> NodeSamples { get; set; } = new List>();
public double? LegacyLastTickOffset { get; set; }
}
diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index dba76eef49..26f20b223a 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.iOS")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Android")]
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index daa3f61de3..8dd00756f2 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -43,10 +43,13 @@ namespace osu.Game.Rulesets.Catch.Replays
float positionChange = Math.Abs(lastPosition - h.X);
double timeAvailable = h.StartTime - lastTime;
- //So we can either make it there without a dash or not.
- double speedRequired = positionChange / timeAvailable;
+ // So we can either make it there without a dash or not.
+ // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
+ // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
+ double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable;
- bool dashRequired = speedRequired > movement_speed && h.StartTime != 0;
+ bool dashRequired = speedRequired > movement_speed;
+ bool impossibleJump = speedRequired > movement_speed * 2;
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f;
@@ -59,9 +62,8 @@ namespace osu.Game.Rulesets.Catch.Replays
return;
}
- if (h is Banana)
+ if (impossibleJump)
{
- // auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
}
else if (h.HyperDash)
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..83f9e30800
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider-expected-conversion.json
@@ -0,0 +1,150 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 177
+ },
+ {
+ "StartTime": 450,
+ "Position": 216.539276
+ },
+ {
+ "StartTime": 532,
+ "Position": 256.5667
+ },
+ {
+ "StartTime": 614,
+ "Position": 296.594116
+ },
+ {
+ "StartTime": 696,
+ "Position": 336.621521
+ },
+ {
+ "StartTime": 778,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 860,
+ "Position": 337.318878
+ },
+ {
+ "StartTime": 942,
+ "Position": 297.291443
+ },
+ {
+ "StartTime": 1024,
+ "Position": 257.264038
+ },
+ {
+ "StartTime": 1106,
+ "Position": 217.2366
+ },
+ {
+ "StartTime": 1188,
+ "Position": 177
+ },
+ {
+ "StartTime": 1270,
+ "Position": 216.818192
+ },
+ {
+ "StartTime": 1352,
+ "Position": 256.8456
+ },
+ {
+ "StartTime": 1434,
+ "Position": 296.873047
+ },
+ {
+ "StartTime": 1516,
+ "Position": 336.900452
+ },
+ {
+ "StartTime": 1598,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 1680,
+ "Position": 337.039948
+ },
+ {
+ "StartTime": 1762,
+ "Position": 297.0125
+ },
+ {
+ "StartTime": 1844,
+ "Position": 256.9851
+ },
+ {
+ "StartTime": 1926,
+ "Position": 216.957672
+ },
+ {
+ "StartTime": 2008,
+ "Position": 177
+ },
+ {
+ "StartTime": 2090,
+ "Position": 217.097137
+ },
+ {
+ "StartTime": 2172,
+ "Position": 257.124573
+ },
+ {
+ "StartTime": 2254,
+ "Position": 297.152
+ },
+ {
+ "StartTime": 2336,
+ "Position": 337.179443
+ },
+ {
+ "StartTime": 2418,
+ "Position": 376.99762
+ },
+ {
+ "StartTime": 2500,
+ "Position": 336.760956
+ },
+ {
+ "StartTime": 2582,
+ "Position": 296.733643
+ },
+ {
+ "StartTime": 2664,
+ "Position": 256.7062
+ },
+ {
+ "StartTime": 2746,
+ "Position": 216.678772
+ },
+ {
+ "StartTime": 2828,
+ "Position": 177
+ },
+ {
+ "StartTime": 2909,
+ "Position": 216.887909
+ },
+ {
+ "StartTime": 2991,
+ "Position": 256.915344
+ },
+ {
+ "StartTime": 3073,
+ "Position": 296.942749
+ },
+ {
+ "StartTime": 3155,
+ "Position": 336.970184
+ },
+ {
+ "StartTime": 3237,
+ "Position": 376.99762
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
new file mode 100644
index 0000000000..c1971edf2c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-repeat-slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+177,191,369,6,0,L|382:192,7,200
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
new file mode 100644
index 0000000000..7333b600fb
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner-expected-conversion.json
@@ -0,0 +1,74 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 65
+ },
+ {
+ "StartTime": 450,
+ "Position": 482
+ },
+ {
+ "StartTime": 532,
+ "Position": 164
+ },
+ {
+ "StartTime": 614,
+ "Position": 315
+ },
+ {
+ "StartTime": 696,
+ "Position": 145
+ },
+ {
+ "StartTime": 778,
+ "Position": 159
+ },
+ {
+ "StartTime": 860,
+ "Position": 310
+ },
+ {
+ "StartTime": 942,
+ "Position": 441
+ },
+ {
+ "StartTime": 1024,
+ "Position": 428
+ },
+ {
+ "StartTime": 1106,
+ "Position": 243
+ },
+ {
+ "StartTime": 1188,
+ "Position": 422
+ },
+ {
+ "StartTime": 1270,
+ "Position": 481
+ },
+ {
+ "StartTime": 1352,
+ "Position": 104
+ },
+ {
+ "StartTime": 1434,
+ "Position": 473
+ },
+ {
+ "StartTime": 1516,
+ "Position": 135
+ },
+ {
+ "StartTime": 1598,
+ "Position": 360
+ },
+ {
+ "StartTime": 1680,
+ "Position": 123
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
new file mode 100644
index 0000000000..208d21a344
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-spinner.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+256,192,369,12,0,1680,0:0:0:0:
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
new file mode 100644
index 0000000000..bbc16ab912
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream-expected-conversion.json
@@ -0,0 +1,234 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 450,
+ "Objects": [{
+ "StartTime": 450,
+ "Position": 254
+ }]
+ },
+ {
+ "StartTime": 532,
+ "Objects": [{
+ "StartTime": 532,
+ "Position": 241
+ }]
+ },
+ {
+ "StartTime": 614,
+ "Objects": [{
+ "StartTime": 614,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 696,
+ "Objects": [{
+ "StartTime": 696,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 778,
+ "Objects": [{
+ "StartTime": 778,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 860,
+ "Objects": [{
+ "StartTime": 860,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 942,
+ "Objects": [{
+ "StartTime": 942,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1024,
+ "Objects": [{
+ "StartTime": 1024,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1106,
+ "Objects": [{
+ "StartTime": 1106,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1188,
+ "Objects": [{
+ "StartTime": 1188,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1270,
+ "Objects": [{
+ "StartTime": 1270,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1352,
+ "Objects": [{
+ "StartTime": 1352,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1434,
+ "Objects": [{
+ "StartTime": 1434,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 1516,
+ "Objects": [{
+ "StartTime": 1516,
+ "Position": 253
+ }]
+ },
+ {
+ "StartTime": 1598,
+ "Objects": [{
+ "StartTime": 1598,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1680,
+ "Objects": [{
+ "StartTime": 1680,
+ "Position": 260
+ }]
+ },
+ {
+ "StartTime": 1762,
+ "Objects": [{
+ "StartTime": 1762,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 1844,
+ "Objects": [{
+ "StartTime": 1844,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 1926,
+ "Objects": [{
+ "StartTime": 1926,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2008,
+ "Objects": [{
+ "StartTime": 2008,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2090,
+ "Objects": [{
+ "StartTime": 2090,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2172,
+ "Objects": [{
+ "StartTime": 2172,
+ "Position": 243
+ }]
+ },
+ {
+ "StartTime": 2254,
+ "Objects": [{
+ "StartTime": 2254,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2336,
+ "Objects": [{
+ "StartTime": 2336,
+ "Position": 278
+ }]
+ },
+ {
+ "StartTime": 2418,
+ "Objects": [{
+ "StartTime": 2418,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2500,
+ "Objects": [{
+ "StartTime": 2500,
+ "Position": 258
+ }]
+ },
+ {
+ "StartTime": 2582,
+ "Objects": [{
+ "StartTime": 2582,
+ "Position": 256
+ }]
+ },
+ {
+ "StartTime": 2664,
+ "Objects": [{
+ "StartTime": 2664,
+ "Position": 242
+ }]
+ },
+ {
+ "StartTime": 2746,
+ "Objects": [{
+ "StartTime": 2746,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2828,
+ "Objects": [{
+ "StartTime": 2828,
+ "Position": 238
+ }]
+ },
+ {
+ "StartTime": 2909,
+ "Objects": [{
+ "StartTime": 2909,
+ "Position": 271
+ }]
+ },
+ {
+ "StartTime": 2991,
+ "Objects": [{
+ "StartTime": 2991,
+ "Position": 254
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
new file mode 100644
index 0000000000..321af4604b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/hardrock-stream.osu
@@ -0,0 +1,50 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+258,189,369,1,0,0:0:0:0:
+258,189,450,1,0,0:0:0:0:
+258,189,532,1,0,0:0:0:0:
+258,189,614,1,0,0:0:0:0:
+258,189,696,1,0,0:0:0:0:
+258,189,778,1,0,0:0:0:0:
+258,189,860,1,0,0:0:0:0:
+258,189,942,1,0,0:0:0:0:
+258,189,1024,1,0,0:0:0:0:
+258,189,1106,1,0,0:0:0:0:
+258,189,1188,1,0,0:0:0:0:
+258,189,1270,1,0,0:0:0:0:
+258,189,1352,1,0,0:0:0:0:
+258,189,1434,1,0,0:0:0:0:
+258,189,1516,1,0,0:0:0:0:
+258,189,1598,1,0,0:0:0:0:
+258,189,1680,1,0,0:0:0:0:
+258,189,1762,1,0,0:0:0:0:
+258,189,1844,1,0,0:0:0:0:
+258,189,1926,1,0,0:0:0:0:
+258,189,2008,1,0,0:0:0:0:
+258,189,2090,1,0,0:0:0:0:
+258,189,2172,1,0,0:0:0:0:
+258,189,2254,1,0,0:0:0:0:
+258,189,2336,1,0,0:0:0:0:
+258,189,2418,1,0,0:0:0:0:
+258,189,2500,1,0,0:0:0:0:
+258,189,2582,1,0,0:0:0:0:
+258,189,2664,1,0,0:0:0:0:
+258,189,2746,1,0,0:0:0:0:
+258,189,2828,1,0,0:0:0:0:
+258,189,2909,1,0,0:0:0:0:
+258,189,2991,1,0,0:0:0:0:
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index e7c7fd77df..0b06e958e6 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -6,8 +6,6 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
@@ -141,7 +139,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- Children = new Drawable[]
+ Children = new[]
{
caughtFruit = new Container
{
@@ -212,7 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
- private Sprite createCatcherSprite() => new CatcherSprite();
+ private Drawable createCatcherSprite() => new CatcherSprite();
///
/// Add a caught fruit to the catcher's stack.
@@ -379,8 +377,8 @@ namespace osu.Game.Rulesets.Catch.UI
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1);
// Correct overshooting.
- if (hyperDashDirection > 0 && hyperDashTargetPosition < X ||
- hyperDashDirection < 0 && hyperDashTargetPosition > X)
+ if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
+ (hyperDashDirection < 0 && hyperDashTargetPosition > X))
{
X = hyperDashTargetPosition;
SetHyperDashState();
@@ -444,23 +442,6 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.Expire();
}
-
- private class CatcherSprite : Sprite
- {
- public CatcherSprite()
- {
- Size = new Vector2(CATCHER_SIZE);
-
- // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
- OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE;
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
- }
- }
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
new file mode 100644
index 0000000000..c0c1952064
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -0,0 +1,33 @@
+// 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.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ public class CatcherSprite : CompositeDrawable
+ {
+ public CatcherSprite()
+ {
+ Size = new Vector2(CatcherArea.CATCHER_SIZE);
+
+ // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
+ OriginPosition = new Vector2(-0.02f, 0.06f) * CatcherArea.CATCHER_SIZE;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle")
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..0a3f05ae54
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.Android/MainActivity.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Mania.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..e6728c801d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj
new file mode 100644
index 0000000000..0e557cb260
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {531F1092-DB27-445D-AA33-2A77C7187C99}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Rulesets.Mania.Tests
+ osu.Game.Rulesets.Mania.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {48f4582b-7687-4621-9cbe-5c24197cb536}
+ osu.Game.Rulesets.Mania
+
+
+ {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}
+ osu.Game
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
index d47ac4643f..0362402320 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, null, "AppDelegate");
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 6b95975059..6f10540973 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[TestCase("basic")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
@@ -35,11 +32,37 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
- protected override ManiaConvertMapping CreateConvertMapping() => new ManiaConvertMapping(Converter);
+ private readonly Dictionary rngSnapshots = new Dictionary();
+
+ protected override void OnConversionGenerated(HitObject original, IEnumerable result, IBeatmapConverter beatmapConverter)
+ {
+ base.OnConversionGenerated(original, result, beatmapConverter);
+
+ rngSnapshots[original] = new RngSnapshot(beatmapConverter);
+ }
+
+ protected override ManiaConvertMapping CreateConvertMapping(HitObject source) => new ManiaConvertMapping(rngSnapshots[source]);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
+ public class RngSnapshot
+ {
+ public readonly uint RandomW;
+ public readonly uint RandomX;
+ public readonly uint RandomY;
+ public readonly uint RandomZ;
+
+ public RngSnapshot(IBeatmapConverter converter)
+ {
+ var maniaConverter = (ManiaBeatmapConverter)converter;
+ RandomW = maniaConverter.Random.W;
+ RandomX = maniaConverter.Random.X;
+ RandomY = maniaConverter.Random.Y;
+ RandomZ = maniaConverter.Random.Z;
+ }
+ }
+
public class ManiaConvertMapping : ConvertMapping, IEquatable
{
public uint RandomW;
@@ -51,13 +74,12 @@ namespace osu.Game.Rulesets.Mania.Tests
{
}
- public ManiaConvertMapping(IBeatmapConverter converter)
+ public ManiaConvertMapping(RngSnapshot snapshot)
{
- var maniaConverter = (ManiaBeatmapConverter)converter;
- RandomW = maniaConverter.Random.W;
- RandomX = maniaConverter.Random.X;
- RandomY = maniaConverter.Random.Y;
- RandomZ = maniaConverter.Random.Z;
+ RandomW = snapshot.RandomW;
+ RandomX = snapshot.RandomX;
+ RandomY = snapshot.RandomY;
+ RandomZ = snapshot.RandomZ;
}
public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
index 04c5724f93..622d840a0c 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Child = drawableObject = new DrawableHoldNote(holdNote)
{
Height = 300,
- AccentColour = OsuColour.Gray(0.3f)
+ AccentColour = { Value = OsuColour.Gray(0.3f) }
}
};
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index b2613a59d5..031abb08e2 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Tests
AutoSizeAxes = Axes.Both,
Child = new NoteContainer(direction, $"note {identifier}, scrolling {direction.ToString().ToLowerInvariant()}")
{
- Child = hitObject = new DrawableNote(note) { AccentColour = Color4.OrangeRed }
+ Child = hitObject = new DrawableNote(note) { AccentColour = { Value = Color4.OrangeRed } }
}
};
}
@@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Child = hitObject = new DrawableHoldNote(note)
{
RelativeSizeAxes = Axes.Both,
- AccentColour = Color4.OrangeRed,
+ AccentColour = { Value = Color4.OrangeRed },
}
}
};
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 dbade6ff8d..013d2a71d4 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
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 704deba78b..e10602312e 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
/// The time to retrieve the sample info list from.
///
- private List sampleInfoListAt(double time)
+ private List sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 1b6ff16388..ea418eedb4 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
- bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
+ bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability);
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
@@ -443,7 +443,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
noteCount = 0;
noteCount = Math.Min(TotalColumns - 1, noteCount);
- bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
+ bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == HitSampleInfo.HIT_WHISTLE || s.Name == HitSampleInfo.HIT_FINISH || s.Name == HitSampleInfo.HIT_CLAP);
var rowPattern = new Pattern();
@@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// The time to retrieve the sample info list from.
///
- private List sampleInfoListAt(double time)
+ private List sampleInfoListAt(double time)
{
var curveData = HitObject as IHasCurve;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 9e95be35fa..b3be08e1f7 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
switch (TotalColumns)
{
- case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000:
+ case 8 when HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000:
addToPattern(pattern, 0, generateHold);
break;
@@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
};
if (hold.Head.Samples == null)
- hold.Head.Samples = new List();
+ hold.Head.Samples = new List();
- hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL });
+ hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index d13b21183b..decd159ee9 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (!convertType.HasFlag(PatternType.KeepSingle))
{
- if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
+ if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
- else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
+ else if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))
convertType |= PatternType.Gathered;
}
}
@@ -263,7 +263,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// Whether this hit object can generate a note in the special column.
///
- private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
+ private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
///
/// Generates a random pattern.
@@ -364,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
- if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
+ if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP))
p2 = 1;
return GetRandomNoteCount(p2, p3, p4, p5);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
index a3cd455886..e4a28167ec 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
/// Keep the same as last row.
///
- ForceStack = 1 << 0,
+ ForceStack = 1,
///
/// Keep different from last row.
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 59fed1031f..4a9c22d339 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
- return new ManiaDifficultyAttributes { Mods = mods };
+ return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
return new ManiaDifficultyAttributes
{
@@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
+ Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d83033f9c6..8966b5058f 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -13,7 +13,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -154,7 +153,7 @@ namespace osu.Game.Rulesets.Mania
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
index 17f4098420..485595cea9 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModMirror : Mod, IApplicableToBeatmap
+ public class ManiaModMirror : Mod, IApplicableToBeatmap
{
public override string Name => "Mirror";
public override string Acronym => "MR";
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
- public void ApplyToBeatmap(Beatmap beatmap)
+ public void ApplyToBeatmap(IBeatmap beatmap)
{
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index ba16140644..9275371a61 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModRandom : Mod, IApplicableToBeatmap
+ public class ManiaModRandom : Mod, IApplicableToBeatmap
{
public override string Name => "Random";
public override string Acronym => "RD";
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 1;
- public void ApplyToBeatmap(Beatmap beatmap)
+ public void ApplyToBeatmap(IBeatmap beatmap)
{
var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns;
var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList();
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 9368af987d..952c6e128e 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
-using osuTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
@@ -36,11 +35,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private bool hasBroken;
- private readonly Container tickContainer;
-
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
{
+ Container tickContainer;
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
@@ -74,6 +72,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(Head);
AddNested(Tail);
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ bodyPiece.AccentColour = colour.NewValue;
+ Head.AccentColour.Value = colour.NewValue;
+ Tail.AccentColour.Value = colour.NewValue;
+ tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue);
+ }, true);
}
protected override void OnDirectionChanged(ValueChangedEvent e)
@@ -83,20 +89,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
- {
- base.AccentColour = value;
-
- bodyPiece.AccentColour = value;
- Head.AccentColour = value;
- Tail.AccentColour = value;
- tickContainer.ForEach(t => t.AccentColour = value);
- }
- }
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (Tail.AllJudged)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
index 9a29273282..9b0322a6cd 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
@@ -3,7 +3,6 @@
using System;
using osuTK;
-using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -23,11 +22,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
public Func HoldStartTime;
- private readonly Container glowContainer;
-
public DrawableHoldNoteTick(HoldNoteTick hitObject)
: base(hitObject)
{
+ Container glowContainer;
+
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
@@ -53,23 +52,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
});
- }
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
+ AccentColour.BindValueChanged(colour =>
{
- base.AccentColour = value;
-
glowContainer.EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 2f,
Roundness = 15f,
- Colour = value.Opacity(0.3f)
+ Colour = colour.NewValue.Opacity(0.3f)
};
- }
+ }, true);
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index 0873f753be..db6b53e76d 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -58,8 +58,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
HitObject = hitObject;
}
+ protected override bool UseTransformStateManagement => false;
+
protected override void UpdateState(ArmedState state)
{
+ // TODO: update to use new state management.
switch (state)
{
case ArmedState.Miss:
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index afd7777861..dccff7f6ac 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -3,7 +3,6 @@
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
-using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Bindings;
@@ -30,6 +29,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Masking = true;
AddInternal(headPiece = new NotePiece());
+
+ AccentColour.BindValueChanged(colour =>
+ {
+ headPiece.AccentColour = colour.NewValue;
+
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour.NewValue.Lighten(1f).Opacity(0.6f),
+ Radius = 10,
+ };
+ }, true);
}
protected override void OnDirectionChanged(ValueChangedEvent e)
@@ -39,23 +50,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
headPiece.Anchor = headPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
- {
- base.AccentColour = value;
- headPiece.AccentColour = AccentColour;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = AccentColour.Lighten(1f).Opacity(0.6f),
- Radius = 10,
- };
- }
- }
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!userTriggered)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
index 8102718edf..a92e56d3c3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
}
}
- private Cached subtractionCache = new Cached();
+ private readonly Cached subtractionCache = new Cached();
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
index f3ea6c7b71..ca1f7036c7 100644
--- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
@@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.iOS")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Android")]
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index c59bed4ea7..91dd236ab1 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Mania.UI
/// The DrawableHitObject to add.
public override void Add(DrawableHitObject hitObject)
{
- hitObject.AccentColour = AccentColour;
+ hitObject.AccentColour.Value = AccentColour;
hitObject.OnNewResult += OnNewResult;
HitObjectContainer.Add(hitObject);
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index 0ec1fc38d2..48470add8b 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.UI
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1),
+ Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour.Value, Color4.White, 0, 1),
Radius = 100,
},
Child = new Box
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..e6c508d99e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.Android/MainActivity.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Osu.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..aad907b241
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj
new file mode 100644
index 0000000000..dcf1573522
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {90CAB706-39CB-4B93-9629-3218A6FF8E9B}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Rulesets.Osu.Tests
+ osu.Game.Rulesets.Osu.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {c92a607b-1fdd-4954-9f92-03ff547d9080}
+ osu.Game.Rulesets.Osu
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
index 7a0797a909..3718264a42 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, null, "AppDelegate");
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index f98d63e6c7..e9fdf924c3 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -8,7 +8,6 @@ using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
@@ -21,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase("basic")]
[TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ [TestCase("repeat-slider")]
+ [TestCase("uneven-repeat-slider")]
+ [TestCase("old-stacking")]
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
@@ -32,22 +31,22 @@ namespace osu.Game.Rulesets.Osu.Tests
{
case Slider slider:
foreach (var nested in slider.NestedHitObjects)
- yield return createConvertValue(nested);
+ yield return createConvertValue((OsuHitObject)nested);
break;
default:
- yield return createConvertValue(hitObject);
+ yield return createConvertValue((OsuHitObject)hitObject);
break;
}
- ConvertValue createConvertValue(HitObject obj) => new ConvertValue
+ ConvertValue createConvertValue(OsuHitObject obj) => new ConvertValue
{
StartTime = obj.StartTime,
EndTime = (obj as IHasEndTime)?.EndTime ?? obj.StartTime,
- X = (obj as IHasPosition)?.X ?? OsuPlayfield.BASE_SIZE.X / 2,
- Y = (obj as IHasPosition)?.Y ?? OsuPlayfield.BASE_SIZE.Y / 2,
+ X = obj.StackedPosition.X,
+ Y = obj.StackedPosition.Y
};
}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index e55dc1f902..693faee3b7 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -15,6 +15,7 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.931145117263422, "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/default-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png
new file mode 100755
index 0000000000..db2f4a5730
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png
new file mode 100755
index 0000000000..b0db9c00af
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hit300k@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png
new file mode 100755
index 0000000000..6674616472
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png
new file mode 100755
index 0000000000..1f98c1697e
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/default-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png
new file mode 100755
index 0000000000..0a6ec6535c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png
new file mode 100755
index 0000000000..919d8f405c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png
new file mode 100755
index 0000000000..a9b2d95d88
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
new file mode 100644
index 0000000000..a2c058193b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
@@ -0,0 +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 System.IO;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.IO.Stores;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public abstract class SkinnableTestScene : OsuGridTestScene
+ {
+ private Skin metricsSkin;
+ private Skin defaultSkin;
+ private Skin specialSkin;
+
+ protected SkinnableTestScene()
+ : base(2, 2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ var skins = new SkinManager(LocalStorage, ContextFactory, null, audio);
+
+ metricsSkin = getSkinFromResources(skins, "metrics_skin");
+ defaultSkin = getSkinFromResources(skins, "default_skin");
+ specialSkin = getSkinFromResources(skins, "special_skin");
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = new LocalSkinOverrideContainer(null) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction());
+ Cell(1).Child = new LocalSkinOverrideContainer(metricsSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction());
+ Cell(2).Child = new LocalSkinOverrideContainer(defaultSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction());
+ Cell(3).Child = new LocalSkinOverrideContainer(specialSkin) { RelativeSizeAxes = Axes.Both }.WithChild(creationFunction());
+ }
+
+ private static Skin getSkinFromResources(SkinManager skins, string name)
+ {
+ using (var storage = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll"))
+ {
+ var tempName = Path.GetTempFileName();
+
+ File.Delete(tempName);
+ Directory.CreateDirectory(tempName);
+
+ var files = storage.GetAvailableResources().Where(f => f.StartsWith($"Resources/{name}"));
+
+ foreach (var file in files)
+ using (var stream = storage.GetStream(file))
+ using (var newFile = File.Create(Path.Combine(tempName, Path.GetFileName(file))))
+ stream.CopyTo(newFile);
+
+ return skins.GetSkin(skins.Import(tempName).Result);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index d44a0cd841..84a7bfc53e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -7,7 +7,6 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System;
@@ -19,37 +18,32 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestSceneHitCircle : OsuTestScene
+ public class TestSceneHitCircle : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
- private readonly Container content;
- protected override Container Content => content;
-
private int depthIndex;
public TestSceneHitCircle()
{
- base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
-
- AddStep("Miss Big Single", () => testSingle(2));
- AddStep("Miss Medium Single", () => testSingle(5));
- AddStep("Miss Small Single", () => testSingle(7));
- AddStep("Hit Big Single", () => testSingle(2, true));
- AddStep("Hit Medium Single", () => testSingle(5, true));
- AddStep("Hit Small Single", () => testSingle(7, true));
- AddStep("Miss Big Stream", () => testStream(2));
- AddStep("Miss Medium Stream", () => testStream(5));
- AddStep("Miss Small Stream", () => testStream(7));
- AddStep("Hit Big Stream", () => testStream(2, true));
- AddStep("Hit Medium Stream", () => testStream(5, true));
- AddStep("Hit Small Stream", () => testStream(7, true));
+ AddStep("Miss Big Single", () => SetContents(() => testSingle(2)));
+ AddStep("Miss Medium Single", () => SetContents(() => testSingle(5)));
+ AddStep("Miss Small Single", () => SetContents(() => testSingle(7)));
+ AddStep("Hit Big Single", () => SetContents(() => testSingle(2, true)));
+ AddStep("Hit Medium Single", () => SetContents(() => testSingle(5, true)));
+ AddStep("Hit Small Single", () => SetContents(() => testSingle(7, true)));
+ AddStep("Miss Big Stream", () => SetContents(() => testStream(2)));
+ AddStep("Miss Medium Stream", () => SetContents(() => testStream(5)));
+ AddStep("Miss Small Stream", () => SetContents(() => testStream(7)));
+ AddStep("Hit Big Stream", () => SetContents(() => testStream(2, true)));
+ AddStep("Hit Medium Stream", () => SetContents(() => testStream(5, true)));
+ AddStep("Hit Small Stream", () => SetContents(() => testStream(7, true)));
}
- private void testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
+ private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
{
positionOffset = positionOffset ?? Vector2.Zero;
@@ -61,27 +55,33 @@ namespace osu.Game.Rulesets.Osu.Tests
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
- var drawable = new TestDrawableHitCircle(circle, auto)
- {
- Anchor = Anchor.Centre,
- Depth = depthIndex++
- };
+ var drawable = CreateDrawableHitCircle(circle, auto);
foreach (var mod in Mods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
- Add(drawable);
+ return drawable;
}
- private void testStream(float circleSize, bool auto = false)
+ protected virtual TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto) => new TestDrawableHitCircle(circle, auto)
{
+ Anchor = Anchor.Centre,
+ Depth = depthIndex++
+ };
+
+ private Drawable testStream(float circleSize, bool auto = false)
+ {
+ var container = new Container { RelativeSizeAxes = Axes.Both };
+
Vector2 pos = new Vector2(-250, 0);
for (int i = 0; i <= 1000; i += 100)
{
- testSingle(circleSize, auto, i, pos);
+ container.Add(testSingle(circleSize, auto, i, pos));
pos.X += 50;
}
+
+ return container;
}
protected class TestDrawableHitCircle : DrawableHitCircle
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
index 12a3a8d27e..8e73d6152f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
@@ -46,11 +46,11 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("move mouse away", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft));
AddStep("click", () => osuInputManager.GameClick());
- AddAssert("not dismissed", () => !resumeFired && resume.State == Visibility.Visible);
+ AddAssert("not dismissed", () => !resumeFired && resume.State.Value == Visibility.Visible);
AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
AddStep("click", () => osuInputManager.GameClick());
- AddAssert("dismissed", () => resumeFired && resume.State == Visibility.Hidden);
+ AddAssert("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden);
}
private class ManualOsuInputManager : OsuInputManager
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 3d8afd66f4..84a73c7cfc 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -1,23 +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.Framework.Graphics;
using osu.Framework.MathUtils;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneShaking : TestSceneHitCircle
{
- public override void Add(Drawable drawable)
+ protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
{
- base.Add(drawable);
+ var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
- if (drawable is TestDrawableHitCircle hitObject)
- {
- Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
- hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
- }
+ Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(),
+ drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
+
+ return drawableHitObject;
}
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index 1ba6d107be..c5a27205d6 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -248,9 +248,9 @@ namespace osu.Game.Rulesets.Osu.Tests
private void createCatmull(int repeats = 0)
{
- var repeatSamples = new List>();
+ var repeatSamples = new List>();
for (int i = 0; i < repeats; i++)
- repeatSamples.Add(new List());
+ repeatSamples.Add(new List());
var slider = new Slider
{
@@ -270,11 +270,11 @@ namespace osu.Game.Rulesets.Osu.Tests
addSlider(slider, 3, 1);
}
- private List> createEmptySamples(int repeats)
+ private List> createEmptySamples(int repeats)
{
- var repeatSamples = new List>();
+ var repeatSamples = new List>();
for (int i = 0; i < repeats; i++)
- repeatSamples.Add(new List());
+ repeatSamples.Add(new List());
return repeatSamples;
}
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 a99a93c3e9..92c5c77aac 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
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index b2beda18f4..bb19b783aa 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Beatmaps
{
@@ -69,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
break;
if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance
- || stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)
+ || (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance))
{
stackBaseIndex = n;
@@ -208,17 +209,22 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
if (beatmap.HitObjects[j].StartTime - stackThreshold > startTime)
break;
+ // The start position of the hitobject, or the position at the end of the path if the hitobject is a slider
+ Vector2 position2 = currHitObject is Slider currSlider
+ ? currSlider.Position + currSlider.Path.PositionAt(1)
+ : currHitObject.Position;
+
if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.Position) < stack_distance)
{
currHitObject.StackHeight++;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
}
- else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, currHitObject.EndPosition) < stack_distance)
+ else if (Vector2Extensions.Distance(beatmap.HitObjects[j].Position, position2) < stack_distance)
{
//Case for sliders - bump notes down and right, rather than up and left.
sliderStack++;
beatmap.HitObjects[j].StackHeight -= sliderStack;
- startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[i].StartTime;
+ startTime = (beatmap.HitObjects[j] as IHasEndTime)?.EndTime ?? beatmap.HitObjects[j].StartTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index e2a1542574..c197933233 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
- return new OsuDifficultyAttributes { Mods = mods };
+ return new OsuDifficultyAttributes { Mods = mods, Skills = skills };
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
@@ -50,7 +50,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
SpeedStrain = speedRating,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6,
- MaxCombo = maxCombo
+ MaxCombo = maxCombo,
+ Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
index 7f6a60c400..fe11ead94d 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
this.hitCircle = hitCircle;
Origin = Anchor.Centre;
- Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Scale = new Vector2(hitCircle.Scale);
CornerRadius = Size.X / 2;
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
index 957550a051..f1f55731b6 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
InternalChild = body = new ManualSliderBody
{
AccentColour = Color4.Transparent,
- PathRadius = slider.Scale * 64
};
}
@@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
- ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true);
+ ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true);
}
private void updatePosition() => Position = slider.StackedPosition;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
new file mode 100644
index 0000000000..adca95cf8a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
@@ -0,0 +1,20 @@
+// 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.Sprites;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModDeflate : OsuModeObjectScaleTween
+ {
+ public override string Name => "Deflate";
+
+ public override string Acronym => "DF";
+
+ public override IconUsage Icon => FontAwesome.Solid.CompressArrowsAlt;
+
+ public override string Description => "Hit them at the right size!";
+
+ protected override float StartScale => 2f;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index a2da2bbf53..3c81203ad7 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -1,17 +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 System.Collections.Generic;
-using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
-using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- internal class OsuModGrow : Mod, IApplicableToDrawableHitObjects
+ internal class OsuModGrow : OsuModeObjectScaleTween
{
public override string Name => "Grow";
@@ -19,58 +13,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage Icon => FontAwesome.Solid.ArrowsAltV;
- public override ModType Type => ModType.Fun;
-
public override string Description => "Hit them at the right size!";
- public override double ScoreMultiplier => 1;
-
- public void ApplyToDrawableHitObjects(IEnumerable drawables)
- {
- foreach (var drawable in drawables)
- {
- switch (drawable)
- {
- case DrawableSpinner _:
- continue;
-
- default:
- drawable.ApplyCustomUpdateState += ApplyCustomState;
- break;
- }
- }
- }
-
- protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
- {
- var h = (OsuHitObject)drawable.HitObject;
-
- // apply grow effect
- switch (drawable)
- {
- case DrawableSliderHead _:
- case DrawableSliderTail _:
- // special cases we should *not* be scaling.
- break;
-
- case DrawableSlider _:
- case DrawableHitCircle _:
- {
- using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
- drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine);
- break;
- }
- }
-
- // remove approach circles
- switch (drawable)
- {
- case DrawableHitCircle circle:
- // we don't want to see the approach circle
- using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
- circle.ApproachCircle.Hide();
- break;
- }
- }
+ protected override float StartScale => 0.5f;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index ddf708d0f1..2d940479f3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index ec23570f54..5625028707 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -13,13 +13,11 @@ using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset
+ public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset
{
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
- public bool AllowFail => false;
-
public void Update(Playfield playfield)
{
bool requiresHold = false;
@@ -37,11 +35,11 @@ namespace osu.Game.Rulesets.Osu.Mods
if (time < osuHit.HitObject.StartTime - relax_leniency) continue;
- if (osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime || osuHit.IsHit)
+ if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
continue;
requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime);
- requiresHold |= osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered) || osuHit is DrawableSpinner;
+ requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
}
if (requiresHit)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
new file mode 100644
index 0000000000..62b5ecfd58
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.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 osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModSpinIn : Mod, IApplicableToDrawableHitObjects, IReadFromConfig
+ {
+ public override string Name => "Spin In";
+ public override string Acronym => "SI";
+ public override IconUsage Icon => FontAwesome.Solid.Undo;
+ public override ModType Type => ModType.Fun;
+ public override string Description => "Circles spin in. No approach circles.";
+ public override double ScoreMultiplier => 1;
+
+ // todo: this mod should be able to be compatible with hidden with a bit of further implementation.
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
+
+ private const int rotate_offset = 360;
+ private const float rotate_starting_width = 2;
+
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
+ {
+ switch (drawable)
+ {
+ case DrawableSpinner _:
+ continue;
+
+ default:
+ drawable.ApplyCustomUpdateState += applyZoomState;
+ break;
+ }
+ }
+ }
+
+ private void applyZoomState(DrawableHitObject drawable, ArmedState state)
+ {
+ var h = (OsuHitObject)drawable.HitObject;
+
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ {
+ circle.ApproachCircle.Hide();
+
+ circle.RotateTo(rotate_offset).Then().RotateTo(0, h.TimePreempt, Easing.InOutSine);
+ circle.ScaleTo(new Vector2(rotate_starting_width, 0)).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine);
+
+ // bypass fade in.
+ if (state == ArmedState.Idle)
+ circle.FadeIn();
+ }
+
+ break;
+
+ case DrawableSlider slider:
+ using (slider.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
+ {
+ slider.ScaleTo(0).Then().ScaleTo(1, h.TimePreempt, Easing.InOutSine);
+
+ // bypass fade in.
+ if (state == ArmedState.Idle)
+ slider.FadeIn();
+ }
+
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
index 571756d056..f0db548e74 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs
@@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Acronym => "TD";
public override double ScoreMultiplier => 1;
+ public override ModType Type => ModType.System;
+
public override bool Ranked => true;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
new file mode 100644
index 0000000000..e926ade41b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
@@ -0,0 +1,87 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ ///
+ /// Adjusts the size of hit objects during their fade in animation.
+ ///
+ public abstract class OsuModeObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
+ {
+ public override ModType Type => ModType.Fun;
+
+ public override double ScoreMultiplier => 1;
+
+ protected virtual float StartScale => 1;
+
+ protected virtual float EndScale => 1;
+
+ private Bindable increaseFirstObjectVisibility = new Bindable();
+
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
+
+ public void ReadFromConfig(OsuConfigManager config)
+ {
+ increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility);
+ }
+
+ public void ApplyToDrawableHitObjects(IEnumerable drawables)
+ {
+ foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
+ {
+ switch (drawable)
+ {
+ case DrawableSpinner _:
+ continue;
+
+ default:
+ drawable.ApplyCustomUpdateState += ApplyCustomState;
+ break;
+ }
+ }
+ }
+
+ protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state)
+ {
+ var h = (OsuHitObject)drawable.HitObject;
+
+ // apply grow effect
+ switch (drawable)
+ {
+ case DrawableSliderHead _:
+ case DrawableSliderTail _:
+ // special cases we should *not* be scaling.
+ break;
+
+ case DrawableSlider _:
+ case DrawableHitCircle _:
+ {
+ using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
+ drawable.ScaleTo(StartScale).Then().ScaleTo(EndScale, h.TimePreempt, Easing.OutSine);
+ break;
+ }
+ }
+
+ // remove approach circles
+ switch (drawable)
+ {
+ case DrawableHitCircle circle:
+ // we don't want to see the approach circle
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
+ circle.ApproachCircle.Hide();
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
index aacf3ee08d..a2a23e9ff7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Anchor = Anchor.Centre,
Alpha = 0.5f,
}
- }, restrictSize: false);
+ }, confineMode: ConfineMode.NoScaling);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index 7569626230..a269b87c75 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -97,13 +97,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Position = pointStartPosition,
Rotation = rotation,
Alpha = 0,
- Scale = new Vector2(1.5f),
+ Scale = new Vector2(1.5f * currHitObject.Scale),
});
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(currHitObject.TimeFadeIn);
- fp.ScaleTo(1, currHitObject.TimeFadeIn, Easing.Out);
+ fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index fef0bfdc2c..ca124e9214 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -6,34 +6,29 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osuTK;
using osu.Game.Rulesets.Scoring;
-using osuTK.Graphics;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
public ApproachCircle ApproachCircle;
- private readonly CirclePiece circle;
- private readonly RingPiece ring;
- private readonly FlashPiece flash;
- private readonly ExplodePiece explode;
- private readonly NumberPiece number;
- private readonly GlowPiece glow;
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
private readonly IBindable scaleBindable = new Bindable();
- public OsuAction? HitAction => circle.HitAction;
-
- private readonly Container explodeContainer;
+ public OsuAction? HitAction => hitArea.HitAction;
private readonly Container scaleContainer;
+ private readonly HitArea hitArea;
+
public DrawableHitCircle(HitCircle h)
: base(h)
{
@@ -48,44 +43,30 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = explodeContainer = new Container
+ Children = new Drawable[]
{
- RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Children = new Drawable[]
+ hitArea = new HitArea
{
- glow = new GlowPiece(),
- circle = new CirclePiece
+ Hit = () =>
{
- Hit = () =>
- {
- if (AllJudged)
- return false;
+ if (AllJudged)
+ return false;
- UpdateResult(true);
- return true;
- },
+ UpdateResult(true);
+ return true;
},
- number = new NumberPiece
- {
- Text = (HitObject.IndexInCurrentCombo + 1).ToString(),
- },
- ring = new RingPiece(),
- flash = new FlashPiece(),
- explode = new ExplodePiece(),
- ApproachCircle = new ApproachCircle
- {
- Alpha = 0,
- Scale = new Vector2(4),
- }
+ },
+ new SkinnableDrawable("Play/osu/hitcircle", _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
+ ApproachCircle = new ApproachCircle
+ {
+ Alpha = 0,
+ Scale = new Vector2(4),
}
}
},
};
- //may not be so correct
- Size = circle.DrawSize;
+ Size = hitArea.DrawSize;
}
[BackgroundDependencyLoader]
@@ -98,19 +79,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
- }
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
- {
- base.AccentColour = value;
- explode.Colour = AccentColour;
- glow.Colour = AccentColour;
- circle.Colour = AccentColour;
- ApproachCircle.Colour = AccentColour;
- }
+ AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true);
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -134,19 +104,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = result);
}
- protected override void UpdatePreemptState()
+ protected override void UpdateInitialTransforms()
{
- base.UpdatePreemptState();
+ base.UpdateInitialTransforms();
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
- ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt);
+ ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
ApproachCircle.Expire(true);
}
- protected override void UpdateCurrentState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
- glow.FadeOut(400);
-
switch (state)
{
case ArmedState.Idle:
@@ -154,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
- circle.HitAction = null;
+ hitArea.HitAction = null;
// override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early.
LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss);
@@ -169,29 +137,50 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
case ArmedState.Hit:
ApproachCircle.FadeOut(50);
- const double flash_in = 40;
- flash.FadeTo(0.8f, flash_in)
- .Then()
- .FadeOut(100);
-
- explode.FadeIn(flash_in);
-
- using (BeginDelayedSequence(flash_in, true))
- {
- //after the flash, we can hide some elements that were behind it
- ring.FadeOut();
- circle.FadeOut();
- number.FadeOut();
-
- this.FadeOut(800);
- explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad);
- }
-
- Expire();
+ // todo: temporary / arbitrary
+ this.Delay(800).Expire();
break;
}
}
public Drawable ProxiedLayer => ApproachCircle;
+
+ private class HitArea : Drawable, IKeyBindingHandler
+ {
+ // IsHovered is used
+ public override bool HandlePositionalInput => true;
+
+ public Func Hit;
+
+ public OsuAction? HitAction;
+
+ public HitArea()
+ {
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+
+ public bool OnPressed(OsuAction action)
+ {
+ switch (action)
+ {
+ case OsuAction.LeftButton:
+ case OsuAction.RightButton:
+ if (IsHovered && (Hit?.Invoke() ?? false))
+ {
+ HitAction = action;
+ return true;
+ }
+
+ break;
+ }
+
+ return false;
+ }
+
+ public bool OnReleased(OsuAction action) => false;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 10b37af957..a89fb8b682 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -1,15 +1,10 @@
// 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.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Skinning;
-using osuTK.Graphics;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@@ -18,6 +13,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
private readonly ShakeContainer shakeContainer;
+ // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects.
+ public override bool HandlePositionalInput => true;
+
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
@@ -26,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ShakeDuration = 30,
RelativeSizeAxes = Axes.Both
});
+
Alpha = 0;
}
@@ -35,47 +34,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void ClearInternal(bool disposeChildren = true) => shakeContainer.Clear(disposeChildren);
protected override bool RemoveInternal(Drawable drawable) => shakeContainer.Remove(drawable);
- protected sealed override void UpdateState(ArmedState state)
- {
- double transformTime = HitObject.StartTime - HitObject.TimePreempt;
+ protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
- base.ApplyTransformsAt(transformTime, true);
- base.ClearTransformsAfter(transformTime, true);
-
- using (BeginAbsoluteSequence(transformTime, true))
- {
- UpdatePreemptState();
-
- var judgementOffset = Math.Min(HitObject.HitWindows.HalfWindowFor(HitResult.Miss), Result?.TimeOffset ?? 0);
-
- using (BeginDelayedSequence(HitObject.TimePreempt + judgementOffset, true))
- UpdateCurrentState(state);
- }
- }
-
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- base.SkinChanged(skin, allowFallback);
-
- if (HitObject is IHasComboInformation combo)
- AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
- }
-
- protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn);
-
- protected virtual void UpdateCurrentState(ArmedState state)
- {
- }
-
- // Todo: At some point we need to move these to DrawableHitObject after ensuring that all other Rulesets apply
- // transforms in the same way and don't rely on them not being cleared
- public override void ClearTransformsAfter(double time, bool propagateChildren = false, string targetMember = null)
- {
- }
-
- public override void ApplyTransformsAt(double time, bool propagateChildren = false)
- {
- }
+ protected override void UpdateInitialTransforms() => this.FadeIn(HitObject.TimeFadeIn);
private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index cce6dfe106..f75b62eecf 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
@@ -20,34 +22,47 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private double animDuration;
+ private readonly SkinnableDrawable scaleContainer;
+
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
: base(repeatPoint)
{
this.repeatPoint = repeatPoint;
this.drawableSlider = drawableSlider;
- Size = new Vector2(45 * repeatPoint.Scale);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Blending = BlendingMode.Additive;
Origin = Anchor.Centre;
- InternalChildren = new Drawable[]
+ InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon
{
- new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon
- {
- RelativeSizeAxes = Axes.Both,
- Icon = FontAwesome.Solid.ChevronRight
- }, restrictSize: false)
+ RelativeSizeAxes = Axes.Both,
+ Icon = FontAwesome.Solid.ChevronRight,
+ Size = new Vector2(0.35f)
+ }, confineMode: ConfineMode.NoScaling)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
};
}
+ private readonly IBindable scaleBindable = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
+ scaleBindable.BindTo(HitObject.ScaleBindable);
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (repeatPoint.StartTime <= Time.Current)
ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss);
}
- protected override void UpdatePreemptState()
+ protected override void UpdateInitialTransforms()
{
animDuration = Math.Min(150, repeatPoint.SpanDuration / 2);
@@ -57,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
);
}
- protected override void UpdateCurrentState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 05cb42d853..a0626707af 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -48,10 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
- Body = new SnakingSliderBody(s)
- {
- PathRadius = s.Scale * 64,
- },
+ Body = new SnakingSliderBody(s),
ticks = new Container { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@@ -105,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale =>
{
- Body.PathRadius = scale.NewValue * 64;
+ updatePathRadius();
Ball.Scale = new Vector2(scale.NewValue);
});
@@ -114,20 +111,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
pathBindable.BindTo(slider.PathBindable);
pathBindable.BindValueChanged(_ => Body.Refresh());
- }
- public override Color4 AccentColour
- {
- get => base.AccentColour;
- set
+ AccentColour.BindValueChanged(colour =>
{
- base.AccentColour = value;
- Body.AccentColour = AccentColour;
- Ball.AccentColour = AccentColour;
+ Body.AccentColour = colour.NewValue;
foreach (var drawableHitObject in NestedHitObjects)
- drawableHitObject.AccentColour = AccentColour;
- }
+ drawableHitObject.AccentColour.Value = colour.NewValue;
+ }, true);
}
public readonly Bindable Tracking = new Bindable();
@@ -156,16 +147,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ public override void OnKilled()
+ {
+ base.OnKilled();
+ Body.RecyclePath();
+ }
+
+ private float sliderPathRadius;
+
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE;
- Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour;
+ sliderPathRadius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+ updatePathRadius();
+
+ Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour.Value;
Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White;
- Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour;
}
+ private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < slider.EndTime)
@@ -189,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
});
}
- protected override void UpdateCurrentState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
Ball.FadeIn();
Ball.ScaleTo(HitObject.Scale);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 72b648bfd0..653e73ac3f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -1,6 +1,8 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
@@ -16,51 +18,62 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public const double ANIM_DURATION = 150;
+ private const float default_tick_size = 16;
+
public bool Tracking { get; set; }
public override bool DisplayResult => false;
+ private readonly SkinnableDrawable scaleContainer;
+
public DrawableSliderTick(SliderTick sliderTick)
: base(sliderTick)
{
- Size = new Vector2(16) * sliderTick.Scale;
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Origin = Anchor.Centre;
- InternalChildren = new Drawable[]
+ InternalChild = scaleContainer = new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new CircularContainer
{
- new SkinnableDrawable("Play/osu/sliderscorepoint", _ => new Container
+ Masking = true,
+ Origin = Anchor.Centre,
+ Size = new Vector2(default_tick_size),
+ BorderThickness = default_tick_size / 4,
+ BorderColour = Color4.White,
+ Child = new Box
{
- Masking = true,
RelativeSizeAxes = Axes.Both,
- Origin = Anchor.Centre,
- CornerRadius = Size.X / 2,
-
- BorderThickness = 2,
- BorderColour = Color4.White,
-
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = AccentColour,
- Alpha = 0.3f,
- }
- }, restrictSize: false)
+ Colour = AccentColour.Value,
+ Alpha = 0.3f,
+ }
+ })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
};
}
+ private readonly IBindable scaleBindable = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true);
+ scaleBindable.BindTo(HitObject.ScaleBindable);
+ }
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (timeOffset >= 0)
ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss);
}
- protected override void UpdatePreemptState()
+ protected override void UpdateInitialTransforms()
{
this.FadeOut().FadeIn(ANIM_DURATION);
this.ScaleTo(0.5f).ScaleTo(1f, ANIM_DURATION * 4, Easing.OutElasticHalf);
}
- protected override void UpdateCurrentState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
switch (state)
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 1794da54b7..a0bd301fdb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -196,9 +196,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint);
}
- protected override void UpdatePreemptState()
+ protected override void UpdateInitialTransforms()
{
- base.UpdatePreemptState();
+ base.UpdateInitialTransforms();
circleContainer.ScaleTo(Spinner.Scale * 0.3f);
circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
@@ -213,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
.ScaleTo(1, 500, Easing.OutQuint);
}
- protected override void UpdateCurrentState(ArmedState state)
+ protected override void UpdateStateTransforms(ArmedState state)
{
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 8ee065aaea..5813197336 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -4,9 +4,9 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -25,7 +25,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) });
+ Child = new SkinnableApproachCircle();
+ }
+
+ private class SkinnableApproachCircle : SkinnableSprite
+ {
+ public SkinnableApproachCircle()
+ : base("Play/osu/approachcircle")
+ {
+ }
+
+ protected override Drawable CreateDefault(string name)
+ {
+ var drawable = base.CreateDefault(name);
+
+ // account for the sprite being used for the default approach circle being taken from stable,
+ // when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
+ drawable.Scale = new Vector2(128 / 118f);
+
+ return drawable;
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index 786cac7198..c92937ef09 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -1,54 +1,45 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Input.Bindings;
-using osu.Game.Skinning;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
- public class CirclePiece : Container, IKeyBindingHandler
+ public class CirclePiece : CompositeDrawable
{
- // IsHovered is used
- public override bool HandlePositionalInput => true;
-
- public Func Hit;
-
- public OsuAction? HitAction;
-
public CirclePiece()
{
- Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Masking = true;
CornerRadius = Size.X / 2;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
-
- InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece());
}
- public bool OnPressed(OsuAction action)
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
{
- switch (action)
+ InternalChildren = new Drawable[]
{
- case OsuAction.LeftButton:
- case OsuAction.RightButton:
- if (IsHovered && (Hit?.Invoke() ?? false))
- {
- HitAction = action;
- return true;
- }
-
- break;
- }
-
- return false;
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = textures.Get(@"Play/osu/disc"),
+ },
+ new TrianglesPiece
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingMode.Additive,
+ Alpha = 0.5f,
+ }
+ };
}
-
- public bool OnReleased(OsuAction action) => false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
deleted file mode 100644
index 047ff943ff..0000000000
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
+++ /dev/null
@@ -1,35 +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.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
-
-namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
-{
- public class DefaultCirclePiece : Container
- {
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- RelativeSizeAxes = Axes.Both;
- Children = new Drawable[]
- {
- new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = textures.Get(@"Play/osu/disc"),
- },
- new TrianglesPiece
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Alpha = 0.5f,
- }
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
index b960f40578..8ff16f8b84 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public ExplodePiece()
{
- Size = new Vector2(128);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
index 8e5eb886aa..c22073f56c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.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 osu.Framework.Graphics;
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public FlashPiece()
{
- Size = new Vector2(128);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
new file mode 100644
index 0000000000..944c93bb6d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/MainCirclePiece.cs
@@ -0,0 +1,94 @@
+// 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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public class MainCirclePiece : CompositeDrawable
+ {
+ private readonly CirclePiece circle;
+ private readonly RingPiece ring;
+ private readonly FlashPiece flash;
+ private readonly ExplodePiece explode;
+ private readonly NumberPiece number;
+ private readonly GlowPiece glow;
+
+ public MainCirclePiece(int index)
+ {
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ glow = new GlowPiece(),
+ circle = new CirclePiece(),
+ number = new NumberPiece
+ {
+ Text = (index + 1).ToString(),
+ },
+ ring = new RingPiece(),
+ flash = new FlashPiece(),
+ explode = new ExplodePiece(),
+ };
+ }
+
+ private readonly IBindable state = new Bindable();
+
+ private readonly Bindable accentColour = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject)
+ {
+ state.BindTo(drawableObject.State);
+ state.BindValueChanged(updateState, true);
+
+ accentColour.BindTo(drawableObject.AccentColour);
+ accentColour.BindValueChanged(colour =>
+ {
+ explode.Colour = colour.NewValue;
+ glow.Colour = colour.NewValue;
+ circle.Colour = colour.NewValue;
+ }, true);
+ }
+
+ private void updateState(ValueChangedEvent state)
+ {
+ glow.FadeOut(400);
+
+ switch (state.NewValue)
+ {
+ case ArmedState.Hit:
+ const double flash_in = 40;
+ const double flash_out = 100;
+
+ flash.FadeTo(0.8f, flash_in)
+ .Then()
+ .FadeOut(flash_out);
+
+ explode.FadeIn(flash_in);
+ this.ScaleTo(1.5f, 400, Easing.OutQuad);
+
+ using (BeginDelayedSequence(flash_in, true))
+ {
+ //after the flash, we can hide some elements that were behind it
+ ring.FadeOut();
+ circle.FadeOut();
+ number.FadeOut();
+
+ this.FadeOut(800);
+ }
+
+ break;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index 84034d3ee9..e8dc63abca 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
- }, restrictSize: false)
+ }, confineMode: ConfineMode.NoScaling)
{
Text = @"1"
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
index 28180a7f71..575f2c92c5 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.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 osu.Framework.Graphics;
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public RingPiece()
{
- Size = new Vector2(128);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 7d1d77ae96..8b72b23ca3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -3,11 +3,13 @@
using System;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Events;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK.Graphics;
using osu.Game.Skinning;
@@ -17,90 +19,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
{
- private const float width = 128;
-
- private Color4 accentColour = Color4.Black;
-
public Func GetInitialHitAction;
- ///
- /// The colour that is used for the slider ball.
- ///
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- accentColour = value;
- if (drawableBall != null)
- drawableBall.Colour = value;
- }
- }
-
private readonly Slider slider;
public readonly Drawable FollowCircle;
- private Drawable drawableBall;
private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
{
this.drawableSlider = drawableSlider;
this.slider = slider;
- Masking = true;
- AutoSizeAxes = Axes.Both;
+
Blending = BlendingMode.Additive;
Origin = Anchor.Centre;
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
Children = new[]
{
- FollowCircle = new Container
+ FollowCircle = new FollowCircleContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Width = width,
- Height = width,
+ RelativeSizeAxes = Axes.Both,
Alpha = 0,
- Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new CircularContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = 5,
- BorderColour = Color4.Orange,
- Blending = BlendingMode.Additive,
- Child = new Box
- {
- Colour = Color4.Orange,
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.2f,
- }
- }),
+ Child = new SkinnableDrawable("Play/osu/sliderfollowcircle", _ => new DefaultFollowCircle()),
},
new CircularContainer
{
Masking = true,
- AutoSizeAxes = Axes.Both,
+ RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 1,
Child = new Container
{
- Width = width,
- Height = width,
+ RelativeSizeAxes = Axes.Both,
// TODO: support skin filename animation (sliderb0, sliderb1...)
- Child = new SkinnableDrawable("Play/osu/sliderb", _ => new CircularContainer
- {
- Masking = true,
- RelativeSizeAxes = Axes.Both,
- BorderThickness = 10,
- BorderColour = Color4.White,
- Alpha = 1,
- Child = drawableBall = new Box
- {
- Colour = AccentColour,
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.4f,
- }
- }),
+ Child = new SkinnableDrawable("Play/osu/sliderball", _ => new DefaultSliderBall()),
}
}
};
@@ -193,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
- lastScreenSpaceMousePosition.HasValue && base.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
+ lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
}
@@ -216,5 +172,62 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Position = slider.CurvePositionAt(completionProgress);
}
+
+ private class FollowCircleContainer : Container
+ {
+ public override bool HandlePositionalInput => true;
+ }
+
+ public class DefaultFollowCircle : CompositeDrawable
+ {
+ public DefaultFollowCircle()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = 5,
+ BorderColour = Color4.Orange,
+ Blending = BlendingMode.Additive,
+ Child = new Box
+ {
+ Colour = Color4.Orange,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.2f,
+ }
+ };
+ }
+ }
+
+ public class DefaultSliderBall : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject, ISkinSource skin)
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ float radius = skin.GetValue(s => s.SliderPathRadius) ?? OsuHitObject.OBJECT_RADIUS;
+
+ InternalChild = new CircularContainer
+ {
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(radius / OsuHitObject.OBJECT_RADIUS),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BorderThickness = 10,
+ BorderColour = Color4.White,
+ Alpha = 1,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.White,
+ Alpha = 0.4f,
+ }
+ };
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 25e1aebd18..24a437c20e 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -1,14 +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 System;
using System.Collections.Generic;
-using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
-using osu.Framework.Graphics.Primitives;
using osuTK;
using osuTK.Graphics;
-using osuTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -16,12 +14,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public const float DEFAULT_BORDER_SIZE = 1;
- private readonly SliderPath path;
+ private SliderPath path;
+
protected Path Path => path;
- private readonly BufferedContainer container;
-
- public float PathRadius
+ public virtual float PathRadius
{
get => path.PathRadius;
set => path.PathRadius = value;
@@ -44,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
path.AccentColour = value;
-
- container.ForceRedraw();
}
}
@@ -61,8 +56,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
path.BorderColour = value;
-
- container.ForceRedraw();
}
}
@@ -78,23 +71,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
path.BorderSize = value;
-
- container.ForceRedraw();
}
}
- public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
-
protected SliderBody()
{
- InternalChild = container = new BufferedContainer
- {
- RelativeSizeAxes = Axes.Both,
- CacheDrawnFrameBuffer = true,
- Child = path = new SliderPath { Blending = BlendingMode.None }
- };
+ RecyclePath();
+ }
- container.Attach(RenderbufferInternalFormat.DepthComponent16);
+ ///
+ /// Initialises a new , releasing all resources retained by the old one.
+ ///
+ public virtual void RecyclePath()
+ {
+ InternalChild = path = new SliderPath
+ {
+ Position = path?.Position ?? Vector2.Zero,
+ PathRadius = path?.PathRadius ?? 10,
+ AccentColour = path?.AccentColour ?? Color4.White,
+ BorderColour = path?.BorderColour ?? Color4.White,
+ BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
+ Vertices = path?.Vertices ?? Array.Empty()
+ };
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
@@ -103,11 +101,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// Sets the vertices of the path which should be drawn by this .
///
/// The vertices
- protected void SetVertices(IReadOnlyList vertices)
- {
- path.Vertices = vertices;
- container.ForceRedraw();
- }
+ protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices;
private class SliderPath : SmoothPath
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index 73b184bffe..70a1bad4a3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -23,6 +24,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
+ public override float PathRadius
+ {
+ get => base.PathRadius;
+ set
+ {
+ if (base.PathRadius == value)
+ return;
+
+ base.PathRadius = value;
+
+ Refresh();
+ }
+ }
+
public override Vector2 PathOffset => snakedPathOffset;
///
@@ -54,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var spanProgress = slider.ProgressAt(completionProgress);
double start = 0;
- double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1;
+ double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / (slider.TimePreempt / 3), 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
@@ -78,9 +93,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
slider.Path.GetPathToProgress(CurrentCurve, 0, 1);
SetVertices(CurrentCurve);
- // The body is sized to the full path size to avoid excessive autosize computations
+ // Force the body to be the final path size to avoid excessive autosize computations
+ Path.AutoSizeAxes = Axes.Both;
Size = Path.Size;
+ updatePathSize();
+
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
@@ -93,6 +111,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
setRange(lastSnakedStart, lastSnakedEnd);
}
+ public override void RecyclePath()
+ {
+ base.RecyclePath();
+ updatePathSize();
+ }
+
+ private void updatePathSize()
+ {
+ // Force the path to its final size to avoid excessive framebuffer resizes
+ Path.AutoSizeAxes = Axes.None;
+ Path.Size = Size;
+ }
+
private void setRange(double p0, double p1)
{
if (p0 > p1)
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 364c182dd4..b52bfcd181 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
- public const double OBJECT_RADIUS = 64;
+ public const float OBJECT_RADIUS = 64;
public double TimePreempt = 600;
public double TimeFadeIn = 400;
@@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
- TimeFadeIn = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1200, 800, 300);
+ TimeFadeIn = 400; // as per osu-stable
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index a8aec005d1..d3279652c7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
- private Cached endPositionCache;
+ private readonly Cached endPositionCache = new Cached();
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
@@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects
///
internal float LazyTravelDistance;
- public List> NodeSamples { get; set; } = new List>();
+ public List> NodeSamples { get; set; } = new List>();
private int repeatCount;
@@ -157,12 +157,12 @@ namespace osu.Game.Rulesets.Osu.Objects
foreach (var e in
SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
{
- var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL)
+ var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL)
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
- var sampleList = new List();
+ var sampleList = new List();
if (firstSample != null)
- sampleList.Add(new SampleInfo
+ sampleList.Add(new HitSampleInfo
{
Bank = firstSample.Bank,
Volume = firstSample.Volume,
@@ -225,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
}
- private List getNodeSamples(int nodeIndex) =>
+ private List getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples;
public override Judgement CreateJudgement() => new OsuJudgement();
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index 83d29c156d..d50d4f401c 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -14,7 +14,6 @@ using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
@@ -134,8 +133,15 @@ namespace osu.Game.Rulesets.Osu
{
new OsuModTransform(),
new OsuModWiggle(),
- new OsuModGrow(),
- new MultiMod(new ModWindUp(), new ModWindDown()),
+ new OsuModSpinIn(),
+ new MultiMod(new OsuModGrow(), new OsuModDeflate()),
+ new MultiMod(new ModWindUp(), new ModWindDown()),
+ };
+
+ case ModType.System:
+ return new Mod[]
+ {
+ new OsuModTouchDevice(),
};
default:
diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
index b9a7096330..c842874635 100644
--- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
@@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.iOS")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Android")]
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json
new file mode 100644
index 0000000000..b994cbd85a
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking-expected-conversion.json
@@ -0,0 +1,278 @@
+{
+ "Mappings": [{
+ "StartTime": 32165,
+ "Objects": [{
+ "StartTime": 32165,
+ "EndTime": 32165,
+ "X": 32,
+ "Y": 320
+ }]
+ },
+ {
+ "StartTime": 32517,
+ "Objects": [{
+ "StartTime": 32517,
+ "EndTime": 32517,
+ "X": 246.396057,
+ "Y": 182.396057
+ }]
+ },
+ {
+ "StartTime": 32605,
+ "Objects": [{
+ "StartTime": 32605,
+ "EndTime": 32605,
+ "X": 249.597382,
+ "Y": 185.597382
+ }]
+ },
+ {
+ "StartTime": 32693,
+ "Objects": [{
+ "StartTime": 32693,
+ "EndTime": 32693,
+ "X": 252.798691,
+ "Y": 188.798691
+ }]
+ },
+ {
+ "StartTime": 32781,
+ "Objects": [{
+ "StartTime": 32781,
+ "EndTime": 32781,
+ "X": 256,
+ "Y": 192
+ }]
+ },
+ {
+ "StartTime": 33248,
+ "Objects": [{
+ "StartTime": 33248,
+ "EndTime": 33248,
+ "X": 39.3960648,
+ "Y": 76.3960648
+ }]
+ },
+ {
+ "StartTime": 33307,
+ "Objects": [{
+ "StartTime": 33307,
+ "EndTime": 33307,
+ "X": 42.5973778,
+ "Y": 79.597374
+ }]
+ },
+ {
+ "StartTime": 33383,
+ "Objects": [{
+ "StartTime": 33383,
+ "EndTime": 33383,
+ "X": 45.798687,
+ "Y": 82.79869
+ }]
+ },
+ {
+ "StartTime": 33459,
+ "Objects": [{
+ "StartTime": 33459,
+ "EndTime": 33459,
+ "X": 49,
+ "Y": 86
+ },
+ {
+ "StartTime": 33635,
+ "EndTime": 33635,
+ "X": 123.847847,
+ "Y": 85.7988
+ },
+ {
+ "StartTime": 33811,
+ "EndTime": 33811,
+ "X": 198.6957,
+ "Y": 85.5975952
+ },
+ {
+ "StartTime": 33988,
+ "EndTime": 33988,
+ "X": 273.9688,
+ "Y": 85.39525
+ },
+ {
+ "StartTime": 34164,
+ "EndTime": 34164,
+ "X": 348.816681,
+ "Y": 85.19404
+ },
+ {
+ "StartTime": 34246,
+ "EndTime": 34246,
+ "X": 398.998718,
+ "Y": 85.05914
+ }
+ ]
+ },
+ {
+ "StartTime": 34341,
+ "Objects": [{
+ "StartTime": 34341,
+ "EndTime": 34341,
+ "X": 401.201324,
+ "Y": 88.20131
+ }]
+ },
+ {
+ "StartTime": 34400,
+ "Objects": [{
+ "StartTime": 34400,
+ "EndTime": 34400,
+ "X": 404.402618,
+ "Y": 91.402626
+ }]
+ },
+ {
+ "StartTime": 34459,
+ "Objects": [{
+ "StartTime": 34459,
+ "EndTime": 34459,
+ "X": 407.603943,
+ "Y": 94.6039352
+ }]
+ },
+ {
+ "StartTime": 34989,
+ "Objects": [{
+ "StartTime": 34989,
+ "EndTime": 34989,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35018,
+ "EndTime": 35018,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35106,
+ "Objects": [{
+ "StartTime": 35106,
+ "EndTime": 35106,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35135,
+ "EndTime": 35135,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35224,
+ "Objects": [{
+ "StartTime": 35224,
+ "EndTime": 35224,
+ "X": 163,
+ "Y": 138
+ },
+ {
+ "StartTime": 35253,
+ "EndTime": 35253,
+ "X": 188,
+ "Y": 138
+ }
+ ]
+ },
+ {
+ "StartTime": 35695,
+ "Objects": [{
+ "StartTime": 35695,
+ "EndTime": 35695,
+ "X": 166,
+ "Y": 76
+ },
+ {
+ "StartTime": 35871,
+ "EndTime": 35871,
+ "X": 240.99855,
+ "Y": 75.53417
+ },
+ {
+ "StartTime": 36011,
+ "EndTime": 36011,
+ "X": 315.9971,
+ "Y": 75.0683441
+ }
+ ]
+ },
+ {
+ "StartTime": 36106,
+ "Objects": [{
+ "StartTime": 36106,
+ "EndTime": 36106,
+ "X": 315,
+ "Y": 75
+ },
+ {
+ "StartTime": 36282,
+ "EndTime": 36282,
+ "X": 240.001526,
+ "Y": 75.47769
+ },
+ {
+ "StartTime": 36422,
+ "EndTime": 36422,
+ "X": 165.003052,
+ "Y": 75.95539
+ }
+ ]
+ },
+ {
+ "StartTime": 36518,
+ "Objects": [{
+ "StartTime": 36518,
+ "EndTime": 36518,
+ "X": 166,
+ "Y": 76
+ },
+ {
+ "StartTime": 36694,
+ "EndTime": 36694,
+ "X": 240.99855,
+ "Y": 75.53417
+ },
+ {
+ "StartTime": 36834,
+ "EndTime": 36834,
+ "X": 315.9971,
+ "Y": 75.0683441
+ }
+ ]
+ },
+ {
+ "StartTime": 36929,
+ "Objects": [{
+ "StartTime": 36929,
+ "EndTime": 36929,
+ "X": 315,
+ "Y": 75
+ },
+ {
+ "StartTime": 37105,
+ "EndTime": 37105,
+ "X": 240.001526,
+ "Y": 75.47769
+ },
+ {
+ "StartTime": 37245,
+ "EndTime": 37245,
+ "X": 165.003052,
+ "Y": 75.95539
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu
new file mode 100644
index 0000000000..4bc9226d67
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/old-stacking.osu
@@ -0,0 +1,40 @@
+osu file format v3
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:5
+OverallDifficulty:8
+ApproachRate:8
+SliderMultiplier:1.5
+SliderTickRate:2
+
+[TimingPoints]
+48,352.941176470588,4,1,1,100,1,0
+
+[HitObjects]
+// Hit circles
+32,320,32165,5,2,0:0:0:0:
+256,192,32517,5,0,0:0:0:0:
+256,192,32605,1,0,0:0:0:0:
+256,192,32693,1,0,0:0:0:0:
+256,192,32781,1,0,0:0:0:0:
+
+// Hit circles on slider endpoints
+49,86,33248,1,0,0:0:0:0:
+49,86,33307,1,0,0:0:0:0:
+49,86,33383,1,0,0:0:0:0:
+49,86,33459,2,0,L|421:85,1,350
+398,85,34341,1,0,0:0:0:0:
+398,85,34400,1,0,0:0:0:0:
+398,85,34459,1,0,0:0:0:0:
+
+// Sliders
+163,138,34989,2,0,L|196:138,1,25
+163,138,35106,2,0,L|196:138,1,25
+163,138,35224,2,0,L|196:138,1,25
+
+// Reversed sliders
+166,76,35695,2,0,L|327:75,1,150
+315,75,36106,2,0,L|158:76,1,150
+166,76,36518,2,0,L|327:75,1,150
+315,75,36929,2,0,L|158:76,1,150
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..fb4050963f
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider-expected-conversion.json
@@ -0,0 +1,222 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "EndTime": 369,
+ "X": 177,
+ "Y": 191
+ },
+ {
+ "StartTime": 450,
+ "EndTime": 450,
+ "X": 216.539276,
+ "Y": 191.192871
+ },
+ {
+ "StartTime": 532,
+ "EndTime": 532,
+ "X": 256.5667,
+ "Y": 191.388138
+ },
+ {
+ "StartTime": 614,
+ "EndTime": 614,
+ "X": 296.594116,
+ "Y": 191.583389
+ },
+ {
+ "StartTime": 696,
+ "EndTime": 696,
+ "X": 336.621521,
+ "Y": 191.778641
+ },
+ {
+ "StartTime": 778,
+ "EndTime": 778,
+ "X": 376.648926,
+ "Y": 191.9739
+ },
+ {
+ "StartTime": 860,
+ "EndTime": 860,
+ "X": 337.318878,
+ "Y": 191.782043
+ },
+ {
+ "StartTime": 942,
+ "EndTime": 942,
+ "X": 297.291443,
+ "Y": 191.586792
+ },
+ {
+ "StartTime": 1024,
+ "EndTime": 1024,
+ "X": 257.264038,
+ "Y": 191.391541
+ },
+ {
+ "StartTime": 1106,
+ "EndTime": 1106,
+ "X": 217.2366,
+ "Y": 191.196274
+ },
+ {
+ "StartTime": 1188,
+ "EndTime": 1188,
+ "X": 177.209213,
+ "Y": 191.001022
+ },
+ {
+ "StartTime": 1270,
+ "EndTime": 1270,
+ "X": 216.818192,
+ "Y": 191.194229
+ },
+ {
+ "StartTime": 1352,
+ "EndTime": 1352,
+ "X": 256.8456,
+ "Y": 191.3895
+ },
+ {
+ "StartTime": 1434,
+ "EndTime": 1434,
+ "X": 296.873047,
+ "Y": 191.584747
+ },
+ {
+ "StartTime": 1516,
+ "EndTime": 1516,
+ "X": 336.900452,
+ "Y": 191.78
+ },
+ {
+ "StartTime": 1598,
+ "EndTime": 1598,
+ "X": 376.927917,
+ "Y": 191.975266
+ },
+ {
+ "StartTime": 1680,
+ "EndTime": 1680,
+ "X": 337.039948,
+ "Y": 191.780685
+ },
+ {
+ "StartTime": 1762,
+ "EndTime": 1762,
+ "X": 297.0125,
+ "Y": 191.585434
+ },
+ {
+ "StartTime": 1844,
+ "EndTime": 1844,
+ "X": 256.9851,
+ "Y": 191.390167
+ },
+ {
+ "StartTime": 1926,
+ "EndTime": 1926,
+ "X": 216.957672,
+ "Y": 191.194916
+ },
+ {
+ "StartTime": 2008,
+ "EndTime": 2008,
+ "X": 177.069717,
+ "Y": 191.000336
+ },
+ {
+ "StartTime": 2090,
+ "EndTime": 2090,
+ "X": 217.097137,
+ "Y": 191.1956
+ },
+ {
+ "StartTime": 2172,
+ "EndTime": 2172,
+ "X": 257.124573,
+ "Y": 191.390854
+ },
+ {
+ "StartTime": 2254,
+ "EndTime": 2254,
+ "X": 297.152,
+ "Y": 191.5861
+ },
+ {
+ "StartTime": 2336,
+ "EndTime": 2336,
+ "X": 337.179443,
+ "Y": 191.781372
+ },
+ {
+ "StartTime": 2418,
+ "EndTime": 2418,
+ "X": 376.7884,
+ "Y": 191.974579
+ },
+ {
+ "StartTime": 2500,
+ "EndTime": 2500,
+ "X": 336.760956,
+ "Y": 191.779327
+ },
+ {
+ "StartTime": 2582,
+ "EndTime": 2582,
+ "X": 296.733643,
+ "Y": 191.584076
+ },
+ {
+ "StartTime": 2664,
+ "EndTime": 2664,
+ "X": 256.7062,
+ "Y": 191.388809
+ },
+ {
+ "StartTime": 2746,
+ "EndTime": 2746,
+ "X": 216.678772,
+ "Y": 191.193558
+ },
+ {
+ "StartTime": 2828,
+ "EndTime": 2828,
+ "X": 177.348663,
+ "Y": 191.0017
+ },
+ {
+ "StartTime": 2909,
+ "EndTime": 2909,
+ "X": 216.887909,
+ "Y": 191.19458
+ },
+ {
+ "StartTime": 2991,
+ "EndTime": 2991,
+ "X": 256.915344,
+ "Y": 191.389832
+ },
+ {
+ "StartTime": 3073,
+ "EndTime": 3073,
+ "X": 296.942749,
+ "Y": 191.585083
+ },
+ {
+ "StartTime": 3155,
+ "EndTime": 3155,
+ "X": 336.970184,
+ "Y": 191.78035
+ },
+ {
+ "StartTime": 3201,
+ "EndTime": 3201,
+ "X": 376.99762,
+ "Y": 191.9756
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu
new file mode 100644
index 0000000000..624d905384
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/repeat-slider.osu
@@ -0,0 +1,18 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+177,191,369,6,0,L|382:192,7,200
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json
new file mode 100644
index 0000000000..12d1645c04
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider-expected-conversion.json
@@ -0,0 +1,348 @@
+{
+ "Mappings": [{
+ "StartTime": 369,
+ "Objects": [{
+ "StartTime": 369,
+ "EndTime": 369,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 450,
+ "EndTime": 450,
+ "X": 166.53389,
+ "Y": 193.8691
+ },
+ {
+ "StartTime": 532,
+ "EndTime": 532,
+ "X": 206.555847,
+ "Y": 193.736572
+ },
+ {
+ "StartTime": 614,
+ "EndTime": 614,
+ "X": 246.57782,
+ "Y": 193.60405
+ },
+ {
+ "StartTime": 696,
+ "EndTime": 696,
+ "X": 286.5998,
+ "Y": 193.471527
+ },
+ {
+ "StartTime": 778,
+ "EndTime": 778,
+ "X": 326.621765,
+ "Y": 193.339
+ },
+ {
+ "StartTime": 860,
+ "EndTime": 860,
+ "X": 366.6437,
+ "Y": 193.206482
+ },
+ {
+ "StartTime": 942,
+ "EndTime": 942,
+ "X": 406.66568,
+ "Y": 193.073959
+ },
+ {
+ "StartTime": 970,
+ "EndTime": 970,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 997,
+ "EndTime": 997,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 1079,
+ "EndTime": 1079,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 1161,
+ "EndTime": 1161,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 1243,
+ "EndTime": 1243,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 1325,
+ "EndTime": 1325,
+ "X": 247.0659,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 1407,
+ "EndTime": 1407,
+ "X": 207.043915,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 1489,
+ "EndTime": 1489,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 1571,
+ "EndTime": 1571,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 1653,
+ "EndTime": 1653,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 1735,
+ "EndTime": 1735,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 1817,
+ "EndTime": 1817,
+ "X": 247.065887,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 1899,
+ "EndTime": 1899,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 1981,
+ "EndTime": 1981,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 2062,
+ "EndTime": 2062,
+ "X": 366.643738,
+ "Y": 193.206482
+ },
+ {
+ "StartTime": 2144,
+ "EndTime": 2144,
+ "X": 406.665649,
+ "Y": 193.073959
+ },
+ {
+ "StartTime": 2172,
+ "EndTime": 2172,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 2199,
+ "EndTime": 2199,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 2281,
+ "EndTime": 2281,
+ "X": 367.1318,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 2363,
+ "EndTime": 2363,
+ "X": 327.1098,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 2445,
+ "EndTime": 2445,
+ "X": 287.08783,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 2527,
+ "EndTime": 2527,
+ "X": 247.065887,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 2609,
+ "EndTime": 2609,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 2691,
+ "EndTime": 2691,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 2773,
+ "EndTime": 2773,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 2855,
+ "EndTime": 2855,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 2937,
+ "EndTime": 2937,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 3019,
+ "EndTime": 3019,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 3101,
+ "EndTime": 3101,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 3183,
+ "EndTime": 3183,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 3265,
+ "EndTime": 3265,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 3347,
+ "EndTime": 3347,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 3374,
+ "EndTime": 3374,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 3401,
+ "EndTime": 3401,
+ "X": 407.153748,
+ "Y": 193.072342
+ },
+ {
+ "StartTime": 3483,
+ "EndTime": 3483,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 3565,
+ "EndTime": 3565,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 3647,
+ "EndTime": 3647,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 3729,
+ "EndTime": 3729,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 3811,
+ "EndTime": 3811,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 3893,
+ "EndTime": 3893,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 3975,
+ "EndTime": 3975,
+ "X": 127,
+ "Y": 194
+ },
+ {
+ "StartTime": 4057,
+ "EndTime": 4057,
+ "X": 167.021988,
+ "Y": 193.867477
+ },
+ {
+ "StartTime": 4139,
+ "EndTime": 4139,
+ "X": 207.043976,
+ "Y": 193.734955
+ },
+ {
+ "StartTime": 4221,
+ "EndTime": 4221,
+ "X": 247.065948,
+ "Y": 193.602432
+ },
+ {
+ "StartTime": 4303,
+ "EndTime": 4303,
+ "X": 287.087952,
+ "Y": 193.46991
+ },
+ {
+ "StartTime": 4385,
+ "EndTime": 4385,
+ "X": 327.109772,
+ "Y": 193.337387
+ },
+ {
+ "StartTime": 4467,
+ "EndTime": 4467,
+ "X": 367.131775,
+ "Y": 193.204865
+ },
+ {
+ "StartTime": 4540,
+ "EndTime": 4540,
+ "X": 420.331726,
+ "Y": 193.0287
+ },
+ {
+ "StartTime": 4549,
+ "EndTime": 4549,
+ "X": 407.153748,
+ "Y": 193.072342
+ }
+ ]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu
new file mode 100644
index 0000000000..64aeeb0c07
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/uneven-repeat-slider.osu
@@ -0,0 +1,19 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.4
+Mode: 0
+
+[Difficulty]
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8
+SliderMultiplier:1.6
+SliderTickRate:4
+
+[TimingPoints]
+369,327.868852459016,4,2,2,32,1,0
+
+[HitObjects]
+// A slider with an un-even amount of ticks
+127,194,369,6,0,L|429:193,7,293.333333333333
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu
new file mode 100644
index 0000000000..18736043b5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/zero-length-sliders.osu
@@ -0,0 +1,28 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:3
+CircleSize:3
+OverallDifficulty:3
+ApproachRate:4.5
+SliderMultiplier:0.799999999999999
+SliderTickRate:1
+
+[TimingPoints]
+800,260.869565217391,3,2,10,60,1,0
+
+[HitObjects]
+// Linear
+78,193,2365,2,0,L|330:193,1,0
+78,193,3669,2,0,L|330:193,1,0
+78,193,4973,2,0,L|330:193,1,0
+
+// Perfect-curve
+151,206,6278,2,0,P|293:75|345:204,1,0
+151,206,8104,2,0,P|293:75|345:204,1,0
+151,206,9930,2,0,P|293:75|345:204,1,0
+
+// Bezier
+76,191,11756,2,0,B|176:59|358:340|438:190,1,0
+76,191,13582,2,0,B|176:59|358:340|438:190,1,0
+76,191,15408,2,0,B|176:59|358:340|438:190,1,0
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 1b8fa0de01..05eb0ffdbf 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -6,7 +6,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.OpenGL.Buffers;
+using osu.Framework.Graphics.Batches;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shaders;
@@ -54,8 +54,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
for (int i = 0; i < max_sprites; i++)
{
- parts[i].InvalidationID = 0;
- parts[i].WasUpdated = true;
+ // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node
+ // This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms
+ parts[i].InvalidationID = 1;
}
}
@@ -147,7 +148,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public Vector2 Position;
public float Time;
public long InvalidationID;
- public bool WasUpdated;
}
private class TrailDrawNode : DrawNode
@@ -162,16 +162,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
- private readonly VertexBuffer vertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw);
+ private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1);
public TrailDrawNode(CursorTrail source)
: base(source)
{
for (int i = 0; i < max_sprites; i++)
- {
parts[i].InvalidationID = 0;
- parts[i].WasUpdated = false;
- }
}
public override void ApplyState()
@@ -192,55 +189,24 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override void Draw(Action vertexAction)
{
- shader.GetUniform("g_FadeClock").UpdateValue(ref time);
-
- int updateStart = -1, updateEnd = 0;
-
- for (int i = 0; i < parts.Length; ++i)
- {
- if (parts[i].WasUpdated)
- {
- if (updateStart == -1)
- updateStart = i;
- updateEnd = i + 1;
-
- int start = i * 4;
- int end = start;
-
- Vector2 pos = parts[i].Position;
- float localTime = parts[i].Time;
-
- texture.DrawQuad(
- new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
- DrawColourInfo.Colour,
- null,
- v => vertexBuffer.Vertices[end++] = new TexturedTrailVertex
- {
- Position = v.Position,
- TexturePosition = v.TexturePosition,
- Time = localTime + 1,
- Colour = v.Colour,
- });
-
- parts[i].WasUpdated = false;
- }
- else if (updateStart != -1)
- {
- vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
- updateStart = -1;
- }
- }
-
- // Update all remaining vertices that have been changed.
- if (updateStart != -1)
- vertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
-
base.Draw(vertexAction);
shader.Bind();
+ shader.GetUniform("g_FadeClock").UpdateValue(ref time);
- texture.TextureGL.Bind();
- vertexBuffer.Draw();
+ for (int i = 0; i < parts.Length; ++i)
+ {
+ vertexBatch.DrawTime = parts[i].Time;
+
+ Vector2 pos = parts[i].Position;
+
+ DrawQuad(
+ texture,
+ new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
+ DrawColourInfo.Colour,
+ null,
+ vertexBatch.AddAction);
+ }
shader.Unbind();
}
@@ -249,7 +215,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
base.Dispose(isDisposing);
- vertexBuffer.Dispose();
+ vertexBatch.Dispose();
+ }
+
+ // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures.
+ private class TrailBatch : QuadBatch
+ {
+ public new readonly Action AddAction;
+ public float DrawTime;
+
+ public TrailBatch(int size, int maxBuffers)
+ : base(size, maxBuffers)
+ {
+ AddAction = v => Add(new TexturedTrailVertex
+ {
+ Position = v.Position,
+ TexturePosition = v.TexturePosition,
+ Time = DrawTime + 1,
+ Colour = v.Colour,
+ });
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 27546fa424..eb1977a13d 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
public class OsuCursor : SkinReloadableDrawable
{
+ private const float size = 28;
+
private bool cursorExpand;
private Bindable cursorScale;
@@ -30,7 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public OsuCursor()
{
Origin = Anchor.Centre;
- Size = new Vector2(28);
+
+ Size = new Vector2(size);
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
@@ -46,66 +49,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 6,
- BorderColour = Color4.White,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Pink.Opacity(0.5f),
- Radius = 5,
- },
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 3,
- BorderColour = Color4.White.Opacity(0.5f),
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- },
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Scale = new Vector2(0.1f),
- Masking = true,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.White,
- },
- },
- },
- }
- }, restrictSize: false)
+ Child = scaleTarget = new SkinnableDrawable("Play/osu/cursor", _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
}
};
@@ -145,5 +92,76 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
+
+ private class DefaultCursor : CompositeDrawable
+ {
+ public DefaultCursor()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = size / 6,
+ BorderColour = Color4.White,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Pink.Opacity(0.5f),
+ Radius = 5,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = size / 3,
+ BorderColour = Color4.White.Opacity(0.5f),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ },
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(0.1f),
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.White,
+ },
+ },
+ },
+ }
+ }
+ };
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index d72c334ed3..893c7875fa 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -73,7 +74,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
- if (--downCount == 0)
+ // Todo: Math.Max() is required as a temporary measure to address https://github.com/ppy/osu-framework/issues/2576
+ downCount = Math.Max(0, downCount - 1);
+
+ if (downCount == 0)
updateExpandedState();
break;
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 0cbe0cca85..9037faf606 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.UI;
using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -39,7 +40,13 @@ namespace osu.Game.Rulesets.Osu.UI
RelativeSizeAxes = Axes.Both,
Depth = 1,
},
- HitObjectContainer,
+ // Todo: This should not exist, but currently helps to reduce LOH allocations due to unbinding skin source events on judgement disposal
+ // Todo: Remove when hitobjects are properly pooled
+ new LocalSkinOverrideContainer(null)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = HitObjectContainer,
+ },
approachCircles = new ApproachCircleProxyContainer
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
index e28ff5f460..9c8be868b0 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs
@@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Osu.UI
protected override Container Content => content;
private readonly Container content;
+ private const float playfield_size_adjust = 0.8f;
+
public OsuPlayfieldAdjustmentContainer()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Size = new Vector2(0.75f);
+ // Calculated from osu!stable as 512 (default gamefield size) / 640 (default window size)
+ Size = new Vector2(playfield_size_adjust);
InternalChild = new Container
{
@@ -40,7 +43,19 @@ namespace osu.Game.Rulesets.Osu.UI
{
base.Update();
+ // The following calculation results in a constant of 1.6 when OsuPlayfieldAdjustmentContainer
+ // is consuming the full game_size. This matches the osu-stable "magic ratio".
+ //
+ // game_size = DrawSizePreservingFillContainer.TargetSize = new Vector2(1024, 768)
+ //
+ // Parent is a 4:3 aspect enforced, using height as the constricting dimension
+ // Parent.ChildSize.X = min(game_size.X, game_size.Y * (4 / 3)) * playfield_size_adjust
+ // Parent.ChildSize.X = 819.2
+ //
+ // Scale = 819.2 / 512
+ // Scale = 1.6
Scale = new Vector2(Parent.ChildSize.X / OsuPlayfield.BASE_SIZE.X);
+ // Size = 0.625
Size = Vector2.Divide(Vector2.One, Scale);
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
index 0d4e7edb7b..9e5df0d6b1 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI
private GameplayCursorContainer localCursorContainer;
- public override CursorContainer LocalCursor => State == Visibility.Visible ? localCursorContainer : null;
+ public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
protected override string Message => "Click the orange cursor to resume";
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs b/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..1128a0d37f
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/MainActivity.cs
@@ -0,0 +1,16 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Taiko.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..cd4b74aa16
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj
new file mode 100644
index 0000000000..392442b713
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj
@@ -0,0 +1,39 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {3701A0A1-8476-42C6-B5C4-D24129B4A484}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Rulesets.Taiko.Tests
+ osu.Game.Rulesets.Taiko.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {f167e17a-7de6-4af5-b920-a5112296c695}
+ osu.Game.Rulesets.Taiko
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
index 6613e9e2b4..330cb42901 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, null, "AppDelegate");
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
}
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 68ae7544c2..6c1882b4e2 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -20,10 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[NonParallelizable]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
- public new void Test(string name)
- {
- base.Test(name);
- }
+ public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
index 02300a5dde..8c1b0c4c62 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
typeof(InputDrum),
typeof(DrumSampleMapping),
- typeof(SampleInfo),
+ typeof(HitSampleInfo),
typeof(SampleControlPoint)
};
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
new file mode 100644
index 0000000000..d0db193738
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -0,0 +1,68 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TestSceneTaikoSuddenDeath : PlayerTestScene
+ {
+ public TestSceneTaikoSuddenDeath()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ protected override bool AllowFail => true;
+
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
+ return new ScoreAccessiblePlayer();
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) =>
+ new TaikoBeatmap
+ {
+ HitObjects =
+ {
+ new Swell { StartTime = 1500 },
+ new Hit { StartTime = 100000 },
+ },
+ BeatmapInfo =
+ {
+ Ruleset = new TaikoRuleset().RulesetInfo
+ }
+ };
+
+ [Test]
+ public void TestSpinnerDoesNotFail()
+ {
+ bool judged = false;
+ AddStep("Setup judgements", () =>
+ {
+ judged = false;
+ ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true;
+ });
+ AddUntilStep("swell judged", () => judged);
+ AddAssert("not failed", () => !Player.HasFailed);
+ }
+
+ private class ScoreAccessiblePlayer : TestPlayer
+ {
+ public ScoreAccessiblePlayer()
+ : base(false, false)
+ {
+ }
+
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 216cc0222f..82055ecaee 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,9 +2,9 @@
-
+
-
+
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
index d7fa661e8a..ad2596931d 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
foreach (var s in samplePoints)
{
var centre = s.GetSampleInfo();
- var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP);
+ var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
// todo: this is ugly
centre.Namespace = "taiko";
@@ -43,9 +43,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
}
}
- private SkinnableSound addSound(SampleInfo sampleInfo)
+ private SkinnableSound addSound(HitSampleInfo hitSampleInfo)
{
- var drawable = new SkinnableSound(sampleInfo);
+ var drawable = new SkinnableSound(hitSampleInfo);
Sounds.Add(drawable);
return drawable;
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index f8672037cd..f0cf8d9c7d 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
var curveData = obj as IHasCurve;
// Old osu! used hit sounding to determine various hit type information
- List samples = obj.Samples;
+ List samples = obj.Samples;
- bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
+ bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
if (distanceData != null)
{
@@ -117,15 +117,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
- List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples });
+ List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples });
int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
{
- List currentSamples = allSamples[i];
- bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
- strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH);
+ List currentSamples = allSamples[i];
+ bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
+ strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
if (isRim)
{
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}
else
{
- bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE);
+ bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
if (isRim)
{
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 685ad9949b..c8f3e18911 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
{
if (beatmap.HitObjects.Count == 0)
- return new TaikoDifficultyAttributes { Mods = mods };
+ return new TaikoDifficultyAttributes { Mods = mods, Skills = skills };
return new TaikoDifficultyAttributes
{
@@ -36,6 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate,
MaxCombo = beatmap.HitObjects.Count(h => h is Hit),
+ Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
index 502dd54e9e..a6f902208c 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
@@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06;
+ public override bool HasImplementation => false;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 4c8d5d5204..34ae7db984 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -94,6 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void UpdateState(ArmedState state)
{
+ // TODO: update to use new state management.
var circlePiece = MainPiece as CirclePiece;
circlePiece?.FlashBox.FinishTransforms();
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 119940536e..b46738c69a 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -121,8 +121,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
+ protected override bool UseTransformStateManagement => false;
+
// Normal and clap samples are handled by the drum
- protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP);
+ protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
protected override string SampleNamespace => "Taiko";
diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
index 81f15fb293..ca7d04876e 100644
--- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
@@ -10,3 +10,4 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.iOS")]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Android")]
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index a67004e9c7..83356b77c2 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -12,7 +12,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Replays.Types;
-using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
@@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Fun:
return new Mod[]
{
- new MultiMod(new ModWindUp(), new ModWindDown())
+ new MultiMod(new ModWindUp(), new ModWindDown())
};
default:
diff --git a/osu.Game.Tests.Android/MainActivity.cs b/osu.Game.Tests.Android/MainActivity.cs
new file mode 100644
index 0000000000..0695c8e37b
--- /dev/null
+++ b/osu.Game.Tests.Android/MainActivity.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 Android.App;
+using Android.Content.PM;
+using osu.Framework.Android;
+
+namespace osu.Game.Tests.Android
+{
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.SensorLandscape, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
+ public class MainActivity : AndroidGameActivity
+ {
+ protected override Framework.Game CreateGame() => new OsuTestBrowser();
+ }
+}
diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/Properties/AndroidManifest.xml
new file mode 100644
index 0000000000..bb996dc5ca
--- /dev/null
+++ b/osu.Game.Tests.Android/Properties/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj
new file mode 100644
index 0000000000..c2dd194e09
--- /dev/null
+++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj
@@ -0,0 +1,77 @@
+
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {5CC222DC-5716-4499-B897-DCBDDA4A5CF9}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ {122416d6-6b49-4ee2-a1e8-b825f31c79fe}
+ osu.Game.Tests
+ osu.Game.Tests.Android
+ Properties\AndroidManifest.xml
+ armeabi-v7a;x86;arm64-v8a
+
+
+
+
+
+
+
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+ %(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
+ {58f6c80c-1253-4a0e-a465-b8c85ebeadf3}
+ osu.Game.Rulesets.Catch
+
+
+ {48f4582b-7687-4621-9cbe-5c24197cb536}
+ osu.Game.Rulesets.Mania
+
+
+ {c92a607b-1fdd-4954-9f92-03ff547d9080}
+ osu.Game.Rulesets.Osu
+
+
+ {f167e17a-7de6-4af5-b920-a5112296c695}
+ osu.Game.Rulesets.Taiko
+
+
+ {2a66dd92-adb1-4994-89e2-c94e04acda0d}
+ osu.Game
+
+
+
+
+ 2.0.0
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs
index a23fe4e129..d96a3e27a4 100644
--- a/osu.Game.Tests.iOS/Application.cs
+++ b/osu.Game.Tests.iOS/Application.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Tests.iOS
{
public static void Main(string[] args)
{
- UIApplication.Main(args, null, "AppDelegate");
+ UIApplication.Main(args, "GameUIApplication", "AppDelegate");
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 5fd5fe342d..535320530d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -354,14 +354,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, hitObjects[0].StartTime);
- Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
+ Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, hitObjects[1].StartTime);
- Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
+ Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
}
@@ -384,7 +384,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
}
- SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@@ -402,7 +402,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
}
- SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@@ -422,7 +422,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
- SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
+ HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
}
[Test]
@@ -438,34 +438,34 @@ namespace osu.Game.Tests.Beatmaps.Formats
var slider1 = (ConvertSlider)hitObjects[0];
Assert.AreEqual(1, slider1.NodeSamples[0].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[1].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
Assert.AreEqual(1, slider1.NodeSamples[2].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
var slider2 = (ConvertSlider)hitObjects[1];
Assert.AreEqual(2, slider2.NodeSamples[0].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
- Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[1].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
- Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
Assert.AreEqual(2, slider2.NodeSamples[2].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
- Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
var slider3 = (ConvertSlider)hitObjects[2];
Assert.AreEqual(2, slider3.NodeSamples[0].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
- Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
Assert.AreEqual(1, slider3.NodeSamples[1].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
Assert.AreEqual(2, slider3.NodeSamples[2].Count);
- Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
- Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
+ Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
}
}
@@ -482,5 +482,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
}
}
+
+ [Test]
+ public void TestInvalidEventStillPasses()
+ {
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
+
+ using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
+ using (var badStream = new StreamReader(badResStream))
+ {
+ Assert.DoesNotThrow(() => decoder.Decode(badStream));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 39b7735a55..a725c58462 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -101,14 +101,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
- Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
+ Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
positionData = beatmap.HitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
- Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
+ Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
}
[TestCase(normal)]
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index f020c2a805..ad0ed00989 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -11,7 +11,9 @@ using NUnit.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
+using osu.Framework.Logging;
using osu.Game.Beatmaps;
+using osu.Game.IO;
using osu.Game.Tests.Resources;
using SharpCompress.Archives.Zip;
@@ -21,14 +23,14 @@ namespace osu.Game.Tests.Beatmaps.IO
public class ImportBeatmapTest
{
[Test]
- public void TestImportWhenClosed()
+ public async Task TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
{
try
{
- LoadOszIntoOsu(loadOsu(host));
+ await LoadOszIntoOsu(loadOsu(host));
}
finally
{
@@ -38,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenDelete()
+ public async Task TestImportThenDelete()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
@@ -47,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
}
@@ -59,7 +61,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenImport()
+ public async Task TestImportThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
@@ -68,17 +70,15 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
- var manager = osu.Dependencies.Get();
-
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
+ checkSingleReferencedFileCount(osu, 18);
}
finally
{
@@ -88,30 +88,41 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestRollbackOnFailure()
+ public async Task TestRollbackOnFailure()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
{
try
{
+ int itemAddRemoveFireCount = 0;
+ int loggedExceptionCount = 0;
+
+ Logger.NewEntry += l =>
+ {
+ if (l.Target == LoggingTarget.Database && l.Exception != null)
+ Interlocked.Increment(ref loggedExceptionCount);
+ };
+
var osu = loadOsu(host);
var manager = osu.Dependencies.Get();
- int fireCount = 0;
-
// ReSharper disable once AccessToModifiedClosure
- manager.ItemAdded += (_, __) => fireCount++;
- manager.ItemRemoved += _ => fireCount++;
+ manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
+ manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
- Assert.AreEqual(0, fireCount -= 1);
+ Assert.AreEqual(0, itemAddRemoveFireCount -= 1);
imported.Hash += "-changed";
manager.Update(imported);
- Assert.AreEqual(0, fireCount -= 2);
+ Assert.AreEqual(0, itemAddRemoveFireCount -= 2);
+
+ checkBeatmapSetCount(osu, 1);
+ checkBeatmapCount(osu, 12);
+ checkSingleReferencedFileCount(osu, 18);
var breakTemp = TestResources.GetTestBeatmapForImport();
@@ -127,19 +138,24 @@ namespace osu.Game.Tests.Beatmaps.IO
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
}
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
- Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
-
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
- manager.Import(breakTemp);
+ try
+ {
+ await manager.Import(breakTemp);
+ }
+ catch
+ {
+ }
// no events should be fired in the case of a rollback.
- Assert.AreEqual(0, fireCount);
+ Assert.AreEqual(0, itemAddRemoveFireCount);
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
- Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
+ checkBeatmapCount(osu, 12);
+
+ checkSingleReferencedFileCount(osu, 18);
+
+ Assert.AreEqual(1, loggedExceptionCount);
}
finally
{
@@ -149,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenImportDifferentHash()
+ public async Task TestImportThenImportDifferentHash()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
@@ -159,19 +175,18 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
var manager = osu.Dependencies.Get();
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
imported.Hash += "-changed";
manager.Update(imported);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
- Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 1);
}
finally
{
@@ -181,7 +196,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportThenDeleteThenImport()
+ public async Task TestImportThenDeleteThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
@@ -190,11 +205,11 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
@@ -209,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[TestCase(true)]
[TestCase(false)]
- public void TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
+ public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
@@ -218,7 +233,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = loadOsu(host);
- var imported = LoadOszIntoOsu(osu);
+ var imported = await LoadOszIntoOsu(osu);
if (set)
imported.OnlineBeatmapSetID = 1234;
@@ -229,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO
deleteBeatmapSet(imported, osu);
- var importedSecondTime = LoadOszIntoOsu(osu);
+ var importedSecondTime = await LoadOszIntoOsu(osu);
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
Assert.IsTrue(imported.ID != importedSecondTime.ID);
@@ -243,7 +258,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportWithDuplicateBeatmapIDs()
+ public async Task TestImportWithDuplicateBeatmapIDs()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
@@ -284,7 +299,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- var imported = manager.Import(toImport);
+ var imported = await manager.Import(toImport);
Assert.NotNull(imported);
Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
@@ -330,7 +345,7 @@ namespace osu.Game.Tests.Beatmaps.IO
}
[Test]
- public void TestImportWhenFileOpen()
+ public async Task TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
{
@@ -339,7 +354,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp))
- osu.Dependencies.Get().Import(temp);
+ await osu.Dependencies.Get().Import(temp);
ensureLoaded(osu);
File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
@@ -351,13 +366,13 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
- public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
+ public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null)
{
var temp = path ?? TestResources.GetTestBeatmapForImport();
var manager = osu.Dependencies.Get();
- manager.Import(temp);
+ await manager.Import(temp);
var imported = manager.GetAllUsableBeatmapSets();
@@ -373,11 +388,32 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
manager.Delete(imported);
- Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
- Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
+ checkBeatmapSetCount(osu, 0);
+ checkBeatmapSetCount(osu, 1, true);
+ checkSingleReferencedFileCount(osu, 0);
+
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
+ private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false)
+ {
+ var manager = osu.Dependencies.Get();
+
+ Assert.AreEqual(expected, includeDeletePending
+ ? manager.QueryBeatmapSets(_ => true).ToList().Count
+ : manager.GetAllUsableBeatmapSets().Count);
+ }
+
+ private void checkBeatmapCount(OsuGameBase osu, int expected)
+ {
+ Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count);
+ }
+
+ private void checkSingleReferencedFileCount(OsuGameBase osu, int expected)
+ {
+ Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count());
+ }
+
private OsuGameBase loadOsu(GameHost host)
{
var osu = new OsuGameBase();
diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
new file mode 100644
index 0000000000..9fba0f1668
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs
@@ -0,0 +1,116 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [TestFixture]
+ public class SliderEventGenerationTest
+ {
+ private const double start_time = 0;
+ private const double span_duration = 1000;
+
+ [Test]
+ public void TestSingleSpan()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[3].Time, Is.EqualTo(span_duration));
+ }
+
+ [Test]
+ public void TestRepeat()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat));
+ Assert.That(events[2].Time, Is.EqualTo(span_duration));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2));
+
+ Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[5].Time, Is.EqualTo(2 * span_duration));
+ }
+
+ [Test]
+ public void TestNonEvenTicks()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
+
+ Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
+ Assert.That(events[0].Time, Is.EqualTo(start_time));
+
+ Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[1].Time, Is.EqualTo(300));
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[2].Time, Is.EqualTo(600));
+
+ Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[3].Time, Is.EqualTo(900));
+
+ Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat));
+ Assert.That(events[4].Time, Is.EqualTo(span_duration));
+
+ Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[5].Time, Is.EqualTo(1100));
+
+ Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[6].Time, Is.EqualTo(1400));
+
+ Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick));
+ Assert.That(events[7].Time, Is.EqualTo(1700));
+
+ Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail));
+ Assert.That(events[9].Time, Is.EqualTo(2 * span_duration));
+ }
+
+ [Test]
+ public void TestLegacyLastTickOffset()
+ {
+ var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
+
+ Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
+ Assert.That(events[2].Time, Is.EqualTo(900));
+ }
+
+ [Test]
+ public void TestMinimumTickDistance()
+ {
+ const double velocity = 5;
+ const double min_distance = velocity * 10;
+
+ var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
+
+ Assert.Multiple(() =>
+ {
+ int tickIndex = -1;
+
+ while (++tickIndex < events.Length)
+ {
+ if (events[tickIndex].Type != SliderEventType.Tick)
+ continue;
+
+ Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance));
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
similarity index 94%
rename from osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs
rename to osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
index 73387fa5ab..18cbd4e7c5 100644
--- a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs
+++ b/osu.Game.Tests/NonVisual/FramedReplayInputHandlerTest.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Replays;
@@ -9,7 +10,7 @@ using osu.Game.Rulesets.Replays;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
- public class FramedReplayinputHandlerTest
+ public class FramedReplayInputHandlerTest
{
private Replay replay;
private TestInputHandler handler;
@@ -160,10 +161,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestRewindInsideImportantSection()
{
- // fast forward to important section
- while (handler.SetFrameFromTime(3000) != null)
- {
- }
+ fastForwardToPoint(3000);
setTime(4000, 4000);
confirmCurrentFrame(4);
@@ -205,10 +203,7 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestRewindOutOfImportantSection()
{
- // fast forward to important section
- while (handler.SetFrameFromTime(3500) != null)
- {
- }
+ fastForwardToPoint(3500);
confirmCurrentFrame(3);
confirmNextFrame(4);
@@ -227,6 +222,15 @@ namespace osu.Game.Tests.NonVisual
confirmNextFrame(2);
}
+ private void fastForwardToPoint(double destination)
+ {
+ for (int i = 0; i < 1000; i++)
+ if (handler.SetFrameFromTime(destination) == null)
+ return;
+
+ throw new TimeoutException("Seek was never fulfilled");
+ }
+
private void setTime(double set, double? expect)
{
Assert.AreEqual(expect, handler.SetFrameFromTime(set));
@@ -274,6 +278,7 @@ namespace osu.Game.Tests.NonVisual
public TestInputHandler(Replay replay)
: base(replay)
{
+ FrameAccuratePlayback = true;
}
protected override double AllowedImportantTimeSpan => 1000;
diff --git a/osu.Game.Tests/Resources/invalid-events.osu b/osu.Game.Tests/Resources/invalid-events.osu
new file mode 100644
index 0000000000..df86b26dba
--- /dev/null
+++ b/osu.Game.Tests/Resources/invalid-events.osu
@@ -0,0 +1,14 @@
+osu file format v14
+
+[Events]
+bad,event,this,should,fail
+//Background and Video events
+0,0,"machinetop_background.jpg",0,0
+//Break Periods
+2,122474,140135
+//Storyboard Layer 0 (Background)
+this,is,also,bad
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Sound Samples
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index e39f18c3cd..4babb07213 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -23,13 +23,13 @@ namespace osu.Game.Tests.Scores.IO
public class ImportScoreTest
{
[Test]
- public void TestBasicImport()
+ public async Task TestBasicImport()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@@ -43,7 +43,7 @@ namespace osu.Game.Tests.Scores.IO
OnlineScoreID = 12345,
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Rank, imported.Rank);
Assert.AreEqual(toImport.TotalScore, imported.TotalScore);
@@ -62,20 +62,20 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public void TestImportMods()
+ public async Task TestImportMods()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock));
Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime));
@@ -88,13 +88,13 @@ namespace osu.Game.Tests.Scores.IO
}
[Test]
- public void TestImportStatistics()
+ public async Task TestImportStatistics()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
{
try
{
- var osu = loadOsu(host);
+ var osu = await loadOsu(host);
var toImport = new ScoreInfo
{
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Scores.IO
}
};
- var imported = loadIntoOsu(osu, toImport);
+ var imported = await loadIntoOsu(osu, toImport);
Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]);
Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]);
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score)
+ private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
{
var beatmapManager = osu.Dependencies.Get();
@@ -125,20 +125,24 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
- scoreManager.Import(score);
+ await scoreManager.Import(score);
return scoreManager.GetAllUsableScores().First();
}
- private OsuGameBase loadOsu(GameHost host)
+ private async Task loadOsu(GameHost host)
{
var osu = new OsuGameBase();
+
+#pragma warning disable 4014
Task.Run(() => host.Run(osu));
+#pragma warning restore 4014
+
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
var beatmapFile = TestResources.GetTestBeatmapForImport();
var beatmapManager = osu.Dependencies.Get();
- beatmapManager.Import(beatmapFile);
+ await beatmapManager.Import(beatmapFile);
return osu;
}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
similarity index 84%
rename from osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs
rename to osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
index 7104a420a3..3061a3a542 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
@@ -10,13 +10,13 @@ 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.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -36,7 +36,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
[TestFixture]
- public class TestSceneBackgroundScreenBeatmap : ManualInputManagerTestScene
+ public class TestSceneUserDimContainer : ManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -50,29 +50,17 @@ namespace osu.Game.Tests.Visual.Background
private DummySongSelect songSelect;
private TestPlayerLoader playerLoader;
private TestPlayer player;
- private DatabaseContextFactory factory;
private BeatmapManager manager;
private RulesetStore rulesets;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
- factory = new DatabaseContextFactory(LocalStorage);
- factory.ResetDatabase();
-
- using (var usage = factory.Get())
- usage.Migrate();
-
- factory.ResetDatabase();
-
- using (var usage = factory.Get())
- usage.Migrate();
-
- Dependencies.Cache(rulesets = new RulesetStore(factory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default));
+ 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());
+ manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
Beatmap.SetDefault();
}
@@ -131,20 +119,20 @@ namespace osu.Game.Tests.Visual.Background
{
performFullSetup();
createFakeStoryboard();
- AddStep("Storyboard Enabled", () =>
+ AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
- AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible());
- AddStep("Storyboard Disabled", () =>
+ 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.IsStoryboardInvisible());
+ AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
}
///
@@ -161,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background
}
///
- /// Check if the is properly accepting user-defined visual changes at all.
+ /// Ensure is properly accepting user-defined visual changes for a background.
///
[Test]
- public void DisableUserDimTest()
+ public void DisableUserDimBackgroundTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
+ AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
- AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
+ 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
///
@@ -241,14 +251,15 @@ namespace osu.Game.Tests.Visual.Background
{
player.StoryboardEnabled.Value = false;
player.ReplacesBackground.Value = false;
- player.CurrentStoryboardContainer.Add(new OsuSpriteText
+ player.DimmableStoryboard.Add(new OsuSpriteText
{
- Size = new Vector2(250, 50),
+ Size = new Vector2(500, 50),
Alpha = 1,
- Colour = Color4.Tomato,
+ Colour = Color4.White,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "THIS IS A STORYBOARD",
+ Font = new FontUsage(size: 50)
});
});
@@ -300,7 +311,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
- public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25);
+ public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
@@ -333,17 +344,7 @@ namespace osu.Game.Tests.Visual.Background
{
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
- protected override UserDimContainer CreateStoryboardContainer()
- {
- return new TestUserDimContainer(true)
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 1,
- EnableUserDim = { Value = true }
- };
- }
-
- public UserDimContainer CurrentStoryboardContainer => StoryboardContainer;
+ public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
// Whether or not the player should be allowed to load.
public bool BlockLoad;
@@ -357,9 +358,7 @@ namespace osu.Game.Tests.Visual.Background
{
}
- public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1;
-
- public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1;
+ public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, CancellationToken token)
@@ -392,15 +391,15 @@ namespace osu.Game.Tests.Visual.Background
private class FadeAccessibleBackground : BackgroundScreenBeatmap
{
- protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both };
+ protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
- public Color4 CurrentColour => fadeContainer.CurrentColour;
+ public Color4 CurrentColour => dimmable.CurrentColour;
- public float CurrentAlpha => fadeContainer.CurrentAlpha;
+ public float CurrentAlpha => dimmable.CurrentAlpha;
public Vector2 CurrentBlur => Background.BlurSigma;
- private TestUserDimContainer fadeContainer;
+ private TestDimmableBackground dimmable;
public FadeAccessibleBackground(WorkingBeatmap beatmap)
: base(beatmap)
@@ -408,15 +407,10 @@ namespace osu.Game.Tests.Visual.Background
}
}
- private class TestUserDimContainer : UserDimContainer
+ private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
{
- public Color4 CurrentColour => DimContainer.Colour;
- public float CurrentAlpha => DimContainer.Alpha;
-
- public TestUserDimContainer(bool isStoryboard = false)
- : base(isStoryboard)
- {
- }
+ public Color4 CurrentColour => Content.Colour;
+ public float CurrentAlpha => Content.Alpha;
}
}
}
diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
index e97983dd8b..55aaeed8bf 100644
--- a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
+++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs
@@ -14,94 +14,132 @@ namespace osu.Game.Tests.Visual.Components
[TestFixture]
public class TestSceneIdleTracker : ManualInputManagerTestScene
{
- private readonly IdleTrackingBox box1;
- private readonly IdleTrackingBox box2;
- private readonly IdleTrackingBox box3;
- private readonly IdleTrackingBox box4;
+ private IdleTrackingBox box1;
+ private IdleTrackingBox box2;
+ private IdleTrackingBox box3;
+ private IdleTrackingBox box4;
- public TestSceneIdleTracker()
+ private IdleTrackingBox[] boxes;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Children = new Drawable[]
+ InputManager.MoveMouseTo(Vector2.Zero);
+
+ Children = boxes = new[]
{
- box1 = new IdleTrackingBox(1000)
+ box1 = new IdleTrackingBox(2000)
{
+ Name = "TopLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Red,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
},
- box2 = new IdleTrackingBox(2000)
+ box2 = new IdleTrackingBox(4000)
{
+ Name = "TopRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Green,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
- box3 = new IdleTrackingBox(3000)
+ box3 = new IdleTrackingBox(6000)
{
+ Name = "BottomLeft",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Blue,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
- box4 = new IdleTrackingBox(4000)
+ box4 = new IdleTrackingBox(8000)
{
+ Name = "BottomRight",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Orange,
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
},
};
- }
+ });
[Test]
public void TestNudge()
{
- AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to top left", () => InputManager.MoveMouseTo(box1));
- AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
+ waitForAllIdle();
AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1)));
- AddAssert("check not idle", () => !box1.IsIdle);
- AddAssert("check idle", () => box2.IsIdle);
- AddAssert("check idle", () => box3.IsIdle);
- AddAssert("check idle", () => box4.IsIdle);
+ checkIdleStatus(1, false);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, true);
}
[Test]
public void TestMovement()
{
- AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to top right", () => InputManager.MoveMouseTo(box2));
- AddAssert("check not idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle);
- AddAssert("check idle", () => box3.IsIdle);
- AddAssert("check idle", () => box4.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, true);
- AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre));
- AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre));
+ AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3));
+ AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4));
- AddAssert("check not idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle);
- AddAssert("check idle", () => !box3.IsIdle);
- AddAssert("check idle", () => !box4.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
- AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
+ waitForAllIdle();
}
[Test]
public void TestTimings()
{
- AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre));
+ AddStep("move to centre", () => InputManager.MoveMouseTo(Content));
+
+ checkIdleStatus(1, false);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
- AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
AddUntilStep("Wait for idle", () => box1.IsIdle);
- AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle);
+
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, false);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
+
AddUntilStep("Wait for idle", () => box2.IsIdle);
- AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle);
+
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, false);
+ checkIdleStatus(4, false);
+
AddUntilStep("Wait for idle", () => box3.IsIdle);
+ checkIdleStatus(1, true);
+ checkIdleStatus(2, true);
+ checkIdleStatus(3, true);
+ checkIdleStatus(4, false);
+
+ waitForAllIdle();
+ }
+
+ private void checkIdleStatus(int box, bool expectedIdle)
+ {
+ AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle);
+ }
+
+ private void waitForAllIdle()
+ {
AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle);
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
index 3cd1b8307a..879e15c548 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs
@@ -1,8 +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 System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play;
@@ -11,78 +14,172 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneBreakOverlay : OsuTestScene
{
- private readonly BreakOverlay breakOverlay;
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(BreakOverlay),
+ };
+
+ private readonly TestBreakOverlay breakOverlay;
+
+ private readonly IReadOnlyList testBreaks = new List
+ {
+ new BreakPeriod
+ {
+ StartTime = 1000,
+ EndTime = 5000,
+ },
+ new BreakPeriod
+ {
+ StartTime = 6000,
+ EndTime = 13500,
+ },
+ };
public TestSceneBreakOverlay()
{
- Child = breakOverlay = new BreakOverlay(true);
-
- AddStep("2s break", () => startBreak(2000));
- AddStep("5s break", () => startBreak(5000));
- AddStep("10s break", () => startBreak(10000));
- AddStep("15s break", () => startBreak(15000));
- AddStep("2s, 2s", startMultipleBreaks);
- AddStep("0.5s, 0.7s, 1s, 2s", startAnotherMultipleBreaks);
+ Add(breakOverlay = new TestBreakOverlay(true));
}
- private void startBreak(double duration)
+ [Test]
+ public void TestShowBreaks()
{
- breakOverlay.Breaks = new List
+ setClock(false);
+
+ addShowBreakStep(2);
+ addShowBreakStep(5);
+ addShowBreakStep(15);
+ }
+
+ [Test]
+ public void TestNoEffectsBreak()
+ {
+ var shortBreak = new BreakPeriod { EndTime = 500 };
+
+ setClock(true);
+ loadBreaksStep("short break", new[] { shortBreak });
+
+ addBreakSeeks(shortBreak, false);
+ }
+
+ [Test]
+ public void TestMultipleBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ foreach (var b in testBreaks)
+ addBreakSeeks(b, false);
+ }
+
+ [Test]
+ public void TestRewindBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ foreach (var b in testBreaks.Reverse())
+ addBreakSeeks(b, true);
+ }
+
+ [Test]
+ public void TestSkipBreaks()
+ {
+ setClock(true);
+ loadBreaksStep("multiple breaks", testBreaks);
+
+ seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
+ AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1);
+
+ seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
+ seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
+ seekAndAssertBreak("seek to break after end", testBreaks[1].EndTime + 500, false);
+ }
+
+ private void addShowBreakStep(double seconds)
+ {
+ AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List
{
new BreakPeriod
{
StartTime = Clock.CurrentTime,
- EndTime = Clock.CurrentTime + duration,
+ EndTime = Clock.CurrentTime + seconds * 1000,
}
- };
+ });
}
- private void startMultipleBreaks()
+ private void setClock(bool useManual)
{
- double currentTime = Clock.CurrentTime;
-
- breakOverlay.Breaks = new List
- {
- new BreakPeriod
- {
- StartTime = currentTime,
- EndTime = currentTime + 2000,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 4000,
- EndTime = currentTime + 6000,
- }
- };
+ AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual));
}
- private void startAnotherMultipleBreaks()
+ private void loadBreaksStep(string breakDescription, IReadOnlyList breaks)
{
- double currentTime = Clock.CurrentTime;
+ AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks);
+ seekAndAssertBreak("seek back to 0", 0, false);
+ }
- breakOverlay.Breaks = new List
+ private void addBreakSeeks(BreakPeriod b, bool isReversed)
+ {
+ if (isReversed)
{
- new BreakPeriod // Duration is less than 650 - too short to appear
- {
- StartTime = currentTime,
- EndTime = currentTime + 500,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 1500,
- EndTime = currentTime + 2200,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 3200,
- EndTime = currentTime + 4200,
- },
- new BreakPeriod
- {
- StartTime = currentTime + 5200,
- EndTime = currentTime + 7200,
- }
- };
+ seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
+ seekAndAssertBreak("seek to break end", b.EndTime, false);
+ seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
+ seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
+ }
+ else
+ {
+ seekAndAssertBreak("seek to break start", b.StartTime, b.HasEffect);
+ seekAndAssertBreak("seek to break middle", b.StartTime + b.Duration / 2, b.HasEffect);
+ seekAndAssertBreak("seek to break end", b.EndTime, false);
+ seekAndAssertBreak("seek to break after end", b.EndTime + 500, false);
+ }
+ }
+
+ private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
+ {
+ AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time);
+ AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
+ {
+ breakOverlay.ProgressTime();
+ return breakOverlay.IsBreakTime.Value == shouldBeBreak;
+ });
+ }
+
+ private class TestBreakOverlay : BreakOverlay
+ {
+ private readonly FramedClock framedManualClock;
+ private readonly ManualClock manualClock;
+ private IFrameBasedClock originalClock;
+
+ public new int CurrentBreakIndex => base.CurrentBreakIndex;
+
+ public double ManualClockTime
+ {
+ get => manualClock.CurrentTime;
+ set => manualClock.CurrentTime = value;
+ }
+
+ public TestBreakOverlay(bool letterboxing)
+ : base(letterboxing)
+ {
+ framedManualClock = new FramedClock(manualClock = new ManualClock());
+ ProcessCustomClock = false;
+ }
+
+ public void ProgressTime()
+ {
+ framedManualClock.ProcessFrame();
+ Update();
+ }
+
+ public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ originalClock = Clock;
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
new file mode 100644
index 0000000000..f4e8a68819
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs
@@ -0,0 +1,50 @@
+// 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.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneFailAnimation : AllPlayersTestScene
+ {
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Array.Empty();
+ return new FailPlayer();
+ }
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(AllPlayersTestScene),
+ typeof(TestPlayer),
+ typeof(Player),
+ };
+
+ protected override void AddCheckSteps()
+ {
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible);
+ }
+
+ private class FailPlayer : TestPlayer
+ {
+ public new FailOverlay FailOverlay => base.FailOverlay;
+
+ public FailPlayer()
+ : base(false, false)
+ {
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ ScoreProcessor.FailConditions += (_, __) => true;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
index ba9c583b08..4727140d99 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Press select", () => press(GlobalAction.Select));
- AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
+ AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
@@ -237,7 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddAssert("Action was triggered", () => triggered);
- AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
+ AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
///
@@ -272,7 +272,7 @@ namespace osu.Game.Tests.Visual.Gameplay
return triggered;
});
- AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
+ AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
}
private void press(Key key)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
new file mode 100644
index 0000000000..237fee1594
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -0,0 +1,109 @@
+// 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.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneGameplayRewinding : PlayerTestScene
+ {
+ private RulesetExposingPlayer player => (RulesetExposingPlayer)Player;
+
+ [Resolved]
+ private AudioManager audioManager { get; set; }
+
+ public TestSceneGameplayRewinding()
+ : base(new OsuRuleset())
+ {
+ }
+
+ private Track track;
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ {
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ track = working.Track;
+ return working;
+ }
+
+ [Test]
+ public void TestNoJudgementsOnRewind()
+ {
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+ addSeekStep(3000);
+ AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
+ AddStep("clear results", () => player.AppliedResults.Clear());
+ addSeekStep(0);
+ AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
+ AddAssert("no results triggered", () => player.AppliedResults.Count == 0);
+ }
+
+ private void addSeekStep(double time)
+ {
+ AddStep($"seek to {time}", () => track.Seek(time));
+
+ // Allow a few frames of lenience
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
+
+ protected override Player CreatePlayer(Ruleset ruleset)
+ {
+ Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ return new RulesetExposingPlayer();
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } },
+ };
+
+ for (int i = 0; i < 15; i++)
+ {
+ beatmap.HitObjects.Add(new HitCircle
+ {
+ Position = new Vector2(256, 192),
+ StartTime = 1000 + 30 * i
+ });
+ }
+
+ return beatmap;
+ }
+
+ private class RulesetExposingPlayer : Player
+ {
+ public readonly List AppliedResults = new List();
+
+ public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
+
+ public new DrawableRuleset DrawableRuleset => base.DrawableRuleset;
+
+ public RulesetExposingPlayer()
+ : base(false, false)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScoreProcessor.NewJudgement += r => AppliedResults.Add(r);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
index b6f8638f4a..5808a78056 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs
@@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseAfterFail()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
- AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
+ AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible);
confirmClockRunning(false);
@@ -137,6 +137,22 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
+ [Test]
+ public void TestExitViaHoldToExit()
+ {
+ AddStep("exit", () =>
+ {
+ InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer));
+ InputManager.PressButton(MouseButton.Left);
+ });
+
+ confirmPaused();
+
+ AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
+
+ exitAndConfirm();
+ }
+
[Test]
public void TestExitFromPause()
{
@@ -189,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown);
private void confirmClockRunning(bool isRunning) =>
- AddAssert("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
+ AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning);
protected override bool AllowFail => true;
@@ -203,9 +219,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public new HUDOverlay HUDOverlay => base.HUDOverlay;
- public bool FailOverlayVisible => FailOverlay.State == Visibility.Visible;
+ public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible;
- public bool PauseOverlayVisible => PauseOverlay.State == Visibility.Visible;
+ public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
public override void OnEntering(IScreen last)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index daee3a520c..ab519360ac 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -9,6 +9,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -16,12 +17,13 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.PlayerSettings;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerLoader : ManualInputManagerTestScene
{
- private PlayerLoader loader;
+ private TestPlayerLoader loader;
private OsuScreenStack stack;
[SetUp]
@@ -31,19 +33,29 @@ namespace osu.Game.Tests.Visual.Gameplay
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
+ [Test]
+ public void TestBlockLoadViaMouseMovement()
+ {
+ AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false))));
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+ AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20);
+ AddAssert("loader still active", () => loader.IsCurrentScreen());
+ AddUntilStep("loads after idle", () => !loader.IsCurrentScreen());
+ }
+
[Test]
public void TestLoadContinuation()
{
Player player = null;
SlowLoadPlayer slowPlayer = null;
- AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false))));
+ AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false))));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () =>
{
- stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
+ stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
});
@@ -61,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("load player", () =>
{
Mods.Value = new[] { gameMod = new TestMod() };
- stack.Push(loader = new PlayerLoader(() => player = new TestPlayer()));
+ stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer()));
});
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
@@ -85,6 +97,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied);
}
+ private class TestPlayerLoader : PlayerLoader
+ {
+ public new VisualSettings VisualSettings => base.VisualSettings;
+
+ public TestPlayerLoader(Func createPlayer)
+ : base(createPlayer)
+ {
+ }
+ }
+
private class TestMod : Mod, IApplicableToScoreProcessor
{
public override string Name => string.Empty;
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
new file mode 100644
index 0000000000..0dfcda122f
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.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 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.Screens.Play;
+using osu.Game.Users;
+using osuTK;
+using System;
+using System.Collections.Generic;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ [TestFixture]
+ public class TestSceneReplayDownloadButton : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ReplayDownloadButton)
+ };
+
+ private TestReplayDownloadButton downloadButton;
+
+ public TestSceneReplayDownloadButton()
+ {
+ createButton(true);
+ AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading));
+ AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
+ AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
+ createButton(false);
+ }
+
+ private void createButton(bool withReplay)
+ {
+ AddStep(withReplay ? @"create button with replay" : "create button without replay", () =>
+ {
+ Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(withReplay))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(80, 40),
+ };
+ });
+ }
+
+ private ScoreInfo getScoreInfo(bool replayAvailable)
+ {
+ return new APILegacyScoreInfo
+ {
+ ID = 1,
+ OnlineScoreID = 2553163309,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ Replay = replayAvailable,
+ User = new User
+ {
+ Id = 39828,
+ Username = @"WubWoofWolf",
+ }
+ };
+ }
+
+ private class TestReplayDownloadButton : ReplayDownloadButton
+ {
+ public void SetDownloadState(DownloadState state) => State.Value = state;
+
+ public TestReplayDownloadButton(ScoreInfo score)
+ : base(score)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
deleted file mode 100644
index c7a0df6e9f..0000000000
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs
+++ /dev/null
@@ -1,145 +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 NUnit.Framework;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Skinning;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Gameplay
-{
- public class TestSceneSkinReloadable : OsuTestScene
- {
- [Test]
- public void TestInitialLoad()
- {
- var secondarySource = new SecondarySource();
- SkinConsumer consumer = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
- }
- };
- });
-
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- [Test]
- public void TestOverride()
- {
- var secondarySource = new SecondarySource();
-
- SkinConsumer consumer = null;
- Container target = null;
-
- AddStep("setup layout", () =>
- {
- Child = new SkinSourceContainer
- {
- RelativeSizeAxes = Axes.Both,
- Child = target = new LocalSkinOverrideContainer(secondarySource)
- {
- RelativeSizeAxes = Axes.Both,
- }
- };
- });
-
- AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
- AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
- AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
- }
-
- private class NamedBox : Container
- {
- public NamedBox(string name)
- {
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Black,
- RelativeSizeAxes = Axes.Both,
- },
- new OsuSpriteText
- {
- Font = OsuFont.Default.With(size: 40),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = name
- }
- };
- }
- }
-
- private class SkinConsumer : SkinnableDrawable
- {
- public new Drawable Drawable => base.Drawable;
- public int SkinChangedCount { get; private set; }
-
- public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true)
- : base(name, defaultImplementation, allowFallback, restrictSize)
- {
- }
-
- protected override void SkinChanged(ISkinSource skin, bool allowFallback)
- {
- base.SkinChanged(skin, allowFallback);
- SkinChangedCount++;
- }
- }
-
- private class BaseSourceBox : NamedBox
- {
- public BaseSourceBox()
- : base("Base Source")
- {
- }
- }
-
- private class SecondarySourceBox : NamedBox
- {
- public SecondarySourceBox()
- : base("Secondary Source")
- {
- }
- }
-
- private class SecondarySource : ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
-
- private class SkinSourceContainer : Container, ISkin
- {
- public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
-
- public Texture GetTexture(string componentName) => throw new NotImplementedException();
-
- public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
-
- public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
new file mode 100644
index 0000000000..0b5978e3eb
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableDrawable.cs
@@ -0,0 +1,283 @@
+// 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.Globalization;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableDrawable : OsuTestScene
+ {
+ [Test]
+ public void TestConfineScaleDown()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new LocalSkinOverrideContainer(new SizedSource(50))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(30),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 30, 30, 30, 50 }));
+ }
+
+ [Test]
+ public void TestConfineScaleUp()
+ {
+ FillFlowContainer fill = null;
+
+ AddStep("setup layout larger source", () =>
+ {
+ Child = new LocalSkinOverrideContainer(new SizedSource(30))
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = fill = new FillFlowContainer
+ {
+ Size = new Vector2(50),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(10),
+ Children = new[]
+ {
+ new ExposedSkinnableDrawable("default", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.ScaleToFit),
+ new ExposedSkinnableDrawable("available", _ => new DefaultBox(), _ => true, ConfineMode.NoScaling)
+ }
+ },
+ };
+ });
+
+ AddAssert("check sizes", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ AddStep("adjust scale", () => fill.Scale = new Vector2(2));
+ AddAssert("check sizes unchanged by scale", () => fill.Children.Select(c => c.Drawable.DrawWidth).SequenceEqual(new float[] { 50, 30, 50, 30 }));
+ }
+
+ [Test]
+ public void TestInitialLoad()
+ {
+ var secondarySource = new SecondarySource();
+ SkinConsumer consumer = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
+ }
+ };
+ });
+
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ [Test]
+ public void TestOverride()
+ {
+ var secondarySource = new SecondarySource();
+
+ SkinConsumer consumer = null;
+ Container target = null;
+
+ AddStep("setup layout", () =>
+ {
+ Child = new SkinSourceContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = target = new LocalSkinOverrideContainer(secondarySource)
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ };
+ });
+
+ AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
+ AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
+ AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
+ }
+
+ private class ExposedSkinnableDrawable : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+
+ public ExposedSkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
+ : base(name, defaultImplementation, allowFallback, confineMode)
+ {
+ }
+ }
+
+ private class DefaultBox : DrawWidthBox
+ {
+ public DefaultBox()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+ }
+
+ private class DrawWidthBox : Container
+ {
+ private readonly OsuSpriteText text;
+
+ public DrawWidthBox()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Gray,
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+
+ protected override void UpdateAfterChildren()
+ {
+ base.UpdateAfterChildren();
+ text.Text = DrawWidth.ToString(CultureInfo.InvariantCulture);
+ }
+ }
+
+ private class NamedBox : Container
+ {
+ public NamedBox(string name)
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(size: 40),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = name
+ }
+ };
+ }
+ }
+
+ private class SkinConsumer : SkinnableDrawable
+ {
+ public new Drawable Drawable => base.Drawable;
+ public int SkinChangedCount { get; private set; }
+
+ public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null)
+ : base(name, defaultImplementation, allowFallback)
+ {
+ }
+
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+ SkinChangedCount++;
+ }
+ }
+
+ private class BaseSourceBox : NamedBox
+ {
+ public BaseSourceBox()
+ : base("Base Source")
+ {
+ }
+ }
+
+ private class SecondarySourceBox : NamedBox
+ {
+ public SecondarySourceBox()
+ : base("Secondary Source")
+ {
+ }
+ }
+
+ private class SizedSource : ISkin
+ {
+ private readonly float size;
+
+ public SizedSource(float size)
+ {
+ this.size = size;
+ }
+
+ public Drawable GetDrawableComponent(string componentName) =>
+ componentName == "available"
+ ? new DrawWidthBox
+ {
+ Colour = Color4.Yellow,
+ Size = new Vector2(size)
+ }
+ : null;
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+
+ private class SecondarySource : ISkin
+ {
+ public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+
+ private class SkinSourceContainer : Container, ISkin
+ {
+ public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
+
+ public Texture GetTexture(string componentName) => throw new NotImplementedException();
+
+ public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 0519660477..b152c21454 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -1,19 +1,88 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
using osu.Game.Screens.Play;
+using osuTK;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneSkipOverlay : OsuTestScene
+ public class TestSceneSkipOverlay : ManualInputManagerTestScene
{
- protected override void LoadComplete()
- {
- base.LoadComplete();
+ private SkipOverlay skip;
+ private int requestCount;
- Add(new SkipOverlay(Clock.CurrentTime + 5000));
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ requestCount = 0;
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = new FramedOffsetClock(Clock)
+ {
+ Offset = -Clock.CurrentTime,
+ },
+ Children = new Drawable[]
+ {
+ skip = new SkipOverlay(6000)
+ {
+ RequestSeek = _ => requestCount++
+ }
+ },
+ };
+ });
+
+ [Test]
+ public void TestFadeOnIdle()
+ {
+ AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
+ AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
+
+ AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
+ AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
}
+
+ [Test]
+ public void TestClickableAfterFade()
+ {
+ AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
+ AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkRequestCount(1);
+ }
+
+ [Test]
+ public void TestClickOnlyActuatesOnce()
+ {
+ AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkRequestCount(1);
+ }
+
+ [Test]
+ public void TestDoesntFadeOnMouseDown()
+ {
+ AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
+ AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
+ AddUntilStep("wait for overlay disapper", () => !skip.IsAlive);
+ AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
+ AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
+ checkRequestCount(0);
+ }
+
+ private void checkRequestCount(int expected) =>
+ AddAssert($"request count is {expected}", () => requestCount == expected);
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index 213cdf5e48..ead7a4b7fc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
- State = Visibility.Visible,
+ State = { Value = Visibility.Visible },
});
AddStep("Restart", restart);
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
new file mode 100644
index 0000000000..d03d341ee4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
@@ -0,0 +1,69 @@
+// 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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Screens;
+using osu.Game.Screens;
+using osu.Game.Screens.Menu;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public abstract class IntroTestScene : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(StartupScreen),
+ typeof(IntroScreen),
+ typeof(OsuScreen),
+ typeof(IntroTestScene),
+ };
+
+ [Cached]
+ private OsuLogo logo;
+
+ protected IntroTestScene()
+ {
+ Drawable introStack = null;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = float.MaxValue,
+ Colour = Color4.Black,
+ },
+ logo = new OsuLogo
+ {
+ Alpha = 0,
+ RelativePositionAxes = Axes.Both,
+ Depth = float.MinValue,
+ Position = new Vector2(0.5f),
+ }
+ };
+
+ AddStep("restart sequence", () =>
+ {
+ logo.FinishTransforms();
+ logo.IsTracking = false;
+
+ introStack?.Expire();
+
+ Add(introStack = new OsuScreenStack(CreateScreen())
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+ });
+ }
+
+ protected abstract IScreen CreateScreen();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
new file mode 100644
index 0000000000..107734cc8d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.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 NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneIntroCircles : IntroTestScene
+ {
+ protected override IScreen CreateScreen() => new IntroCircles();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs
deleted file mode 100644
index b59fb18428..0000000000
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs
+++ /dev/null
@@ -1,54 +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 NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Timing;
-using osu.Game.Screens.Menu;
-using osuTK.Graphics;
-
-namespace osu.Game.Tests.Visual.Menus
-{
- [TestFixture]
- public class TestSceneIntroSequence : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(OsuLogo),
- };
-
- public TestSceneIntroSequence()
- {
- OsuLogo logo;
-
- var rateAdjustClock = new StopwatchClock(true);
- var framedClock = new FramedClock(rateAdjustClock);
- framedClock.ProcessFrame();
-
- Add(new Container
- {
- RelativeSizeAxes = Axes.Both,
- Clock = framedClock,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- logo = new OsuLogo
- {
- Anchor = Anchor.Centre,
- }
- }
- });
-
- AddStep(@"Restart", logo.PlayIntro);
- AddSliderStep("Playback speed", 0.0, 2.0, 1, v => rateAdjustClock.Rate = v);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index 0c789d8cb7..f24589ed35 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Menus
{
typeof(ToolbarButton),
typeof(ToolbarRulesetSelector),
- typeof(ToolbarRulesetButton),
+ typeof(ToolbarRulesetTabButton),
typeof(ToolbarNotificationButton),
};
public TestSceneToolbar()
{
- var toolbar = new Toolbar { State = Visibility.Visible };
+ var toolbar = new Toolbar { State = { Value = Visibility.Visible } };
ToolbarNotificationButton notificationButton = null;
AddStep("create toolbar", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index 8091e93471..8d842fc865 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
settings = new TestRoomSettings
{
RelativeSizeAxes = Axes.Both,
- State = Visibility.Visible
+ State = { Value = Visibility.Visible }
};
Child = settings;
diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
index a7e725ec3f..35449f5687 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online
api.Logout();
api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true);
- AddStep("show", () => accountCreation.State = Visibility.Visible);
+ AddStep("show", () => accountCreation.Show());
AddStep("logout", () => api.Logout());
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs
new file mode 100644
index 0000000000..fe94165777
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs
@@ -0,0 +1,95 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneBeatmapAvailability : OsuTestScene
+ {
+ private readonly BeatmapAvailability container;
+
+ public TestSceneBeatmapAvailability()
+ {
+ Add(container = new BeatmapAvailability());
+ }
+
+ [Test]
+ public void TestUndownloadableWithLink()
+ {
+ AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Availability = new BeatmapSetOnlineAvailability
+ {
+ DownloadDisabled = true,
+ ExternalLink = @"https://osu.ppy.sh",
+ },
+ },
+ });
+
+ visiblityAssert(true);
+ }
+
+ [Test]
+ public void TestUndownloadableNoLink()
+ {
+ AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Availability = new BeatmapSetOnlineAvailability
+ {
+ DownloadDisabled = true,
+ },
+ },
+ });
+
+ visiblityAssert(true);
+ }
+
+ [Test]
+ public void TestPartsRemovedWithLink()
+ {
+ AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Availability = new BeatmapSetOnlineAvailability
+ {
+ DownloadDisabled = false,
+ ExternalLink = @"https://osu.ppy.sh",
+ },
+ },
+ });
+
+ visiblityAssert(true);
+ }
+
+ [Test]
+ public void TestNormal()
+ {
+ AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Availability = new BeatmapSetOnlineAvailability
+ {
+ DownloadDisabled = false,
+ },
+ },
+ });
+
+ visiblityAssert(false);
+ }
+
+ private void visiblityAssert(bool shown)
+ {
+ AddAssert($"is container {(shown ? "visible" : "hidden")}", () => container.Alpha == (shown ? 1 : 0));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index 5910da7b88..daee419b52 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneBeatmapSetOverlay : OsuTestScene
{
- private readonly BeatmapSetOverlay overlay;
+ private readonly TestBeatmapSetOverlay overlay;
public override IReadOnlyList RequiredTypes => new[]
{
@@ -32,375 +32,204 @@ namespace osu.Game.Tests.Visual.Online
typeof(BasicStats),
typeof(BeatmapPicker),
typeof(Details),
- typeof(DownloadButton),
+ typeof(HeaderDownloadButton),
typeof(FavouriteButton),
typeof(Header),
typeof(HeaderButton),
typeof(Info),
typeof(PreviewButton),
typeof(SuccessRate),
+ typeof(BeatmapAvailability),
};
+ private RulesetInfo taikoRuleset;
+ private RulesetInfo maniaRuleset;
+
public TestSceneBeatmapSetOverlay()
{
- Add(overlay = new BeatmapSetOverlay());
+ Add(overlay = new TestBeatmapSetOverlay());
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
- var mania = rulesets.GetRuleset(3);
- var taiko = rulesets.GetRuleset(1);
+ taikoRuleset = rulesets.GetRuleset(1);
+ maniaRuleset = rulesets.GetRuleset(3);
+ }
+ [Test]
+ public void TestLoading()
+ {
AddStep(@"show loading", () => overlay.ShowBeatmapSet(null));
+ }
+ [Test]
+ public void TestOnline()
+ {
AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55));
+ }
+ [Test]
+ public void TestLocalBeatmaps()
+ {
AddStep(@"show first", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
+ OnlineBeatmapSetID = 1235,
Metadata = new BeatmapMetadata
{
- Title = @"Lachryma ",
- Artist = @"Kaneko Chiharu",
- Source = @"SOUND VOLTEX III GRAVITY WARS",
- Tags = @"sdvx grace the 5th kac original song contest konami bemani",
+ Title = @"an awesome beatmap",
+ Artist = @"naru narusegawa",
+ Source = @"hinata sou",
+ Tags = @"test tag tag more tag",
Author = new User
{
- Username = @"Fresh Chicken",
- Id = 3984370,
+ Username = @"BanchoBot",
+ Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
- Preview = @"https://b.ppy.sh/preview/415886.mp3",
- PlayCount = 681380,
- FavouriteCount = 356,
- Submitted = new DateTime(2016, 2, 10),
- Ranked = new DateTime(2016, 6, 19),
- Status = BeatmapSetOnlineStatus.Ranked,
- BPM = 236,
+ Preview = @"https://b.ppy.sh/preview/12345.mp3",
+ PlayCount = 123,
+ FavouriteCount = 456,
+ Submitted = DateTime.Now,
+ Ranked = DateTime.Now,
+ BPM = 111,
HasVideo = true,
- Covers = new BeatmapSetOnlineCovers
- {
- Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778",
- },
+ HasStoryboard = true,
+ Covers = new BeatmapSetOnlineCovers(),
},
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List
{
new BeatmapInfo
{
- StarDifficulty = 1.36,
- Version = @"BASIC",
- Ruleset = mania,
+ StarDifficulty = 9.99,
+ Version = @"TEST",
+ Length = 456000,
+ Ruleset = maniaRuleset,
BaseDifficulty = new BeatmapDifficulty
{
- CircleSize = 4,
- DrainRate = 6.5f,
- OverallDifficulty = 6.5f,
- ApproachRate = 5,
+ CircleSize = 1,
+ DrainRate = 2.3f,
+ OverallDifficulty = 4.5f,
+ ApproachRate = 6,
},
OnlineInfo = new BeatmapOnlineInfo
{
- Length = 115000,
- CircleCount = 265,
- SliderCount = 71,
- PlayCount = 47906,
- PassCount = 19899,
+ CircleCount = 111,
+ SliderCount = 12,
+ PlayCount = 222,
+ PassCount = 21,
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 2.22,
- Version = @"NOVICE",
- Ruleset = mania,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 4,
- DrainRate = 7,
- OverallDifficulty = 7,
- ApproachRate = 5,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 118000,
- CircleCount = 592,
- SliderCount = 62,
- PlayCount = 162021,
- PassCount = 72116,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 3.49,
- Version = @"ADVANCED",
- Ruleset = mania,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 4,
- DrainRate = 7.5f,
- OverallDifficulty = 7.5f,
- ApproachRate = 5,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 118000,
- CircleCount = 1042,
- SliderCount = 79,
- PlayCount = 225178,
- PassCount = 73001,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 4.24,
- Version = @"EXHAUST",
- Ruleset = mania,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 4,
- DrainRate = 8,
- OverallDifficulty = 8,
- ApproachRate = 5,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 118000,
- CircleCount = 1352,
- SliderCount = 69,
- PlayCount = 131545,
- PassCount = 42703,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 5.26,
- Version = @"GRAVITY",
- Ruleset = mania,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 4,
- DrainRate = 8.5f,
- OverallDifficulty = 8.5f,
- ApproachRate = 5,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 118000,
- CircleCount = 1730,
- SliderCount = 115,
- PlayCount = 117673,
- PassCount = 24241,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
});
});
- AddStep(@"show second", () =>
+ downloadAssert(true);
+ }
+
+ [Test]
+ public void TestAvailability()
+ {
+ AddStep(@"show undownloadable", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
+ OnlineBeatmapSetID = 1234,
Metadata = new BeatmapMetadata
{
- Title = @"Soumatou Labyrinth",
- Artist = @"Yunomi with Momobako&miko",
- Tags = @"mmbk.com yuzu__rinrin charlotte",
+ Title = @"undownloadable beatmap",
+ Artist = @"no one",
+ Source = @"some source",
+ Tags = @"another test tag tag more test tags",
Author = new User
{
- Username = @"komasy",
- Id = 1980256,
+ Username = @"BanchoBot",
+ Id = 3,
},
},
OnlineInfo = new BeatmapSetOnlineInfo
{
- Preview = @"https://b.ppy.sh/preview/625493.mp3",
- PlayCount = 22996,
- FavouriteCount = 58,
- Submitted = new DateTime(2016, 6, 11),
- Ranked = new DateTime(2016, 7, 12),
- Status = BeatmapSetOnlineStatus.Pending,
- BPM = 160,
- HasVideo = false,
- Covers = new BeatmapSetOnlineCovers
+ Availability = new BeatmapSetOnlineAvailability
{
- Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472",
+ DownloadDisabled = true,
+ ExternalLink = "https://osu.ppy.sh",
},
+ Preview = @"https://b.ppy.sh/preview/1234.mp3",
+ PlayCount = 123,
+ FavouriteCount = 456,
+ Submitted = DateTime.Now,
+ Ranked = DateTime.Now,
+ BPM = 111,
+ HasVideo = true,
+ HasStoryboard = true,
+ Covers = new BeatmapSetOnlineCovers(),
},
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() },
Beatmaps = new List
{
new BeatmapInfo
{
- StarDifficulty = 1.40,
- Version = @"yzrin's Kantan",
- Ruleset = taiko,
+ StarDifficulty = 5.67,
+ Version = @"ANOTHER TEST",
+ Length = 123000,
+ Ruleset = taikoRuleset,
BaseDifficulty = new BeatmapDifficulty
{
- CircleSize = 2,
- DrainRate = 7,
- OverallDifficulty = 3,
- ApproachRate = 10,
+ CircleSize = 9,
+ DrainRate = 8,
+ OverallDifficulty = 7,
+ ApproachRate = 6,
},
OnlineInfo = new BeatmapOnlineInfo
{
- Length = 193000,
- CircleCount = 262,
- SliderCount = 0,
- PlayCount = 3952,
- PassCount = 1373,
+ CircleCount = 123,
+ SliderCount = 45,
+ PlayCount = 567,
+ PassCount = 89,
},
Metrics = new BeatmapMetrics
{
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 2.23,
- Version = @"Futsuu",
- Ruleset = taiko,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 2,
- DrainRate = 6,
- OverallDifficulty = 4,
- ApproachRate = 10,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 193000,
- CircleCount = 464,
- SliderCount = 0,
- PlayCount = 4833,
- PassCount = 920,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 3.19,
- Version = @"Muzukashii",
- Ruleset = taiko,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 2,
- DrainRate = 6,
- OverallDifficulty = 5,
- ApproachRate = 10,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 193000,
- CircleCount = 712,
- SliderCount = 0,
- PlayCount = 4405,
- PassCount = 854,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 3.97,
- Version = @"Charlotte's Oni",
- Ruleset = taiko,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 5,
- DrainRate = 6,
- OverallDifficulty = 5.5f,
- ApproachRate = 10,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 193000,
- CircleCount = 943,
- SliderCount = 0,
- PlayCount = 3950,
- PassCount = 693,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
- },
- },
- new BeatmapInfo
- {
- StarDifficulty = 5.08,
- Version = @"Labyrinth Oni",
- Ruleset = taiko,
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 5,
- DrainRate = 5,
- OverallDifficulty = 6,
- ApproachRate = 10,
- },
- OnlineInfo = new BeatmapOnlineInfo
- {
- Length = 193000,
- CircleCount = 1068,
- SliderCount = 0,
- PlayCount = 5856,
- PassCount = 1207,
- },
- Metrics = new BeatmapMetrics
- {
- Ratings = Enumerable.Range(0, 11),
- Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6),
- Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6),
+ Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
});
});
+ downloadAssert(false);
+ }
+
+ [Test]
+ public void TestHide()
+ {
AddStep(@"hide", overlay.Hide);
+ }
+
+ [Test]
+ public void TestShowWithNoReload()
+ {
AddStep(@"show without reload", overlay.Show);
}
+
+ private void downloadAssert(bool shown)
+ {
+ AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown);
+ }
+
+ private class TestBeatmapSetOverlay : BeatmapSetOverlay
+ {
+ public bool DownloadButtonsVisible => Header.DownloadButtonsVisible;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
new file mode 100644
index 0000000000..2a45e68c0a
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs
@@ -0,0 +1,69 @@
+// 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.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet;
+using osu.Game.Screens.Select.Details;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneBeatmapSetOverlayDetails : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Details)
+ };
+
+ private RatingsExposingDetails details;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = details = new RatingsExposingDetails
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ });
+
+ [Test]
+ public void TestMetrics()
+ {
+ var firstSet = createSet();
+ var secondSet = createSet();
+
+ AddStep("set first set", () => details.BeatmapSet = firstSet);
+ AddAssert("ratings set", () => details.Ratings.Metrics == firstSet.Metrics);
+
+ AddStep("set second set", () => details.BeatmapSet = secondSet);
+ AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics);
+
+ BeatmapSetInfo createSet() => new BeatmapSetInfo
+ {
+ Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() },
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
+ }
+ }
+ }
+ };
+ }
+
+ private class RatingsExposingDetails : Details
+ {
+ public new UserRatings Ratings => base.Ratings;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
new file mode 100644
index 0000000000..05f5c117e4
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
@@ -0,0 +1,82 @@
+// 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.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.BeatmapSet;
+using osu.Game.Screens.Select.Details;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneBeatmapSetOverlaySuccessRate : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Details)
+ };
+
+ private GraphExposingSuccessRate successRate;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(275, 220),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Gray,
+ },
+ successRate = new GraphExposingSuccessRate
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(275, 220),
+ Padding = new MarginPadding(20)
+ }
+ }
+ };
+ });
+
+ [Test]
+ public void TestMetrics()
+ {
+ var firstBeatmap = createBeatmap();
+ var secondBeatmap = createBeatmap();
+
+ AddStep("set first set", () => successRate.Beatmap = firstBeatmap);
+ AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics);
+
+ AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
+ AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
+
+ BeatmapInfo createBeatmap() => new BeatmapInfo
+ {
+ Metrics = new BeatmapMetrics
+ {
+ Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
+ Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
+ }
+ };
+ }
+
+ private class GraphExposingSuccessRate : SuccessRate
+ {
+ public new FailRetryGraph Graph => base.Graph;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index d1a7730bee..cf8bac7642 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ChangelogListing),
typeof(ChangelogSingleBuild),
typeof(ChangelogBuild),
+ typeof(Comments),
};
protected override void LoadComplete()
@@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.Online
{
Version = "2018.712.0",
DisplayVersion = "2018.712.0",
- UpdateStream = new APIUpdateStream { Name = "lazer" },
+ UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME },
ChangelogEntries = new List
{
new APIChangelogEntry
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
index 364c986723..16e47c5df9 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
});
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
- channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString();
+ channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue;
AddStep("Add random private channel", addRandomPrivateChannel);
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs
deleted file mode 100644
index 634176e65f..0000000000
--- a/osu.Game.Tests/Visual/Online/TestSceneChatDisplay.cs
+++ /dev/null
@@ -1,45 +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.ComponentModel;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Online.Chat;
-using osu.Game.Overlays;
-using osu.Game.Overlays.Chat;
-using osu.Game.Overlays.Chat.Tabs;
-
-namespace osu.Game.Tests.Visual.Online
-{
- [Description("Testing chat api and overlay")]
- public class TestSceneChatDisplay : OsuTestScene
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ChatOverlay),
- typeof(ChatLine),
- typeof(DrawableChannel),
- typeof(ChannelSelectorTabItem),
- typeof(ChannelTabControl),
- typeof(ChannelTabItem),
- typeof(PrivateChannelTabItem),
- typeof(TabCloseButton)
- };
-
- [Cached]
- private readonly ChannelManager channelManager = new ChannelManager();
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Children = new Drawable[]
- {
- channelManager,
- new ChatOverlay { State = Visibility.Visible }
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
new file mode 100644
index 0000000000..9196513a55
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -0,0 +1,157 @@
+// 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.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Online.Chat;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Chat;
+using osu.Game.Overlays.Chat.Selection;
+using osu.Game.Overlays.Chat.Tabs;
+using osu.Game.Users;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneChatOverlay : ManualInputManagerTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ChatLine),
+ typeof(DrawableChannel),
+ typeof(ChannelSelectorTabItem),
+ typeof(ChannelTabControl),
+ typeof(ChannelTabItem),
+ typeof(PrivateChannelTabItem),
+ typeof(TabCloseButton)
+ };
+
+ private TestChatOverlay chatOverlay;
+ private ChannelManager channelManager;
+
+ private readonly Channel channel1 = new Channel(new User()) { Name = "test really long username" };
+ private readonly Channel channel2 = new Channel(new User()) { Name = "test2" };
+
+ [SetUp]
+ public void Setup()
+ {
+ Schedule(() =>
+ {
+ ChannelManagerContainer container;
+
+ Child = container = new ChannelManagerContainer(new List { channel1, channel2 })
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ chatOverlay = container.ChatOverlay;
+ channelManager = container.ChannelManager;
+ });
+ }
+
+ [Test]
+ public void TestHideOverlay()
+ {
+ AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
+ AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+
+ AddStep("Close chat overlay", () => chatOverlay.Hide());
+
+ AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden);
+ AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+ }
+
+ [Test]
+ public void TestSelectingChannelClosesSelector()
+ {
+ AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+
+ AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
+ AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
+
+ AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
+ AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+ }
+
+ [Test]
+ public void TestCloseChannelWhileSelectorClosed()
+ {
+ AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
+ AddStep("Join channel 2", () => channelManager.JoinChannel(channel2));
+
+ AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2]));
+ AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child));
+
+ AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
+ AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1);
+
+ AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child));
+
+ AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+ }
+
+ private void clickDrawable(Drawable d)
+ {
+ InputManager.MoveMouseTo(d);
+ InputManager.Click(MouseButton.Left);
+ }
+
+ private class ChannelManagerContainer : Container
+ {
+ public TestChatOverlay ChatOverlay { get; private set; }
+
+ [Cached]
+ public ChannelManager ChannelManager { get; } = new ChannelManager();
+
+ private readonly List channels;
+
+ public ChannelManagerContainer(List channels)
+ {
+ this.channels = channels;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ((BindableList)ChannelManager.AvailableChannels).AddRange(channels);
+
+ Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, };
+ ChatOverlay.Show();
+ }
+ }
+
+ private class TestChatOverlay : ChatOverlay
+ {
+ public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value;
+
+ public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay;
+
+ protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl();
+
+ public IReadOnlyDictionary