diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000000..0c6b80e97e
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+custom: https://osu.ppy.sh/home/support
diff --git a/.github/ISSUE_TEMPLATE/00-mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md
new file mode 100644
index 0000000000..f171e80b8b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/00-mobile-issues.md
@@ -0,0 +1,8 @@
+---
+name: Mobile Report
+about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!)
+---
+
+⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them.
+If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome.
+Otherwise, please check back in the future when the focus of development shifts towards mobile!
diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
similarity index 100%
rename from .github/ISSUE_TEMPLATE/bug-issues.md
rename to .github/ISSUE_TEMPLATE/01-bug-issues.md
diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md
similarity index 100%
rename from .github/ISSUE_TEMPLATE/crash-issues.md
rename to .github/ISSUE_TEMPLATE/02-crash-issues.md
diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/03-feature-request-issues.md
similarity index 100%
rename from .github/ISSUE_TEMPLATE/feature-request-issues.md
rename to .github/ISSUE_TEMPLATE/03-feature-request-issues.md
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..69baeee60c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: osu!stable issues
+ url: https://github.com/ppy/osu-stable-issues
+ about: For issues regarding osu!stable (not osu!lazer), open them here.
diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md
deleted file mode 100644
index 5822da9c65..0000000000
--- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-name: Missing for Live
-about: Features which are available in osu!stable but not yet in osu!lazer.
----
-**Describe the missing feature:**
-
-**Proposal designs of the feature:**
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 67%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 2eff16cc91..5372b6f28a 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 67%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index cae9754560..45a94f37c0 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 67%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml
rename to .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 49ec93e1b3..1f09381e08 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 67%
rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml
rename to .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index d0964c6f68..ba530f0ddf 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..89d5b45f67
--- /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..f1d0957b8e 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
similarity index 66%
rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests.xml
rename to .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml
index 95cb4c0e62..23b49abcad 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..5940df2191 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,53 +1,18 @@
{
"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",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -58,13 +23,118 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "osu! (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ }, {
+ "name": "osu! (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build osu! (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Debug)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Debug)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ }
+ },
+ "console": "internalConsole"
+ },
+ {
+ "name": "Tournament (Tests, Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "--tournament"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build tournament tests (Release)",
+ "linux": {
+ "env": {
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -84,4 +154,4 @@
"externalConsole": false
}
]
-}
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index de799a7c03..70e620bca2 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",
@@ -66,7 +65,37 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Restore (netcoreapp2.2)",
+ "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 (netcoreapp3.0)",
"type": "shell",
"command": "dotnet",
"args": [
diff --git a/Gemfile.lock b/Gemfile.lock
index 17c0db12e7..ab594aee74 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,12 +1,12 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.0)
- addressable (2.6.0)
- public_suffix (>= 2.0.2, < 4.0)
+ CFPropertyList (3.0.1)
+ addressable (2.7.0)
+ public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
- babosa (1.0.2)
- claide (1.0.2)
+ babosa (1.0.3)
+ claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
@@ -14,11 +14,11 @@ GEM
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
- domain_name (0.5.20180417)
+ domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- dotenv (2.7.1)
+ dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.62.0)
+ excon (0.67.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@@ -26,8 +26,8 @@ GEM
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
- fastimage (2.1.5)
- fastlane (2.117.0)
+ fastimage (2.1.7)
+ fastlane (2.133.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -37,34 +37,34 @@ GEM
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
- faraday (~> 0.9)
+ faraday (< 0.16.0)
faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (~> 0.9)
+ faraday_middleware (< 0.16.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
- mini_magick (~> 4.5.1)
- multi_json
+ jwt (~> 2.1.0)
+ mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
- rubyzip (>= 1.2.2, < 2.0.0)
+ rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
- terminal-notifier (>= 1.6.2, < 2.0.0)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
- xcodeproj (>= 1.6.0, < 2.0.0)
+ xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
- fastlane-plugin-clean_testflight_testers (0.2.0)
+ fastlane-plugin-clean_testflight_testers (0.3.0)
fastlane-plugin-souyuz (0.8.1)
souyuz (>= 0.8.1)
fastlane-plugin-xamarin (0.6.3)
@@ -77,9 +77,9 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
- google-cloud-core (1.3.0)
+ google-cloud-core (1.3.1)
google-cloud-env (~> 1.0)
- google-cloud-env (1.0.5)
+ google-cloud-env (1.2.1)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@@ -100,19 +100,19 @@ GEM
json (2.2.0)
jwt (2.1.0)
memoist (0.16.0)
- mime-types (3.2.2)
+ mime-types (3.3)
mime-types-data (~> 3.2015)
- mime-types-data (3.2018.0812)
- mini_magick (4.5.1)
+ mime-types-data (3.2019.1009)
+ mini_magick (4.9.5)
mini_portile2 (2.4.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
- nokogiri (1.10.1)
+ nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
- os (1.0.0)
+ os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@@ -121,14 +121,14 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
- rubyzip (1.2.2)
+ rubyzip (1.3.0)
security (0.1.3)
- signet (0.11.0)
+ signet (0.12.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- simctl (1.6.5)
+ simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)
@@ -136,20 +136,20 @@ GEM
fastlane (>= 2.29.0)
highline (~> 1.7)
nokogiri (~> 1.7)
- terminal-notifier (1.8.0)
+ terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
- tty-cursor (0.6.1)
- tty-screen (0.6.5)
- tty-spinner (0.9.0)
- tty-cursor (~> 0.6.0)
+ tty-cursor (0.7.0)
+ tty-screen (0.7.0)
+ tty-spinner (0.9.1)
+ tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.5)
- unicode-display_width (1.4.1)
+ unf_ext (0.0.7.6)
+ unicode-display_width (1.6.0)
word_wrap (1.0.0)
- xcodeproj (1.8.1)
+ xcodeproj (1.12.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/README.md b/README.md
index abddb1faa1..0460e9cbcf 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+
+
+
+
# osu!
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
@@ -10,27 +14,33 @@ 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.
+- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
+- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
-- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
## Running osu!
### 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) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| ------------- | ------------- | ------------- | ------------- |
+
+- **Linux** users are recommended to self-compile until we have official deployment in place.
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 +55,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,12 +73,12 @@ 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:
```shell
-LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop
+LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop
```
### Testing with resource/framework modifications
@@ -83,11 +93,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/appveyor.yml b/appveyor.yml
index 4dcaa7b45e..f59c0b162d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,8 +1,6 @@
clone_depth: 1
version: '{branch}-{build}'
-image: Previous Visual Studio 2017
+image: Visual Studio 2019 Preview
test: off
-install:
- - cmd: git submodule update --init --recursive --depth=5
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
new file mode 100644
index 0000000000..13635b943c
--- /dev/null
+++ b/appveyor_deploy.yml
@@ -0,0 +1,10 @@
+clone_depth: 1
+version: '{build}'
+image: Visual Studio 2019 Preview
+test: off
+skip_non_tags: true
+build_script:
+ - cmd: PowerShell -Version 2.0 .\build.ps1
+deploy:
+ - provider: Environment
+ name: nuget
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.ps1 b/build.ps1
old mode 100644
new mode 100755
index c6a0bf6d4a..2dbd10a150
--- a/build.ps1
+++ b/build.ps1
@@ -1,39 +1,5 @@
-##########################################################################
-# This is a customized Cake bootstrapper script for PowerShell.
-##########################################################################
-
-<#
-
-.SYNOPSIS
-This is a Powershell script to bootstrap a Cake build.
-
-.DESCRIPTION
-This Powershell script restores NuGet tools (including Cake)
-and execute your Cake build script with the parameters you provide.
-
-.PARAMETER Script
-The build script to execute.
-.PARAMETER Target
-The build script target to run.
-.PARAMETER Configuration
-The build configuration to use.
-.PARAMETER Verbosity
-Specifies the amount of information to be displayed.
-.PARAMETER ShowDescription
-Shows description about tasks.
-.PARAMETER DryRun
-Performs a dry run.
-.PARAMETER ScriptArgs
-Remaining arguments are added here.
-
-.LINK
-https://cakebuild.net
-
-#>
-
[CmdletBinding()]
Param(
- [string]$Script = "build.cake",
[string]$Target,
[string]$Configuration,
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
@@ -45,27 +11,8 @@ Param(
[string[]]$ScriptArgs
)
-Write-Host "Preparing to run build script..."
-
-# Determine the script root for resolving other paths.
-if(!$PSScriptRoot) {
- $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
-}
-
-# Resolve the paths for resources used for debugging.
-$BUILD_DIR = Join-Path $PSScriptRoot "build"
-$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
-$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
-
-# Install the required tools locally.
-Write-Host "Restoring cake tools..."
-Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
-
-# Find the Cake executable
-$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
-
# Build Cake arguments
-$cakeArguments = @("$Script");
+$cakeArguments = "";
if ($Target) { $cakeArguments += "-target=$Target" }
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
@@ -74,9 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" }
if ($Experimental) { $cakeArguments += "-experimental" }
$cakeArguments += $ScriptArgs
-# Start Cake
-Write-Host "Running build script..."
-Push-Location -Path $BUILD_DIR
-Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
-Pop-Location
-exit $LASTEXITCODE
+dotnet tool install Cake.Tool --global --version 0.35.0
+dotnet cake ./build/build.cake --bootstrap
+dotnet cake ./build/build.cake $cakeArguments
+exit $LASTEXITCODE
\ No newline at end of file
diff --git a/build.sh b/build.sh
index 8f1ef5b455..ac6bd877a6 100755
--- a/build.sh
+++ b/build.sh
@@ -1,18 +1,5 @@
-#!/usr/bin/env bash
-
-##########################################################################
-# This is a customized Cake bootstrapper script for Shell.
-##########################################################################
-
-echo "Preparing to run build script..."
-
-cd build
-SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
-TOOLS_DIR=$SCRIPT_DIR/tools
-CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
-
-SCRIPT="build.cake"
-CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
+echo "Installing Cake.Tool..."
+dotnet tool install Cake.Tool --global --version 0.35.0
# Parse arguments.
CAKE_ARGUMENTS=()
@@ -25,14 +12,6 @@ for i in "$@"; do
shift
done
-# Install the required tools locally.
-echo "Restoring cake tools..."
-dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1
-
-# Search for the CakeBuild binary.
-CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll")
-
-# Start Cake
echo "Running build script..."
-
-dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}"
+dotnet cake ./build/build.cake --bootstrap
+dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"
\ No newline at end of file
diff --git a/build/build.cake b/build/build.cake
index 1d2588de49..cfdfebee61 100644
--- a/build/build.cake
+++ b/build/build.cake
@@ -1,5 +1,5 @@
-#addin "nuget:?package=CodeFileSanity&version=0.0.21"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1"
+#addin "nuget:?package=CodeFileSanity&version=0.0.33"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
diff --git a/build/cakebuild.csproj b/build/cakebuild.csproj
deleted file mode 100644
index 8ccce35e26..0000000000
--- a/build/cakebuild.csproj
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- Exe
- true
- netcoreapp2.0
-
-
-
-
-
-
\ No newline at end of file
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 3f64bcdf19..7adf42a1eb 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -1,22 +1,84 @@
update_fastlane
-default_platform(:ios)
+platform :android do
+desc 'Deploy to play store'
+ lane :beta do |options|
-platform :ios do
- lane :testflight_prune_dry do
- clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ update_version(
+ version: options[:version],
+ build: options[:build],
+ )
+
+ build(options)
+
+ supply(
+ apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk',
+ package_name: 'sh.ppy.osulazer',
+ track: 'alpha', # upload to alpha, we can promote it later
+ json_key: options[:json_key],
+ )
end
- # Specify a custom number for what's "inactive"
- lane :testflight_prune do
- clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months
+ desc 'Deploy to github release'
+ lane :build_github do |options|
+
+ update_version(
+ version: options[:version],
+ build: options[:build],
+ )
+
+ build(options)
+
+ client = HTTPClient.new
+ changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw'
+ changelog.gsub!('$BUILD_ID', options[:build])
+
+ set_github_release(
+ repository_name: "ppy/osu",
+ api_token: ENV["GITHUB_TOKEN"],
+ name: options[:build],
+ tag_name: options[:build],
+ is_draft: true,
+ description: changelog,
+ commitish: "master",
+ upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"]
+ )
+
+ end
+
+ desc 'Compile the project'
+ lane :build do |options|
+ nuget_restore(
+ project_path: 'osu.Android.sln'
+ )
+
+ souyuz(
+ build_configuration: 'Release',
+ solution_path: 'osu.Android.sln',
+ platform: "android",
+ output_path: "osu.Android/bin/Release/",
+ keystore_path: options[:keystore_path],
+ keystore_alias: options[:keystore_alias],
+ keystore_password: ENV["KEYSTORE_PASSWORD"]
+ )
end
lane :update_version do |options|
- options[:plist_path] = '../osu.iOS/Info.plist'
- app_version(options)
+
+ split = options[:build].split('.')
+ split[1] = split[1].to_s.rjust(4, '0')
+ android_build = split.join('')
+
+ app_version(
+ solution_path: 'osu.Android.sln',
+ version: options[:version],
+ build: android_build,
+ )
end
+end
+
+platform :ios do
desc 'Deploy to testflight'
lane :beta do |options|
update_version(options)
@@ -35,7 +97,7 @@ platform :ios do
changelog.gsub!('$BUILD_ID', options[:build])
pilot(
- wait_processing_interval: 900,
+ wait_processing_interval: 1800,
changelog: changelog,
ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
)
@@ -62,4 +124,17 @@ platform :ios do
match(options)
end
+
+ lane :update_version do |options|
+ options[:plist_path] = '../osu.iOS/Info.plist'
+ app_version(options)
+ end
+
+ lane :testflight_prune_dry do
+ clean_testflight_testers(days_of_inactivity:45, dry_run: true)
+ end
+
+ lane :testflight_prune do
+ clean_testflight_testers(days_of_inactivity: 45)
+ end
end
diff --git a/fastlane/README.md b/fastlane/README.md
index 53bbc62cae..a400ed9516 100644
--- a/fastlane/README.md
+++ b/fastlane/README.md
@@ -15,22 +15,31 @@ Install _fastlane_ using
or alternatively using `brew cask install fastlane`
# Available Actions
+## Android
+### android beta
+```
+fastlane android beta
+```
+Deploy to play store
+### android build_github
+```
+fastlane android build_github
+```
+Deploy to github release
+### android build
+```
+fastlane android build
+```
+Compile the project
+### android update_version
+```
+fastlane android update_version
+```
+
+
+----
+
## iOS
-### ios testflight_prune_dry
-```
-fastlane ios testflight_prune_dry
-```
-
-### ios testflight_prune
-```
-fastlane ios testflight_prune
-```
-
-### ios update_version
-```
-fastlane ios update_version
-```
-
### ios beta
```
fastlane ios beta
@@ -46,6 +55,21 @@ Compile the project
fastlane ios provision
```
Install provisioning profiles using match
+### ios update_version
+```
+fastlane ios update_version
+```
+
+### ios testflight_prune_dry
+```
+fastlane ios testflight_prune_dry
+```
+
+### ios testflight_prune
+```
+fastlane ios testflight_prune
+```
+
----
diff --git a/osu.Android.props b/osu.Android.props
new file mode 100644
index 0000000000..8b31be3f12
--- /dev/null
+++ b/osu.Android.props
@@ -0,0 +1,67 @@
+
+
+ Debug
+ AnyCPU
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..5a97fc7518
--- /dev/null
+++ b/osu.Android.sln.DotSettings
@@ -0,0 +1,834 @@
+
+ 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)
+ Required
+ Required
+ Required
+ Explicit
+ ExpressionBody
+ ExpressionBody
+ True
+ NEXT_LINE
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
+ NEXT_LINE
+ 1
+ 1
+ True
+ NEXT_LINE
+ NEVER
+ NEVER
+ True
+ False
+ True
+ NEVER
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ 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..2e5fa59d20
--- /dev/null
+++ b/osu.Android/OsuGameActivity.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using 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)
+ {
+ // The default current directory on android is '/'.
+ // On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
+ // In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
+ System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
+
+ 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..a91c010809
--- /dev/null
+++ b/osu.Android/OsuGameAndroid.cs
@@ -0,0 +1,40 @@
+// 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;
+using osu.Game.Updater;
+
+namespace osu.Android
+{
+ public class OsuGameAndroid : OsuGame
+ {
+ public override Version AssemblyVersion
+ {
+ get
+ {
+ var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0);
+
+ try
+ {
+ string versionName = packageInfo.VersionCode.ToString();
+ // undo play store version garbling
+ return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
+ }
+ catch
+ {
+ }
+
+ return new Version(packageInfo.VersionName);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Add(new SimpleUpdateManager());
+ }
+ }
+}
\ No newline at end of file
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/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..7725ee6451 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;
@@ -18,6 +17,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform.Windows;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
+using osu.Game.Updater;
namespace osu.Desktop
{
@@ -39,9 +39,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 +53,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 +68,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 e9c5d06f3c..8c759f8487 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -1,40 +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 System;
using osu.Framework.Allocation;
+using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
-using osu.Framework.Platform;
using osu.Game;
-using osu.Game.Configuration;
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;
namespace osu.Desktop.Overlays
{
- public class VersionManager : OverlayContainer
+ public class VersionManager : VisibilityContainer
{
- private OsuConfigManager config;
- private OsuGameBase game;
- private NotificationOverlay notificationOverlay;
- private GameHost host;
-
[BackgroundDependencyLoader]
- private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host)
+ private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
{
- notificationOverlay = notification;
- this.config = config;
- this.game = game;
- this.host = host;
-
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
@@ -65,7 +50,7 @@ namespace osu.Desktop.Overlays
},
new OsuSpriteText
{
- Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
+ Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White,
Text = game.Version
},
}
@@ -89,43 +74,6 @@ namespace osu.Desktop.Overlays
};
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- var version = game.Version;
- var lastVersion = config.Get(OsuSetting.Version);
-
- if (game.IsDeployedBuild && version != lastVersion)
- {
- config.Set(OsuSetting.Version, version);
-
- // only show a notification if we've previously saved a version to the config file (ie. not the first run).
- if (!string.IsNullOrEmpty(lastVersion))
- notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally));
- }
- }
-
- private class UpdateCompleteNotification : SimpleNotification
- {
- public UpdateCompleteNotification(string version, Action openUrl = null)
- {
- Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
- Icon = FontAwesome.Solid.CheckSquare;
- Activated = delegate
- {
- openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}");
- return true;
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- IconBackgound.Colour = colours.BlueDark;
- }
- }
-
protected override void PopIn()
{
this.FadeIn(1400, Easing.OutQuint);
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 9681350ade..60b47a8b3a 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -1,9 +1,7 @@
-// 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;
-using System.IO;
-using System.Reflection;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -22,16 +20,14 @@ using LogLevel = Splat.LogLevel;
namespace osu.Desktop.Updater
{
- public class SquirrelUpdateManager : Component
+ public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
{
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)
@@ -48,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
{
@@ -83,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!");
}
}
@@ -102,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);
}
@@ -136,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,23 +163,14 @@ namespace osu.Desktop.Updater
private class SquirrelLogger : Splat.ILogger, IDisposable
{
- private readonly string path;
- private readonly object locker = new object();
public LogLevel Level { get; set; } = LogLevel.Info;
- public SquirrelLogger()
- {
- var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location ?? Directory.GetCurrentDirectory()), "SquirrelSetupUpdater.log");
- if (File.Exists(file)) File.Delete(file);
- path = file;
- }
-
public void Write(string message, LogLevel logLevel)
{
if (logLevel < Level)
return;
- lock (locker) File.AppendAllText(path, message + "\r\n");
+ logger.Add(message);
}
public void Dispose()
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..2d1282634f 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,7 +1,7 @@
- netcoreapp2.2
+ netcoreapp3.0
WinExe
AnyCPU
true
@@ -17,18 +17,19 @@
osu.Desktop.Program
+
-
+
-
+
-
-
+
+
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.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
index 37e7c45a4e..7990c35e09 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
index 5dfaf5ec39..4030d2d9e7 100644
--- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 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/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
new file mode 100644
index 0000000000..04e6dea376
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
@@ -0,0 +1,29 @@
+// 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.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class CatchLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
similarity index 88%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index 102afa9ca6..ab3c040b4e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -13,21 +13,21 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestCaseAutoJuiceStream : PlayerTestCase
+ public class TestSceneAutoJuiceStream : PlayerTestScene
{
- public TestCaseAutoJuiceStream()
+ public TestSceneAutoJuiceStream()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
similarity index 84%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index d413b53d17..0ad72412fc 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -13,7 +13,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseBananaShower : PlayerTestCase
+ public class TestSceneBananaShower : PlayerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -24,19 +24,19 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableCatchRuleset),
};
- public TestCaseBananaShower()
+ public TestSceneBananaShower()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
similarity index 78%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
index 5b242d05d7..9836a7811a 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
@@ -7,9 +7,9 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatchPlayer : PlayerTestCase
+ public class TestSceneCatchPlayer : PlayerTestScene
{
- public TestCaseCatchPlayer()
+ public TestSceneCatchPlayer()
: base(new CatchRuleset())
{
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
similarity index 80%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 5a16a23a4e..9ce46ad6ba 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -9,21 +9,21 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatchStacker : PlayerTestCase
+ public class TestSceneCatchStacker : PlayerTestScene
{
- public TestCaseCatchStacker()
+ public TestSceneCatchStacker()
: base(new CatchRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
new file mode 100644
index 0000000000..720ef1db42
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -0,0 +1,106 @@
+// 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.Bindables;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+
+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(ISkinComponent component)
+ {
+ switch (component.LookupName)
+ {
+ case "Gameplay/catch/fruit-catcher-idle":
+ return new CatcherCustomSkin();
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) =>
+ throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName) =>
+ throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
similarity index 95%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 5e3fcd239f..3ae6886c31 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -13,7 +13,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseCatcherArea : OsuTestCase
+ public class TestSceneCatcherArea : OsuTestScene
{
private RulesetInfo catchRuleset;
private TestCatcherArea catcherArea;
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(CatcherArea),
};
- public TestCaseCatcherArea()
+ public TestSceneCatcherArea()
{
AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
new file mode 100644
index 0000000000..0369b6db4e
--- /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.Add(0, 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/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
similarity index 96%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index a59f4ce150..44517382f7 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -15,7 +15,7 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseFruitObjects : OsuTestCase
+ public class TestSceneFruitObjects : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(Pulp),
};
- public TestCaseFruitObjects()
+ public TestSceneFruitObjects()
{
Add(new GridContainer
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
similarity index 80%
rename from osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs
rename to osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index a7e7f0ab14..7b8c699f2c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.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;
@@ -10,26 +9,28 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestCaseHyperDash : PlayerTestCase
+ public class TestSceneHyperDash : PlayerTestScene
{
- public TestCaseHyperDash()
+ public TestSceneHyperDash()
: base(new CatchRuleset())
{
}
- [BackgroundDependencyLoader]
- private void load()
+ protected override bool Autoplay => true;
+
+ [Test]
+ public void TestHyperDash()
{
AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo =
{
- Ruleset = ruleset.RulesetInfo,
+ Ruleset = ruleset,
BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f }
}
};
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 3f8b3bf086..4b629902cb 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,14 +2,14 @@
-
-
-
+
+
+
WinExe
- netcoreapp2.2
+ netcoreapp3.0
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..71d68ace94 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;
@@ -24,10 +23,12 @@ namespace osu.Game.Rulesets.Catch
{
public class CatchRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
+ public const string SHORT_NAME = "fruits";
+
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
@@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new CatchModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new CatchModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
@@ -66,14 +72,8 @@ namespace osu.Game.Rulesets.Catch
if (mods.HasFlag(LegacyMods.NoFail))
yield return new CatchModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new CatchModPerfect();
-
if (mods.HasFlag(LegacyMods.Relax))
yield return new CatchModRelax();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new CatchModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
@@ -108,7 +108,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:
@@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch";
- public override string ShortName => "fruits";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponent.cs b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
new file mode 100644
index 0000000000..8bf53e53e3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch
+{
+ public class CatchSkinComponent : GameplaySkinComponent
+ {
+ public CatchSkinComponent(CatchSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => "catch"; // todo: use CatchRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
new file mode 100644
index 0000000000..7e482d4045
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Catch
+{
+ public enum CatchSkinComponents
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index bd647fd667..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
};
}
@@ -57,33 +58,20 @@ namespace osu.Game.Rulesets.Catch.Difficulty
CatchHitObject lastObject = null;
- foreach (var hitObject in beatmap.HitObjects.OfType())
+ // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
+ foreach (var hitObject in beatmap.HitObjects
+ .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj })
+ .Cast()
+ .OrderBy(x => x.StartTime))
{
- if (lastObject == null)
- {
- lastObject = hitObject;
+ // We want to only consider fruits that contribute to the combo.
+ if (hitObject is BananaShower || hitObject is TinyDroplet)
continue;
- }
- switch (hitObject)
- {
- // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
- case Fruit fruit:
- yield return new CatchDifficultyHitObject(fruit, lastObject, clockRate, halfCatchWidth);
+ if (lastObject != null)
+ yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth);
- lastObject = hitObject;
- break;
-
- case JuiceStream _:
- foreach (var nested in hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)))
- {
- yield return new CatchDifficultyHitObject(nested, lastObject, clockRate, halfCatchWidth);
-
- lastObject = nested;
- }
-
- break;
- }
+ lastObject = hitObject;
}
}
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..e4ad49ea50 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -1,10 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
@@ -12,7 +15,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; }
@@ -22,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboOffset { get; set; }
- public int IndexInCurrentCombo { get; set; }
+ public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
- public int ComboIndex { get; set; }
+ public int IndexInCurrentCombo
+ {
+ get => IndexInCurrentComboBindable.Value;
+ set => IndexInCurrentComboBindable.Value = value;
+ }
+
+ public Bindable ComboIndexBindable { get; } = new Bindable();
+
+ public int ComboIndex
+ {
+ get => ComboIndexBindable.Value;
+ set => ComboIndexBindable.Value = value;
+ }
///
/// Difference between the distance to the next object
@@ -33,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects
///
public float DistanceToHyperDash { get; set; }
+ public Bindable LastInComboBindable { get; } = new Bindable();
+
///
/// The next fruit starts a new combo. Used for explodey.
///
- public virtual bool LastInCombo { get; set; }
+ public virtual bool LastInCombo
+ {
+ get => LastInComboBindable.Value;
+ set => LastInComboBindable.Value = value;
+ }
public float Scale { get; set; } = 1;
@@ -54,10 +88,12 @@ 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;
}
- protected override HitWindows CreateHitWindows() => null;
+ protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
public enum FruitVisualRepresentation
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
index 42646851d7..ea415e18fa 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
@@ -2,35 +2,50 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBananaShower : DrawableCatchHitObject
{
+ private readonly Func> createDrawableRepresentation;
private readonly Container bananaContainer;
public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null)
: base(s)
{
+ this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
X = 0;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
-
- foreach (var b in s.NestedHitObjects.Cast())
- AddNested(createDrawableRepresentation?.Invoke(b));
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
- bananaContainer.Add(h);
- base.AddNested(h);
+ base.AddNestedHitObject(hitObject);
+ bananaContainer.Add(hitObject);
+ }
+
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ bananaContainer.Clear();
+ }
+
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case Banana banana:
+ return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
+ }
+
+ return base.CreateNestedHitObject(hitObject);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 2f8ccec48b..dd4a58a5ef 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
{
@@ -52,6 +50,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public Func CheckPosition;
+ public bool IsOnPlate;
+
+ public override bool RemoveWhenNotAlive => IsOnPlate;
+
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (CheckPosition == null) return;
@@ -60,21 +62,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)
+ protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
+
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(200);
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
- 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 void UpdateState(ArmedState state)
- {
- using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
- this.FadeIn(200);
-
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
using (BeginAbsoluteSequence(endTime, true))
@@ -82,11 +75,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
switch (state)
{
case ArmedState.Miss:
- this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire();
+ this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
case ArmedState.Hit:
- this.FadeOut().Expire();
+ this.FadeOut();
break;
}
}
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..1af77b75fc 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
}
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AccentColour = Color4.Red,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0.5f,
Scale = new Vector2(1.333f)
});
@@ -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/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
index 9e5e9f6a04..a24821b3ce 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -2,38 +2,50 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableJuiceStream : DrawableCatchHitObject
{
+ private readonly Func> createDrawableRepresentation;
private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null)
: base(s)
{
+ this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.Both;
Origin = Anchor.BottomLeft;
X = 0;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
-
- foreach (var o in s.NestedHitObjects.Cast())
- AddNested(createDrawableRepresentation?.Invoke(o));
}
- protected override void AddNested(DrawableHitObject h)
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
- var catchObject = (DrawableCatchHitObject)h;
+ base.AddNestedHitObject(hitObject);
+ dropletContainer.Add(hitObject);
+ }
- catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ dropletContainer.Clear();
+ }
- dropletContainer.Add(h);
- base.AddNested(h);
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case CatchHitObject catchObject:
+ return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
+ }
+
+ return base.CreateNestedHitObject(hitObject);
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
index b9b6d5b924..1e9daf18db 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Colour = Color4.White.Opacity(0.9f);
}
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..6c8515eb90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Catch.Replays
protected Replay Replay;
+ private CatchReplayFrame currentFrame;
+
public override Replay Generate()
{
// todo: add support for HT DT
@@ -35,18 +37,18 @@ namespace osu.Game.Rulesets.Catch.Replays
float lastPosition = 0.5f;
double lastTime = 0;
- // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition));
-
void moveToNext(CatchHitObject h)
{
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;
@@ -55,19 +57,18 @@ namespace osu.Game.Rulesets.Catch.Replays
{
//we are already in the correct range.
lastTime = h.StartTime;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition));
+ addFrame(h.StartTime, lastPosition);
return;
}
- if (h is Banana)
+ if (impossibleJump)
{
- // auto bananas unrealistically warp to catch 100% combo.
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime, h.X);
}
else if (h.HyperDash)
{
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable, lastPosition);
+ addFrame(h.StartTime, h.X);
}
else if (dashRequired)
{
@@ -79,16 +80,16 @@ namespace osu.Game.Rulesets.Catch.Replays
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
+ addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
+ addFrame(h.StartTime, h.X);
}
else
{
double timeBefore = positionChange / movement_speed;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
+ addFrame(h.StartTime - timeBefore, lastPosition);
+ addFrame(h.StartTime, h.X);
}
lastTime = h.StartTime;
@@ -120,5 +121,16 @@ namespace osu.Game.Rulesets.Catch.Replays
return Replay;
}
+
+ private void addFrame(double time, float? position = null, bool dashing = false)
+ {
+ // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
+ if (Replay.Frames.Count == 0)
+ Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
+
+ var last = currentFrame;
+ currentFrame = new CatchReplayFrame(time, position, dashing, last);
+ Replay.Frames.Add(currentFrame);
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 103aa6c3f1..22532bc9ec 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using osu.Framework.Input.StateChanges;
using osu.Framework.MathUtils;
using osu.Game.Replays;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
+ protected override bool IsImportant(CatchReplayFrame frame) => frame.Actions.Any();
protected float? Position
{
@@ -38,21 +39,11 @@ namespace osu.Game.Rulesets.Catch.Replays
{
if (!Position.HasValue) return new List();
- var actions = new List();
-
- if (CurrentFrame.Dashing)
- actions.Add(CatchAction.Dash);
-
- if (Position.Value > CurrentFrame.Position)
- actions.Add(CatchAction.MoveRight);
- else if (Position.Value < CurrentFrame.Position)
- actions.Add(CatchAction.MoveLeft);
-
return new List
{
new CatchReplayState
{
- PressedActions = actions,
+ PressedActions = CurrentFrame?.Actions ?? new List(),
CatcherX = Position.Value
},
};
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index 1e88b35c3b..b41a5e0612 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.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.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Catch.UI;
@@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Replays
{
public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
+ public List Actions = new List();
+
public float Position;
public bool Dashing;
@@ -18,17 +21,40 @@ namespace osu.Game.Rulesets.Catch.Replays
{
}
- public CatchReplayFrame(double time, float? position = null, bool dashing = false)
+ public CatchReplayFrame(double time, float? position = null, bool dashing = false, CatchReplayFrame lastFrame = null)
: base(time)
{
Position = position ?? -1;
Dashing = dashing;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ if (lastFrame != null)
+ {
+ if (Position > lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastFrame.Position)
+ lastFrame.Actions.Add(CatchAction.MoveLeft);
+ }
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
- Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
- Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
+ Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
+ Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
+
+ if (Dashing)
+ Actions.Add(CatchAction.Dash);
+
+ // this probably needs some cross-checking with osu-stable to ensure it is actually correct.
+ if (lastFrame is CatchReplayFrame lastCatchFrame)
+ {
+ if (Position > lastCatchFrame.Position)
+ lastCatchFrame.Actions.Add(CatchAction.MoveRight);
+ else if (Position < lastCatchFrame.Position)
+ Actions.Add(CatchAction.MoveLeft);
+ }
}
}
}
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/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
similarity index 87%
rename from osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
rename to osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
index 837662f5fe..ff793a372e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs
@@ -1,10 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
-namespace osu.Game.Rulesets.Catch.Objects
+namespace osu.Game.Rulesets.Catch.Scoring
{
public class CatchHitWindows : HitWindows
{
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 99b22b2d56..18785d65ea 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index e7c7fd77df..56c8b33e02 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;
@@ -55,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (lastPlateableFruit == null)
return;
- // this is required to make this run after the last caught fruit runs UpdateState at least once.
+ // this is required to make this run after the last caught fruit runs updateState() at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
@@ -71,10 +69,12 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
+ caughtFruit.IsOnPlate = true;
caughtFruit.Anchor = Anchor.TopCentre;
caughtFruit.Origin = Anchor.Centre;
caughtFruit.Scale *= 0.7f;
+ caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit);
@@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- Children = new Drawable[]
+ Children = new[]
{
caughtFruit = new Container
{
@@ -203,16 +203,17 @@ namespace osu.Game.Rulesets.Catch.UI
additive.Scale = Scale;
additive.Colour = HyperDashing ? Color4.Red : Color4.White;
additive.RelativePositionAxes = RelativePositionAxes;
- additive.Blending = BlendingMode.Additive;
+ additive.Blending = BlendingParameters.Additive;
AdditiveTarget.Add(additive);
- additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
+ additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
+ additive.Expire(true);
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.
@@ -302,6 +303,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
+ Trail &= Dashing;
}
}
else
@@ -379,8 +381,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();
@@ -408,6 +410,9 @@ namespace osu.Game.Rulesets.Catch.UI
f.MoveToY(f.Y + 75, 750, Easing.InSine);
f.FadeOut(750);
+
+ // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired.
+ f.LifetimeStart = Time.Current;
f.Expire();
}
}
@@ -438,29 +443,15 @@ namespace osu.Game.Rulesets.Catch.UI
ExplodingFruitTarget.Add(fruit);
}
+ fruit.ClearTransforms();
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine).Then().MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
+ // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired.
+ fruit.LifetimeStart = Time.Current;
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..e3c6c93d01
--- /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("Gameplay/catch/fruit-catcher-idle")
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index f48b84e344..6b7f00c5d0 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
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.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
index 24abccb19d..58c2e2aa5a 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
index 492f894484..779eb4f277 100644
--- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 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/ManiaInputTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
similarity index 93%
rename from osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
rename to osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
index f281883e0c..909d0d45c6 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs
@@ -8,12 +8,12 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
- public abstract class ManiaInputTestCase : OsuTestCase
+ public abstract class ManiaInputTestScene : OsuTestScene
{
private readonly Container content;
protected override Container Content => content ?? base.Content;
- protected ManiaInputTestCase(int keys)
+ protected ManiaInputTestScene(int keys)
{
base.Content.Add(content = new LocalInputManager(keys));
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
new file mode 100644
index 0000000000..957743c5f1
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class ManiaLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(ManiaModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(ManiaModFlashlight), typeof(ManiaModNightcore) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(ManiaModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
+ [TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new ManiaRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
similarity index 75%
rename from osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs
rename to osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
index 9ad22498a9..afde1c9521 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs
@@ -20,15 +20,20 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer
+ public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
{
private readonly Column column;
[Cached(typeof(IReadOnlyList))]
private IReadOnlyList mods { get; set; } = Array.Empty();
- protected ManiaPlacementBlueprintTestCase()
+ [Cached(typeof(IScrollingInfo))]
+ private IScrollingInfo scrollingInfo;
+
+ protected ManiaPlacementBlueprintTestScene()
{
+ scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
+
Add(column = new Column(0)
{
Anchor = Anchor.Centre,
@@ -36,15 +41,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
- }
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
-
- dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
-
- return dependencies;
+ AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
}
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
similarity index 86%
rename from osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs
rename to osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
index a22e599681..b598893e8c 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs
@@ -13,14 +13,14 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[Cached(Type = typeof(IManiaHitObjectComposer))]
- public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase, IManiaHitObjectComposer
+ public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
{
[Cached(Type = typeof(IAdjustableClock))]
private readonly IAdjustableClock clock = new StopwatchClock();
private readonly Column column;
- protected ManiaSelectionBlueprintTestCase()
+ protected ManiaSelectionBlueprintTestScene()
{
Add(column = new Column(0)
{
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs
deleted file mode 100644
index e8a056bbff..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs
+++ /dev/null
@@ -1,179 +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.Linq;
-using NUnit.Framework;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Replays;
-using osu.Game.Rulesets.Replays;
-using osu.Game.Tests.Visual;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestCaseAutoGeneration : OsuTestCase
- {
- [Test]
- public void TestSingleNote()
- {
- // | |
- // | - |
- // | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
- beatmap.HitObjects.Add(new Note { StartTime = 1000 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
- }
-
- [Test]
- public void TestSingleHoldNote()
- {
- // | |
- // | * |
- // | * |
- // | * |
- // | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
- }
-
- [Test]
- public void TestSingleNoteChord()
- {
- // | | |
- // | - | - |
- // | | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new Note { StartTime = 1000 });
- beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
- }
-
- [Test]
- public void TestHoldNoteChord()
- {
- // | | |
- // | * | * |
- // | * | * |
- // | * | * |
- // | | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
- }
-
- [Test]
- public void TestSingleNoteStair()
- {
- // | | |
- // | | - |
- // | - | |
- // | | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new Note { StartTime = 1000 });
- beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
- Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
- Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
- }
-
- [Test]
- public void TestHoldNoteStair()
- {
- // | | |
- // | | * |
- // | * | * |
- // | * | * |
- // | * | |
- // | | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
- Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
- Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
- Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
- }
-
- [Test]
- public void TestHoldNoteWithReleasePress()
- {
- // | | |
- // | * | - |
- // | * | |
- // | * | |
- // | | |
-
- var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
- beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
- beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
-
- var generated = new ManiaAutoGenerator(beatmap).Generate();
-
- Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
- Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
- Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
- Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
- Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
- Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
- Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
- }
-
- private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
new file mode 100644
index 0000000000..a5248c7712
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs
@@ -0,0 +1,188 @@
+// 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.Testing;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ [HeadlessTest]
+ public class TestSceneAutoGeneration : OsuTestScene
+ {
+ ///
+ /// The number of frames which are generated at the start of a replay regardless of hitobject content.
+ ///
+ private const int frame_offset = 1;
+
+ [Test]
+ public void TestSingleNote()
+ {
+ // | |
+ // | - |
+ // | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
+ }
+
+ [Test]
+ public void TestSingleHoldNote()
+ {
+ // | |
+ // | * |
+ // | * |
+ // | * |
+ // | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteChord()
+ {
+ // | | |
+ // | - | - |
+ // | | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteChord()
+ {
+ // | | |
+ // | * | * |
+ // | * | * |
+ // | * | * |
+ // | | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
+
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
+
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
+ }
+
+ [Test]
+ public void TestSingleNoteStair()
+ {
+ // | | |
+ // | | - |
+ // | - | |
+ // | | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ beatmap.HitObjects.Add(new Note { StartTime = 1000 });
+ beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
+ Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteStair()
+ {
+ // | | |
+ // | | * |
+ // | * | * |
+ // | * | * |
+ // | * | |
+ // | | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
+ Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
+ Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
+ }
+
+ [Test]
+ public void TestHoldNoteWithReleasePress()
+ {
+ // | | |
+ // | * | - |
+ // | * | |
+ // | * | |
+ // | | |
+
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
+ beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
+ beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
+
+ var generated = new ManiaAutoGenerator(beatmap).Generate();
+
+ Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
+ Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
+ Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
+ Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
+ }
+
+ private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
similarity index 97%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
index d46b661eea..d94a986dae 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs
@@ -22,7 +22,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- public class TestCaseColumn : ManiaInputTestCase
+ public class TestSceneColumn : ManiaInputTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private readonly List columns = new List();
- public TestCaseColumn()
+ public TestSceneColumn()
: base(2)
{
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
similarity index 92%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
index e721eb6fd9..7ed886be49 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs
@@ -11,11 +11,11 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- public class TestCaseEditor : EditorTestCase
+ public class TestSceneEditor : EditorTestScene
{
private readonly Bindable direction = new Bindable();
- public TestCaseEditor()
+ public TestSceneEditor()
: base(new ManiaRuleset())
{
AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up);
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
new file mode 100644
index 0000000000..26a1b1b1ec
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHitExplosion.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestSceneHitExplosion : OsuTestScene
+ {
+ private ScrollingTestContainer scrolling;
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableNote),
+ typeof(DrawableManiaHitObject),
+ };
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Child = scrolling = new ScrollingTestContainer(ScrollingDirection.Down)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativePositionAxes = Axes.Y,
+ Y = -0.25f,
+ Size = new Vector2(Column.COLUMN_WIDTH, NotePiece.NOTE_HEIGHT),
+ };
+
+ int runcount = 0;
+
+ AddRepeatStep("explode", () =>
+ {
+ runcount++;
+
+ if (runcount % 15 > 12)
+ return;
+
+ scrolling.AddRange(new Drawable[]
+ {
+ new HitExplosion((runcount / 15) % 2 == 0 ? new Color4(94, 0, 57, 255) : new Color4(6, 84, 0, 255), runcount % 6 != 0)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ });
+ }, 100);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs
similarity index 88%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs
index 411412e127..b4332264b9 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class TestCaseHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestCase
+ public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint();
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
similarity index 83%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
index ae614ae4b8..90394f3d1b 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs
@@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -15,14 +14,14 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class TestCaseHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase
+ public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
{
private readonly DrawableHoldNote drawableObject;
protected override Container Content => content ?? base.Content;
private readonly Container content;
- public TestCaseHoldNoteSelectionBlueprint()
+ public TestSceneHoldNoteSelectionBlueprint()
{
var holdNote = new HoldNote { Column = 0, Duration = 1000 };
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@@ -36,9 +35,11 @@ namespace osu.Game.Rulesets.Mania.Tests
Child = drawableObject = new DrawableHoldNote(holdNote)
{
Height = 300,
- AccentColour = OsuColour.Gray(0.3f)
+ AccentColour = { Value = OsuColour.Gray(0.3f) }
}
};
+
+ AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
}
protected override void Update()
@@ -51,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests
nested.Y = (float)(-finalPosition * content.DrawHeight);
}
}
-
- protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs
similarity index 88%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs
index 12cbeb81f3..d7b539a2a0 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs
@@ -10,7 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase
+ public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs
similarity index 77%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs
index 99fe464cfd..1514bdf0bd 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -15,18 +14,18 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Tests
{
- public class TestCaseNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase
+ public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
{
- private readonly DrawableNote drawableObject;
-
protected override Container Content => content ?? base.Content;
private readonly Container content;
- public TestCaseNoteSelectionBlueprint()
+ public TestSceneNoteSelectionBlueprint()
{
var note = new Note { Column = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ DrawableNote drawableObject;
+
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{
Anchor = Anchor.Centre,
@@ -34,8 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Size = new Vector2(50, 20),
Child = drawableObject = new DrawableNote(note)
};
- }
- protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject);
+ AddBlueprint(new NoteSelectionBlueprint(drawableObject));
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
similarity index 92%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs
index 2220873d89..8dae5e6d84 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.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;
@@ -11,10 +11,11 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
@@ -27,7 +28,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- public class TestCaseNotes : OsuTestCase
+ public class TestSceneNotes : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -40,6 +41,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
Child = new FillFlowContainer
{
+ Clock = new FramedClock(new ManualClock()),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
@@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private Drawable createNoteDisplay(ScrollingDirection direction, int identifier, out DrawableNote hitObject)
{
- var note = new Note { StartTime = 999999999 };
+ var note = new Note { StartTime = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -70,14 +72,14 @@ 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 } }
}
};
}
private Drawable createHoldNoteDisplay(ScrollingDirection direction, int identifier, out DrawableHoldNote hitObject)
{
- var note = new HoldNote { StartTime = 999999999, Duration = 5000 };
+ var note = new HoldNote { StartTime = 0, Duration = 5000 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return new ScrollingTestContainer(direction)
@@ -88,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Child = hitObject = new DrawableHoldNote(note)
{
RelativeSizeAxes = Axes.Both,
- AccentColour = Color4.OrangeRed,
+ AccentColour = { Value = Color4.OrangeRed },
}
}
};
@@ -133,12 +135,12 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Both,
Width = 1.25f,
- Colour = Color4.Black.Opacity(0.5f)
+ Colour = Color4.Green.Opacity(0.5f)
},
content = new Container { RelativeSizeAxes = Axes.Both }
}
},
- new SpriteText
+ new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
similarity index 86%
rename from osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
rename to osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
index 9a7a3d1c5c..d5fd2808b8 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs
@@ -22,7 +22,7 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- public class TestCaseStage : ManiaInputTestCase
+ public class TestSceneStage : ManiaInputTestScene
{
private const int columns = 4;
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private FillFlowContainer fill;
- public TestCaseStage()
+ public TestSceneStage()
: base(columns)
{
}
@@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre));
AddStep("flip direction", () =>
{
@@ -75,10 +77,14 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre));
+ AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
}
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
+ private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
+
private void createNote()
{
foreach (var stage in stages)
@@ -114,8 +120,7 @@ namespace osu.Game.Rulesets.Mania.Tests
var obj = new BarLine
{
StartTime = Time.Current + 2000,
- ControlPoint = new TimingControlPoint(),
- BeatIndex = major ? 0 : 1
+ Major = major,
};
obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
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 fd17285a38..30511d672d 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,14 +2,14 @@
-
-
-
+
+
+
WinExe
- netcoreapp2.2
+ netcoreapp3.0
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/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
index b591f9da22..f5412dcfc5 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
- Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0);
+ Set(ManiaRulesetSetting.ScrollTime, 1500.0, 50.0, 5000.0, 50.0);
Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 59fed1031f..37cba1fd3c 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -11,7 +11,9 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mania.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Mods;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
@@ -30,14 +32,18 @@ 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 };
+
+ HitWindows hitWindows = new ManiaHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
return new ManiaDifficultyAttributes
{
StarRating = difficultyValue(skills) * star_scaling_factor,
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,
+ GreatHitWindow = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate,
+ Skills = skills
};
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index 26115311f7..bcbc1ee527 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -3,7 +3,6 @@
using System;
using osu.Framework.Graphics;
-using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
using osuTK;
@@ -49,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private double originalStartTime;
- protected override bool OnMouseMove(MouseMoveEvent e)
+ public override void UpdatePosition(Vector2 screenSpacePosition)
{
- base.OnMouseMove(e);
+ base.UpdatePosition(screenSpacePosition);
if (PlacementBegun)
{
- var endTime = TimeAt(e.ScreenSpaceMousePosition);
+ var endTime = TimeAt(screenSpacePosition);
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
HitObject.Duration = Math.Abs(endTime - originalStartTime);
@@ -65,10 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
headPiece.Width = tailPiece.Width = SnappedWidth;
headPiece.X = tailPiece.X = SnappedMousePosition.X;
- originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
+ originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
}
-
- return true;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
index d64c5dbc6a..3a9eb1f043 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs
@@ -16,45 +16,51 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
{
- public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
+ public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
private readonly IBindable direction = new Bindable();
- private readonly BodyPiece body;
+ [Resolved]
+ private OsuColour colours { get; set; }
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
: base(hold)
{
- InternalChildren = new Drawable[]
- {
- new HoldNoteNoteSelectionBlueprint(hold.Head),
- new HoldNoteNoteSelectionBlueprint(hold.Tail),
- body = new BodyPiece
- {
- AccentColour = Color4.Transparent
- },
- };
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, IScrollingInfo scrollingInfo)
+ private void load(IScrollingInfo scrollingInfo)
{
- body.BorderColour = colours.Yellow;
-
direction.BindTo(scrollingInfo.Direction);
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ InternalChildren = new Drawable[]
+ {
+ new HoldNoteNoteSelectionBlueprint(DrawableObject.Head),
+ new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail),
+ new BodyPiece
+ {
+ AccentColour = Color4.Transparent,
+ BorderColour = colours.Yellow
+ },
+ };
+ }
+
protected override void Update()
{
base.Update();
- Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight);
+ Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
if (direction.Value == ScrollingDirection.Down)
- Y -= HitObject.Tail.DrawHeight;
+ Y -= DrawableObject.Tail.DrawHeight;
}
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
@@ -71,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
- Anchor = HitObject.Anchor;
- Origin = HitObject.Origin;
+ Anchor = DrawableObject.Anchor;
+ Origin = DrawableObject.Origin;
- Position = HitObject.DrawPosition;
+ Position = DrawableObject.DrawPosition;
}
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index d3779e2e18..b28d8bb0e6 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column == null)
return base.OnMouseDown(e);
- HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = Column.Index;
-
- BeginPlacement();
+ BeginPlacement(TimeAt(e.ScreenSpaceMousePosition));
return true;
}
@@ -62,19 +60,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return base.OnMouseUp(e);
}
- protected override bool OnMouseMove(MouseMoveEvent e)
+ public override void UpdatePosition(Vector2 screenSpacePosition)
{
if (!PlacementBegun)
- Column = ColumnAt(e.ScreenSpaceMousePosition);
+ Column = ColumnAt(screenSpacePosition);
- if (Column == null) return false;
+ if (Column == null) return;
SnappedWidth = Column.DrawWidth;
// Snap to the column
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
- SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y);
- return true;
+ SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
}
protected double TimeAt(Vector2 screenSpacePosition)
@@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
- var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y;
+ var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
@@ -103,16 +100,58 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
scrollingInfo.TimeRange.Value,
Column.HitObjectContainer.DrawHeight);
- return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y;
+ if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
+ pos = Column.HitObjectContainer.DrawHeight - pos;
+
+ return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
}
protected Column ColumnAt(Vector2 screenSpacePosition)
- => composer.ColumnAt(applyPositionOffset(screenSpacePosition, false));
+ => composer.ColumnAt(screenSpacePosition);
- private Vector2 applyPositionOffset(Vector2 position, bool reverse)
+ ///
+ /// Converts a mouse position to a hitobject position.
+ ///
+ ///
+ /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
+ ///
+ /// The mouse position.
+ /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.
+ private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
{
- position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2;
- return position;
+ switch (scrollingInfo.Direction.Value)
+ {
+ case ScrollingDirection.Up:
+ mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
+ break;
+
+ case ScrollingDirection.Down:
+ mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
+ break;
+ }
+
+ return mousePosition;
+ }
+
+ ///
+ /// Converts a hitobject position to a mouse position.
+ ///
+ /// The hitobject position.
+ /// The resulting mouse position, anchored at the centre of the hitobject.
+ private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
+ {
+ switch (scrollingInfo.Direction.Value)
+ {
+ case ScrollingDirection.Up:
+ hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
+ break;
+
+ case ScrollingDirection.Down:
+ hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
+ break;
+ }
+
+ return hitObjectPosition;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
index d3c12b1944..3bd7fb2d49 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; }
- protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject;
+ public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
protected IClock EditorClock { get; private set; }
@@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
- public ManiaSelectionBlueprint(DrawableHitObject hitObject)
- : base(hitObject)
+ public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
+ : base(drawableObject)
{
RelativeSizeAxes = Axes.None;
}
@@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
- Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero));
+ Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
}
protected override bool OnMouseDown(MouseDownEvent e)
{
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
- DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
+ DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return base.OnMouseDown(e);
}
@@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
var result = base.OnDrag(e);
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
- DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
+ DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return result;
}
public override void Show()
{
- HitObject.AlwaysAlive = true;
+ DrawableObject.AlwaysAlive = true;
base.Show();
}
public override void Hide()
{
- HitObject.AlwaysAlive = false;
+ DrawableObject.AlwaysAlive = false;
base.Hide();
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
index d345b14e84..b83c4aa9aa 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
- Size = HitObject.DrawSize;
+ Size = DrawableObject.DrawSize;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index e5f379f608..97d8aaa052 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 2729621ab3..0bfe6f9517 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit
[Cached(Type = typeof(IManiaHitObjectComposer))]
public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer
{
- protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; }
+ private DrawableManiaEditRuleset drawableRuleset;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
@@ -33,23 +33,23 @@ namespace osu.Game.Rulesets.Mania.Edit
///
/// The screen-space position.
/// The column which intersects with .
- public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition);
+ public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition);
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
- public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns;
+ public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns;
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
{
- DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
+ drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
- dependencies.CacheAs(DrawableRuleset.ScrollingInfo);
+ dependencies.CacheAs(drawableRuleset.ScrollingInfo);
- return DrawableRuleset;
+ return drawableRuleset;
}
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 6f49c7f0c4..732231b0d9 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -3,9 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Input.Events;
using osu.Framework.Timing;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
@@ -31,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Edit
editorClock = clock;
}
- public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
+ public override void HandleMovement(MoveSelectionEvent moveEvent)
{
- adjustOrigins((ManiaSelectionBlueprint)blueprint);
- performDragMovement(dragEvent);
- performColumnMovement(dragEvent);
+ var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
+ int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
- base.HandleDrag(blueprint, dragEvent);
+ adjustOrigins(maniaBlueprint);
+ performDragMovement(moveEvent);
+ performColumnMovement(lastColumn, moveEvent);
+
+ base.HandleMovement(moveEvent);
}
///
@@ -47,41 +48,44 @@ namespace osu.Game.Rulesets.Mania.Edit
/// The that received the drag event.
private void adjustOrigins(ManiaSelectionBlueprint reference)
{
- var referenceParent = (HitObjectContainer)reference.HitObject.Parent;
+ var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent;
- float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y;
+ float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y;
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
// Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = targetPosition - referenceParent.DrawHeight;
- float movementDelta = targetPosition - reference.HitObject.Position.Y;
+ float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
foreach (var b in SelectedBlueprints.OfType())
- b.HitObject.Y += movementDelta;
+ b.DrawableObject.Y += movementDelta;
}
- private void performDragMovement(DragEvent dragEvent)
+ private void performDragMovement(MoveSelectionEvent moveEvent)
{
+ float delta = moveEvent.InstantDelta.Y;
+
+ // When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
+ // This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
+ if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
+ delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight;
+
foreach (var b in SelectedBlueprints)
{
- var hitObject = b.HitObject;
-
+ var hitObject = b.DrawableObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
- // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
- // without the position having been updated by the parenting ScrollingHitObjectContainer
- hitObject.Y += dragEvent.Delta.Y;
+ // StartTime could be used to adjust the position if only one movement event was received per frame.
+ // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
+ hitObject.Y += delta;
- float targetPosition;
+ float targetPosition = hitObject.Position.Y;
- // If we're scrolling downwards, a position of 0 is actually further away from the hit target
- // so we need to flip the vertical coordinate in the hitobject container's space
+ // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
- targetPosition = -hitObject.Position.Y;
- else
- targetPosition = hitObject.Position.Y;
+ targetPosition = -targetPosition;
objectParent.Remove(hitObject);
@@ -94,14 +98,13 @@ namespace osu.Game.Rulesets.Mania.Edit
}
}
- private void performColumnMovement(DragEvent dragEvent)
+ private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{
- var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition);
- var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition);
- if (lastColumn == null || currentColumn == null)
+ var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
+ if (currentColumn == null)
return;
- int columnDelta = currentColumn.Index - lastColumn.Index;
+ int columnDelta = currentColumn.Index - lastColumn;
if (columnDelta == 0)
return;
diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
index 30b0f09a94..ff8882124f 100644
--- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
@@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
{
public abstract class ManiaSelectionBlueprint : SelectionBlueprint
{
- protected ManiaSelectionBlueprint(DrawableHitObject hitObject)
- : base(hitObject)
+ protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
+ : base(drawableObject)
{
RelativeSizeAxes = Axes.None;
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index d83033f9c6..c74a292331 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;
@@ -32,10 +31,12 @@ namespace osu.Game.Rulesets.Mania
{
public class ManiaRuleset : Ruleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
+ public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
+ public const string SHORT_NAME = "mania";
+
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
@@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.DoubleTime))
yield return new ManiaModDoubleTime();
+ if (mods.HasFlag(LegacyMods.Perfect))
+ yield return new ManiaModPerfect();
+ else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ yield return new ManiaModSuddenDeath();
+
if (mods.HasFlag(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
@@ -96,14 +102,8 @@ namespace osu.Game.Rulesets.Mania
if (mods.HasFlag(LegacyMods.NoFail))
yield return new ManiaModNoFail();
- if (mods.HasFlag(LegacyMods.Perfect))
- yield return new ManiaModPerfect();
-
if (mods.HasFlag(LegacyMods.Random))
yield return new ManiaModRandom();
-
- if (mods.HasFlag(LegacyMods.SuddenDeath))
- yield return new ManiaModSuddenDeath();
}
public override IEnumerable GetModsFor(ModType type)
@@ -154,7 +154,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:
@@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania";
- public override string ShortName => "mania";
+ public override string ShortName => SHORT_NAME;
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
new file mode 100644
index 0000000000..69bd4b0ecf
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponent.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class ManiaSkinComponent : GameplaySkinComponent
+ {
+ public ManiaSkinComponent(ManiaSkinComponents component)
+ : base(component)
+ {
+ }
+
+ protected override string RulesetPrefix => ManiaRuleset.SHORT_NAME;
+
+ protected override string ComponentName => Component.ToString().ToLower();
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
new file mode 100644
index 0000000000..6d85816e5a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSkinComponents.cs
@@ -0,0 +1,9 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania
+{
+ public enum ManiaSkinComponents
+ {
+ }
+}
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/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
index 4c644a8f09..0981b028b2 100644
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
@@ -1,21 +1,12 @@
-// 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.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Objects
{
- public class BarLine : ManiaHitObject
+ public class BarLine : ManiaHitObject, IBarLine
{
- ///
- /// The control point which this bar line is part of.
- ///
- public TimingControlPoint ControlPoint;
-
- ///
- /// The index of the beat which this bar line represents within the control point.
- /// This is a "major" bar line if % == 0.
- ///
- public int BeatIndex;
+ public bool Major { get; set; }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 9c3197504f..56bc797c7f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -40,9 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Colour = new Color4(255, 204, 33, 255),
});
- bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
-
- if (isMajor)
+ if (barLine.Major)
{
AddInternal(new EquilateralTriangle
{
@@ -65,11 +63,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
- if (!isMajor && barLine.BeatIndex % 2 == 1)
+ if (!barLine.Major)
Alpha = 0.2f;
}
- protected override void UpdateState(ArmedState state)
+ protected override void UpdateInitialTransforms()
+ {
+ }
+
+ protected override void UpdateStateTransforms(ArmedState state)
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 9368af987d..87b9633c80 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,14 +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.Linq;
+using System.Diagnostics;
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.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
@@ -21,8 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
- public readonly DrawableNote Head;
- public readonly DrawableNote Tail;
+ public DrawableNote Head => headContainer.Child;
+ public DrawableNote Tail => tailContainer.Child;
+
+ private readonly Container headContainer;
+ private readonly Container tailContainer;
+ private readonly Container tickContainer;
private readonly BodyPiece bodyPiece;
@@ -36,8 +40,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
private bool hasBroken;
- private readonly Container tickContainer;
-
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
{
@@ -45,35 +47,75 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddRangeInternal(new Drawable[]
{
- bodyPiece = new BodyPiece
- {
- RelativeSizeAxes = Axes.X,
- },
- tickContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- ChildrenEnumerable = HitObject.NestedHitObjects.OfType().Select(tick => new DrawableHoldNoteTick(tick)
- {
- HoldStartTime = () => holdStartTime
- })
- },
- Head = new DrawableHeadNote(this)
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre
- },
- Tail = new DrawableTailNote(this)
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre
- }
+ bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
+ tickContainer = new Container { RelativeSizeAxes = Axes.Both },
+ headContainer = new Container { RelativeSizeAxes = Axes.Both },
+ tailContainer = new Container { RelativeSizeAxes = Axes.Both },
});
- foreach (var tick in tickContainer)
- AddNested(tick);
+ AccentColour.BindValueChanged(colour =>
+ {
+ bodyPiece.AccentColour = colour.NewValue;
+ }, true);
+ }
- AddNested(Head);
- AddNested(Tail);
+ protected override void AddNestedHitObject(DrawableHitObject hitObject)
+ {
+ base.AddNestedHitObject(hitObject);
+
+ switch (hitObject)
+ {
+ case DrawableHeadNote head:
+ headContainer.Child = head;
+ break;
+
+ case DrawableTailNote tail:
+ tailContainer.Child = tail;
+ break;
+
+ case DrawableHoldNoteTick tick:
+ tickContainer.Add(tick);
+ break;
+ }
+ }
+
+ protected override void ClearNestedHitObjects()
+ {
+ base.ClearNestedHitObjects();
+ headContainer.Clear();
+ tailContainer.Clear();
+ tickContainer.Clear();
+ }
+
+ protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case TailNote _:
+ return new DrawableTailNote(this)
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ AccentColour = { BindTarget = AccentColour }
+ };
+
+ case Note _:
+ return new DrawableHeadNote(this)
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ AccentColour = { BindTarget = AccentColour }
+ };
+
+ case HoldNoteTick tick:
+ return new DrawableHoldNoteTick(tick)
+ {
+ HoldStartTime = () => holdStartTime,
+ AccentColour = { BindTarget = AccentColour }
+ };
+ }
+
+ return base.CreateNestedHitObject(hitObject);
}
protected override void OnDirectionChanged(ValueChangedEvent e)
@@ -83,20 +125,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)
@@ -112,6 +140,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
}
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ using (BeginDelayedSequence(HitObject.Duration, true))
+ base.UpdateStateTransforms(state);
+ }
+
protected void BeginHold()
{
holdStartTime = Time.Current;
@@ -210,6 +244,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
// Factor in the release lenience
timeOffset /= release_window_lenience;
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..5bfa07bd14 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -45,6 +45,20 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
+
+ protected override void UpdateStateTransforms(ArmedState state)
+ {
+ switch (state)
+ {
+ case ArmedState.Miss:
+ this.FadeOut(150, Easing.In);
+ break;
+
+ case ArmedState.Hit:
+ this.FadeOut(150, Easing.OutQuint);
+ break;
+ }
+ }
}
public abstract class DrawableManiaHitObject : DrawableManiaHitObject
@@ -57,19 +71,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
HitObject = hitObject;
}
-
- protected override void UpdateState(ArmedState state)
- {
- switch (state)
- {
- case ArmedState.Miss:
- this.FadeOut(150, Easing.In).Expire();
- break;
-
- case ArmedState.Hit:
- this.FadeOut(150, Easing.OutQuint).Expire();
- break;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index afd7777861..8f353ae138 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -1,9 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
-using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Bindings;
@@ -30,6 +30,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.2f),
+ Radius = 10,
+ };
+ }, true);
}
protected override void OnDirectionChanged(ValueChangedEvent e)
@@ -39,25 +51,10 @@ 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)
{
+ Debug.Assert(HitObject.HitWindows != null);
+
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
index 8102718edf..31a4857805 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs
@@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public BodyPiece()
{
- Blending = BlendingMode.Additive;
+ Blending = BlendingParameters.Additive;
Children = new[]
{
Background = new Box { RelativeSizeAxes = Axes.Both },
Foreground = new BufferedContainer
{
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
@@ -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/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
index 9e0307c5c2..48c7ea7b7f 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Name = "Top",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha))
},
new Box
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent)
}
};
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index bb33693783..4521af7dfb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -18,8 +18,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
///
internal class NotePiece : Container, IHasAccentColour
{
- public const float NOTE_HEIGHT = 10;
- private const float head_colour_height = 6;
+ public const float NOTE_HEIGHT = 12;
private readonly IBindable direction = new Bindable();
@@ -39,8 +38,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
colouredBox = new Box
{
RelativeSizeAxes = Axes.X,
- Height = head_colour_height,
- Alpha = 0.2f
+ Height = NOTE_HEIGHT / 2,
+ Alpha = 0.1f
}
};
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 5e9f46d9c7..bdba813eed 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -6,6 +6,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -99,5 +100,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}
public override Judgement CreateJudgement() => new HoldNoteJudgement();
+
+ protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
index c133ee73b1..ac6697a6dc 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs
@@ -3,6 +3,7 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
@@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public class HoldNoteTick : ManiaHitObject
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
+
+ protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
index 70720a926b..995e1516cb 100644
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs
@@ -3,7 +3,9 @@
using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types;
+using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs
deleted file mode 100644
index 5f2ceab48b..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.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 System.Collections.Generic;
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Mania.Objects
-{
- public class ManiaHitWindows : HitWindows
- {
- private static readonly IReadOnlyDictionary base_ranges = new Dictionary
- {
- { HitResult.Perfect, (44.8, 38.8, 27.8) },
- { HitResult.Great, (128, 98, 68) },
- { HitResult.Good, (194, 164, 134) },
- { HitResult.Ok, (254, 224, 194) },
- { HitResult.Meh, (302, 272, 242) },
- { HitResult.Miss, (376, 346, 316) },
- };
-
- public override bool IsHitResultAllowed(HitResult result) => true;
-
- public override void SetDifficulty(double difficulty)
- {
- Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
- Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
- Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
- Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
- Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
- Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
- }
- }
-}
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/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index e5669816fa..2b336ca16d 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
@@ -46,9 +47,6 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate()
{
- // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
- Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
-
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
var actions = new List();
@@ -69,6 +67,10 @@ namespace osu.Game.Rulesets.Mania.Replays
}
}
+ // todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
+ if (Replay.Frames.Count == 0)
+ Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
+
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
}
@@ -77,13 +79,37 @@ namespace osu.Game.Rulesets.Mania.Replays
private IEnumerable generateActionPoints()
{
- foreach (var obj in Beatmap.HitObjects)
+ for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
- yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
- yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
+ var currentObject = Beatmap.HitObjects[i];
+ var nextObjectInColumn = GetNextObject(i); // Get the next object that requires pressing the same button
+
+ double endTime = (currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime;
+
+ bool canDelayKeyUp = nextObjectInColumn == null ||
+ nextObjectInColumn.StartTime > endTime + RELEASE_DELAY;
+
+ double calculatedDelay = canDelayKeyUp ? RELEASE_DELAY : (nextObjectInColumn.StartTime - endTime) * 0.9;
+
+ yield return new HitPoint { Time = currentObject.StartTime, Column = currentObject.Column };
+
+ yield return new ReleasePoint { Time = endTime + calculatedDelay, Column = currentObject.Column };
}
}
+ protected override HitObject GetNextObject(int currentIndex)
+ {
+ int desiredColumn = Beatmap.HitObjects[currentIndex].Column;
+
+ for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
+ {
+ if (Beatmap.HitObjects[i].Column == desiredColumn)
+ return Beatmap.HitObjects[i];
+ }
+
+ return null;
+ }
+
private interface IActionPoint
{
double Time { get; set; }
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index f7277d3669..70ba5cd938 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Replays
Actions.AddRange(actions);
}
- public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap)
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
// We don't need to fully convert, just create the converter
var converter = new ManiaBeatmapConverter(beatmap);
diff --git a/osu.Game/Screens/Direct/OnlineListing.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
similarity index 58%
rename from osu.Game/Screens/Direct/OnlineListing.cs
rename to osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index 8376383674..549f0f9214 100644
--- a/osu.Game/Screens/Direct/OnlineListing.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-namespace osu.Game.Screens.Direct
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Scoring
{
- public class OnlineListing : ScreenWhiteBox
+ public class ManiaHitWindows : HitWindows
{
}
}
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 5caf08fb1e..49894a644c 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index c59bed4ea7..3d2a070b0f 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.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.Linq;
@@ -11,6 +11,8 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
- private const float column_width = 45;
+ public const float COLUMN_WIDTH = 80;
private const float special_column_width = 70;
///
@@ -41,10 +43,7 @@ namespace osu.Game.Rulesets.Mania.UI
Index = index;
RelativeSizeAxes = Axes.Y;
- Width = column_width;
-
- Masking = true;
- CornerRadius = 5;
+ Width = COLUMN_WIDTH;
background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
@@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.UI
explosionContainer = new Container
{
Name = "Hit explosions",
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
}
}
},
@@ -90,6 +89,12 @@ namespace osu.Game.Rulesets.Mania.UI
Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
};
+ explosionContainer.Padding = new MarginPadding
+ {
+ Top = dir.NewValue == ScrollingDirection.Up ? NotePiece.NOTE_HEIGHT / 2 : 0,
+ Bottom = dir.NewValue == ScrollingDirection.Down ? NotePiece.NOTE_HEIGHT / 2 : 0
+ };
+
keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}, true);
}
@@ -108,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.UI
isSpecial = value;
- Width = isSpecial ? special_column_width : column_width;
+ Width = isSpecial ? special_column_width : COLUMN_WIDTH;
}
}
@@ -143,7 +148,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);
@@ -163,9 +168,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
- explosionContainer.Add(new HitExplosion(judgedObject)
+ explosionContainer.Add(new HitExplosion(judgedObject.AccentColour.Value, judgedObject is DrawableHoldNoteTick)
{
- Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
+ Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre,
+ Origin = Anchor.Centre
});
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index b4e29ae9f9..57241da564 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -35,14 +35,13 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
- Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
- Blending = BlendingMode.Additive,
+ Blending = BlendingParameters.Additive,
Alpha = 0
}
};
@@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
if (!IsLoaded)
return;
- background.Colour = AccentColour;
+ background.Colour = AccentColour.Darken(5);
var brightPoint = AccentColour.Opacity(0.6f);
var dimPoint = AccentColour.Opacity(0);
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index a0d713067d..386bcbb724 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK.Graphics;
@@ -17,7 +18,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
- private const float hit_target_height = 10;
private const float hit_target_bar_height = 2;
private readonly IBindable direction = new Bindable();
@@ -32,7 +32,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
hitTargetBar = new Box
{
RelativeSizeAxes = Axes.X,
- Height = hit_target_height,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
Colour = Color4.Black
},
hitTargetLine = new Container
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index c8aeda8fe4..d371c1f7a8 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -2,14 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input;
-using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -19,8 +16,8 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -36,40 +33,16 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
+ protected override bool RelativeScaleBeatLengths => true;
+
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
private readonly Bindable configDirection = new Bindable();
- public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
- // Generate the bar lines
- double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue;
-
- var timingPoints = Beatmap.ControlPointInfo.TimingPoints;
- var barLines = new List();
-
- for (int i = 0; i < timingPoints.Count; i++)
- {
- TimingControlPoint point = timingPoints[i];
-
- // Stop on the beat before the next timing point, or if there is no next timing point stop slightly past the last object
- double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature;
-
- int index = 0;
-
- for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++)
- {
- barLines.Add(new BarLine
- {
- StartTime = t,
- ControlPoint = point,
- BeatIndex = index
- });
- }
- }
-
- BarLines = barLines;
+ BarLines = new BarLineGenerator(Beatmap).BarLines;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index 0ec1fc38d2..ccbff226a9 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -1,16 +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 osuTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
-using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -18,51 +16,112 @@ namespace osu.Game.Rulesets.Mania.UI
{
public override bool RemoveWhenNotAlive => true;
- private readonly CircularContainer circle;
+ private readonly CircularContainer largeFaint;
+ private readonly CircularContainer mainGlow1;
- public HitExplosion(DrawableHitObject judgedObject)
+ public HitExplosion(Color4 objectColour, bool isSmall = false)
{
- bool isTick = judgedObject is DrawableHoldNoteTick;
-
- Origin = Anchor.Centre;
-
RelativeSizeAxes = Axes.X;
- Y = NotePiece.NOTE_HEIGHT / 2;
Height = NotePiece.NOTE_HEIGHT;
// scale roughly in-line with visual appearance of notes
- Scale = new Vector2(isTick ? 0.4f : 0.8f);
+ Scale = new Vector2(1f, 0.6f);
- InternalChild = circle = new CircularContainer
+ if (isSmall)
+ Scale *= 0.5f;
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 80;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- // we want our size to be very small so the glow dominates it.
- Size = new Vector2(0.1f),
- EdgeEffect = new EdgeEffectParameters
+ largeFaint = new CircularContainer
{
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1),
- Radius = 100,
- },
- Child = new Box
- {
- Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true
+ Masking = true,
+ // we want our size to be very small so the glow dominates it.
+ Size = new Vector2(0.8f),
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ },
+ },
+ mainGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
+ },
+ new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ Rotation = RNG.NextSingle(-angle_variangle, angle_variangle),
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = colour,
+ Roundness = roundness,
+ Radius = 40,
+ },
}
};
}
protected override void LoadComplete()
{
+ const double duration = 200;
+
base.LoadComplete();
- circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint);
- this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+ mainGlow1.ScaleTo(1.4f, duration, Easing.OutQuint);
+
+ this.FadeOut(duration, Easing.Out);
Expire(true);
}
}
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.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
index 9930a166e3..c7787bd162 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj
@@ -13,14 +13,6 @@
-
- libbass.a
- PreserveNewest
-
-
- libbass_fx.a
- PreserveNewest
-
Linker.xml
diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
index ed03e99b9b..67338b7bbe 100644
--- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 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/OsuLegacyModConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs
new file mode 100644
index 0000000000..495f2738b5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/OsuLegacyModConversionTest.cs
@@ -0,0 +1,30 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Game.Beatmaps.Legacy;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class OsuLegacyModConversionTest : LegacyModConversionTest
+ {
+ [TestCase(LegacyMods.Easy, new[] { typeof(OsuModEasy) })]
+ [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(OsuModHardRock), typeof(OsuModDoubleTime) })]
+ [TestCase(LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime) })]
+ [TestCase(LegacyMods.Nightcore, new[] { typeof(OsuModNightcore) })]
+ [TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModNightcore) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(OsuModFlashlight), typeof(OsuModFlashlight) })]
+ [TestCase(LegacyMods.Perfect, new[] { typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.SuddenDeath, new[] { typeof(OsuModSuddenDeath) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(OsuModDoubleTime), typeof(OsuModPerfect) })]
+ [TestCase(LegacyMods.SpunOut | LegacyMods.Easy, new[] { typeof(OsuModSpunOut), typeof(OsuModEasy) })]
+ public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+
+ protected override Ruleset CreateRuleset() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png
new file mode 100644
index 0000000000..72ef665478
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/approachcircle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png
new file mode 100644
index 0000000000..a91072eb5b
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png
new file mode 100644
index 0000000000..5eb202c021
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit100@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png
new file mode 100644
index 0000000000..878c11cd67
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit300@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@2x.png
new file mode 100644
index 0000000000..f64feded0c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/hit50@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/hit0-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png
new file mode 100644
index 0000000000..37e7e9143f
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png
new file mode 100644
index 0000000000..b75c71927c
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit0-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png
new file mode 100644
index 0000000000..7932667408
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png
new file mode 100644
index 0000000000..0b0ae85972
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit100-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png
new file mode 100644
index 0000000000..441c3ec21f
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png
new file mode 100644
index 0000000000..910d8e2231
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit300-1@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png
new file mode 100644
index 0000000000..6f92db28d3
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-0@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@2x.png
new file mode 100644
index 0000000000..b28503e9f2
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hit50-1@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..38aac50df6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.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.Text.RegularExpressions;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+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, SkinManager skinManager)
+ {
+ var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll");
+
+ metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true);
+ defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
+ specialSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore(dllStore, "Resources/special_skin"), audio, true);
+ }
+
+ public void SetContents(Func creationFunction)
+ {
+ Cell(0).Child = createProvider(null, creationFunction);
+ Cell(1).Child = createProvider(metricsSkin, creationFunction);
+ Cell(2).Child = createProvider(defaultSkin, creationFunction);
+ Cell(3).Child = createProvider(specialSkin, creationFunction);
+ }
+
+ private Drawable createProvider(Skin skin, Func creationFunction)
+ {
+ var mainProvider = new SkinProvidingContainer(skin);
+
+ return mainProvider
+ .WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
+ {
+ Child = creationFunction()
+ });
+ }
+
+ private class TestLegacySkin : LegacySkin
+ {
+ private readonly bool extrapolateAnimations;
+
+ public TestLegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, bool extrapolateAnimations)
+ : base(skin, storage, audioManager, "skin.ini")
+ {
+ this.extrapolateAnimations = extrapolateAnimations;
+ }
+
+ public override Texture GetTexture(string componentName)
+ {
+ // extrapolate frames to test longer animations
+ if (extrapolateAnimations)
+ {
+ var match = Regex.Match(componentName, "-([0-9]*)");
+
+ if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60)
+ return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"));
+ }
+
+ return base.GetTexture(componentName);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs
index e8b99e86f9..871afdb09d 100644
--- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Game.Beatmaps;
+using osu.Game.IO;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
@@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestStacking()
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data)))
- using (var reader = new StreamReader(stream))
+ using (var reader = new LineBufferedReader(stream))
{
var beatmap = Decoder.GetDecoder(reader).Decode(reader);
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty());
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs
deleted file mode 100644
index 1e2a936002..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs
+++ /dev/null
@@ -1,34 +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.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Cursor;
-using osu.Game.Graphics.Cursor;
-using osu.Game.Rulesets.Osu.UI.Cursor;
-using osu.Game.Rulesets.UI;
-using osu.Game.Tests.Visual;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- [TestFixture]
- public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
- {
- private GameplayCursorContainer cursorContainer;
-
- public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) };
-
- public CursorContainer Cursor => cursorContainer;
-
- public bool ProvidingUserCursor => true;
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both });
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs
deleted file mode 100644
index e9284e453e..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs
+++ /dev/null
@@ -1,29 +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.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase
- {
- private readonly DrawableHitCircle drawableObject;
-
- public TestCaseHitCircleSelectionBlueprint()
- {
- var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
- hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
-
- Add(drawableObject = new DrawableHitCircle(hitCircle));
- }
-
- protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
deleted file mode 100644
index 5dc0dc1024..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs
+++ /dev/null
@@ -1,23 +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.Graphics;
-using osu.Framework.MathUtils;
-using osu.Game.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public class TestCaseShaking : TestCaseHitCircle
- {
- public override void Add(Drawable drawable)
- {
- base.Add(drawable);
-
- if (drawable is TestDrawableHitCircle hitObject)
- {
- Scheduler.AddDelayed(() => hitObject.TriggerJudgement(),
- hitObject.HitObject.StartTime - (hitObject.HitObject.HitWindows.HalfWindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current);
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
deleted file mode 100644
index 0f02050605..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs
+++ /dev/null
@@ -1,328 +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 osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Audio;
-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 osuTK.Graphics;
-using osu.Game.Rulesets.Mods;
-using System.Linq;
-using NUnit.Framework;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- [TestFixture]
- public class TestCaseSlider : OsuTestCase
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SliderBall),
- typeof(SliderBody),
- typeof(SliderTick),
- typeof(DrawableSlider),
- typeof(DrawableSliderTick),
- typeof(DrawableRepeatPoint),
- typeof(DrawableOsuHitObject)
- };
-
- private readonly Container content;
- protected override Container Content => content;
-
- private int depthIndex;
-
- public TestCaseSlider()
- {
- base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
-
- AddStep("Big Single", () => testSimpleBig());
- AddStep("Medium Single", () => testSimpleMedium());
- AddStep("Small Single", () => testSimpleSmall());
- AddStep("Big 1 Repeat", () => testSimpleBig(1));
- AddStep("Medium 1 Repeat", () => testSimpleMedium(1));
- AddStep("Small 1 Repeat", () => testSimpleSmall(1));
- AddStep("Big 2 Repeats", () => testSimpleBig(2));
- AddStep("Medium 2 Repeats", () => testSimpleMedium(2));
- AddStep("Small 2 Repeats", () => testSimpleSmall(2));
-
- AddStep("Slow Slider", testSlowSpeed); // slow long sliders take ages already so no repeat steps
- AddStep("Slow Short Slider", () => testShortSlowSpeed());
- AddStep("Slow Short Slider 1 Repeats", () => testShortSlowSpeed(1));
- AddStep("Slow Short Slider 2 Repeats", () => testShortSlowSpeed(2));
-
- AddStep("Fast Slider", () => testHighSpeed());
- AddStep("Fast Slider 1 Repeat", () => testHighSpeed(1));
- AddStep("Fast Slider 2 Repeats", () => testHighSpeed(2));
- AddStep("Fast Short Slider", () => testShortHighSpeed());
- AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1));
- AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2));
- AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6));
-
- AddStep("Perfect Curve", () => testPerfect());
- AddStep("Perfect Curve 1 Repeat", () => testPerfect(1));
- AddStep("Perfect Curve 2 Repeats", () => testPerfect(2));
-
- AddStep("Linear Slider", () => testLinear());
- AddStep("Linear Slider 1 Repeat", () => testLinear(1));
- AddStep("Linear Slider 2 Repeats", () => testLinear(2));
-
- AddStep("Bezier Slider", () => testBezier());
- AddStep("Bezier Slider 1 Repeat", () => testBezier(1));
- AddStep("Bezier Slider 2 Repeats", () => testBezier(2));
-
- AddStep("Linear Overlapping", () => testLinearOverlapping());
- AddStep("Linear Overlapping 1 Repeat", () => testLinearOverlapping(1));
- AddStep("Linear Overlapping 2 Repeats", () => testLinearOverlapping(2));
-
- AddStep("Catmull Slider", () => testCatmull());
- AddStep("Catmull Slider 1 Repeat", () => testCatmull(1));
- AddStep("Catmull Slider 2 Repeats", () => testCatmull(2));
-
- AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
- AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
-
- AddStep("Distance Overflow", () => testDistanceOverflow());
- AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1));
- }
-
- private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
-
- private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
-
- private void testDistanceOverflow(int repeats = 0)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(239, 176),
- Path = new SliderPath(PathType.PerfectCurve, new[]
- {
- Vector2.Zero,
- new Vector2(154, 28),
- new Vector2(52, -34)
- }, 700),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats),
- StackHeight = 10
- };
-
- addSlider(slider, 2, 2);
- }
-
- private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
-
- private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
-
- private void testSlowSpeed() => createSlider(speedMultiplier: 0.5);
-
- private void testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
-
- private void testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
-
- private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
-
- private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(-(distance / 2), 0),
- Path = new SliderPath(PathType.PerfectCurve, new[]
- {
- Vector2.Zero,
- new Vector2(distance, 0),
- }, distance),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats),
- StackHeight = stackHeight
- };
-
- addSlider(slider, circleSize, speedMultiplier);
- }
-
- private void testPerfect(int repeats = 0)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(-200, 0),
- Path = new SliderPath(PathType.PerfectCurve, new[]
- {
- Vector2.Zero,
- new Vector2(200, 200),
- new Vector2(400, 0)
- }, 600),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
- };
-
- addSlider(slider, 2, 3);
- }
-
- private void testLinear(int repeats = 0) => createLinear(repeats);
-
- private void createLinear(int repeats)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(-200, 0),
- Path = new SliderPath(PathType.Linear, new[]
- {
- Vector2.Zero,
- new Vector2(150, 75),
- new Vector2(200, 0),
- new Vector2(300, -200),
- new Vector2(400, 0),
- new Vector2(430, 0)
- }),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
- };
-
- addSlider(slider, 2, 3);
- }
-
- private void testBezier(int repeats = 0) => createBezier(repeats);
-
- private void createBezier(int repeats)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(-200, 0),
- Path = new SliderPath(PathType.Bezier, new[]
- {
- Vector2.Zero,
- new Vector2(150, 75),
- new Vector2(200, 100),
- new Vector2(300, -200),
- new Vector2(430, 0)
- }),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
- };
-
- addSlider(slider, 2, 3);
- }
-
- private void testLinearOverlapping(int repeats = 0) => createOverlapping(repeats);
-
- private void createOverlapping(int repeats)
- {
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(0, 0),
- Path = new SliderPath(PathType.Linear, new[]
- {
- Vector2.Zero,
- new Vector2(-200, 0),
- new Vector2(0, 0),
- new Vector2(0, -200),
- new Vector2(-200, -200),
- new Vector2(0, -200)
- }),
- RepeatCount = repeats,
- NodeSamples = createEmptySamples(repeats)
- };
-
- addSlider(slider, 2, 3);
- }
-
- private void testCatmull(int repeats = 0) => createCatmull(repeats);
-
- private void createCatmull(int repeats = 0)
- {
- var repeatSamples = new List>();
- for (int i = 0; i < repeats; i++)
- repeatSamples.Add(new List());
-
- var slider = new Slider
- {
- StartTime = Time.Current + 1000,
- Position = new Vector2(-100, 0),
- Path = new SliderPath(PathType.Catmull, new[]
- {
- Vector2.Zero,
- new Vector2(50, -50),
- new Vector2(150, 50),
- new Vector2(200, 0)
- }),
- RepeatCount = repeats,
- NodeSamples = repeatSamples
- };
-
- addSlider(slider, 3, 1);
- }
-
- private List> createEmptySamples(int repeats)
- {
- var repeatSamples = new List>();
- for (int i = 0; i < repeats; i++)
- repeatSamples.Add(new List());
- return repeatSamples;
- }
-
- private void addSlider(Slider slider, float circleSize, double speedMultiplier)
- {
- var cpi = new ControlPointInfo();
- cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
-
- slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
-
- var drawable = new DrawableSlider(slider)
- {
- Anchor = Anchor.Centre,
- Depth = depthIndex++
- };
-
- foreach (var mod in Mods.Value.OfType())
- mod.ApplyToDrawableHitObjects(new[] { drawable });
-
- drawable.OnNewResult += onNewResult;
-
- Add(drawable);
- }
-
- private float judgementOffsetDirection = 1;
-
- private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
- {
- var osuObject = judgedObject as DrawableOsuHitObject;
- if (osuObject == null)
- return;
-
- OsuSpriteText text;
- Add(text = new OsuSpriteText
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = result.IsHit ? "Hit!" : "Miss!",
- Colour = result.IsHit ? Color4.Green : Color4.Red,
- Font = OsuFont.GetFont(size: 30),
- Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
- });
-
- text.Delay(150)
- .Then().FadeOut(200)
- .Then().Expire();
-
- judgementOffsetDirection *= -1;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs
deleted file mode 100644
index a7386ba48b..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.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 osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
-using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Tests.Visual;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Tests
-{
- public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase
- {
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(SliderSelectionBlueprint),
- typeof(SliderCircleSelectionBlueprint),
- typeof(SliderBodyPiece),
- typeof(SliderCircle),
- typeof(PathControlPointVisualiser),
- typeof(PathControlPointPiece)
- };
-
- private readonly DrawableSlider drawableObject;
-
- public TestCaseSliderSelectionBlueprint()
- {
- var slider = new Slider
- {
- Position = new Vector2(256, 192),
- Path = new SliderPath(PathType.Bezier, new[]
- {
- Vector2.Zero,
- new Vector2(150, 150),
- new Vector2(300, 0)
- })
- };
-
- slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
-
- Add(drawableObject = new DrawableSlider(slider));
- }
-
- protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
- }
-}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
new file mode 100644
index 0000000000..685a51d208
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneCursorTrail.cs
@@ -0,0 +1,128 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing.Input;
+using osu.Game.Audio;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneCursorTrail : OsuTestScene
+ {
+ [Test]
+ public void TestSmoothCursorTrail()
+ {
+ Container scalingContainer = null;
+
+ createTest(() => scalingContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new CursorTrail()
+ });
+
+ AddStep("set large scale", () => scalingContainer.Scale = new Vector2(10));
+ }
+
+ [Test]
+ public void TestLegacySmoothCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(false)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ [Test]
+ public void TestLegacyDisjointCursorTrail()
+ {
+ createTest(() => new LegacySkinContainer(true)
+ {
+ Child = new LegacyCursorTrail()
+ });
+ }
+
+ private void createTest(Func createContent) => AddStep("create trail", () =>
+ {
+ Clear();
+
+ Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.8f),
+ Child = new MovingCursorInputManager { Child = createContent?.Invoke() }
+ });
+ });
+
+ [Cached(typeof(ISkinSource))]
+ private class LegacySkinContainer : Container, ISkinSource
+ {
+ private readonly bool disjoint;
+
+ public LegacySkinContainer(bool disjoint)
+ {
+ this.disjoint = disjoint;
+
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
+
+ public Texture GetTexture(string componentName)
+ {
+ switch (componentName)
+ {
+ case "cursortrail":
+ var tex = new Texture(Texture.WhitePixel.TextureGL);
+
+ if (disjoint)
+ tex.ScaleAdjust = 1 / 25f;
+ return tex;
+
+ case "cursormiddle":
+ return disjoint ? null : Texture.WhitePixel;
+ }
+
+ return null;
+ }
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
+
+ public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
+
+ public event Action SourceChanged;
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 1000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..433ec6bd25
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableOsuJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
similarity index 79%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
index 83626e7043..4aca34bf64 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs
@@ -7,9 +7,9 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseEditor : EditorTestCase
+ public class TestSceneEditor : EditorTestScene
{
- public TestCaseEditor()
+ public TestSceneEditor()
: base(new OsuRuleset())
{
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
new file mode 100644
index 0000000000..aa170eae1e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -0,0 +1,73 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing.Input;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneGameplayCursor : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(OsuCursorContainer),
+ typeof(CursorTrail)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ SetContents(() => new MovingCursorInputManager
+ {
+ Child = new ClickingCursorContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ }
+ });
+ }
+
+ private class ClickingCursorContainer : OsuCursorContainer
+ {
+ protected override void Update()
+ {
+ base.Update();
+
+ double currentTime = Time.Current;
+
+ if (((int)(currentTime / 1000)) % 2 == 0)
+ OnPressed(OsuAction.LeftButton);
+ else
+ OnReleased(OsuAction.LeftButton);
+ }
+ }
+
+ private class MovingCursorInputManager : ManualInputManager
+ {
+ public MovingCursorInputManager()
+ {
+ UseParentInput = false;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ const double spin_duration = 5000;
+ double currentTime = Time.Current;
+
+ double angle = (currentTime % spin_duration) / spin_duration * 2 * Math.PI;
+ Vector2 rPos = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
+
+ MoveMouseTo(ToScreenSpace(DrawSize / 2 + DrawSize / 3 * rPos));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
similarity index 55%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 31f3146046..84a7bfc53e 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.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 TestCaseHitCircle : OsuTestCase
+ public class TestSceneHitCircle : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(DrawableHitCircle)
};
- private readonly Container content;
- protected override Container Content => content;
-
private int depthIndex;
- public TestCaseHitCircle()
+ 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/TestSceneHitCircleComboChange.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
new file mode 100644
index 0000000000..5695462859
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
@@ -0,0 +1,26 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneHitCircleComboChange : TestSceneHitCircle
+ {
+ private readonly Bindable comboIndex = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
+ }
+
+ protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
+ {
+ circle.ComboIndexBindable.BindTo(comboIndex);
+ circle.IndexInCurrentComboBindable.BindTo(comboIndex);
+ return base.CreateDrawableHitCircle(circle, auto);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
similarity index 84%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
index 7391c0f11a..55c6b22146 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
@@ -10,11 +10,11 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseHitCircleHidden : TestCaseHitCircle
+ public class TestSceneHitCircleHidden : TestSceneHitCircle
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestCaseHitCircleHidden()
+ public TestSceneHitCircleHidden()
{
Mods.Value = new[] { new OsuModHidden() };
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
similarity index 66%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
index 8d097ff1c1..95c2810e94 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleLongCombo.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs
@@ -10,26 +10,27 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseHitCircleLongCombo : PlayerTestCase
+ public class TestSceneHitCircleLongCombo : PlayerTestScene
{
- public TestCaseHitCircleLongCombo()
+ public TestSceneHitCircleLongCombo()
: base(new OsuRuleset())
{
}
- protected override IBeatmap CreateBeatmap(Ruleset ruleset)
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
- Ruleset = ruleset.RulesetInfo
+ Ruleset = ruleset
}
};
for (int i = 0; i < 512; i++)
- beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
+ if (i % 32 < 20)
+ beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
return beatmap;
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs
index d536e39eef..4c6abc45f7 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs
@@ -11,7 +11,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase
+ public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs
new file mode 100644
index 0000000000..0ecce42e88
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs
@@ -0,0 +1,72 @@
+// 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.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
+ {
+ private HitCircle hitCircle;
+ private DrawableHitCircle drawableObject;
+ private TestBlueprint blueprint;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Clear();
+
+ hitCircle = new HitCircle { Position = new Vector2(256, 192) };
+ hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
+
+ Add(drawableObject = new DrawableHitCircle(hitCircle));
+ AddBlueprint(blueprint = new TestBlueprint(drawableObject));
+ });
+
+ [Test]
+ public void TestInitialState()
+ {
+ AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
+ }
+
+ [Test]
+ public void TestMoveHitObject()
+ {
+ AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
+ AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
+ }
+
+ [Test]
+ public void TestMoveAfterApplyingDefaults()
+ {
+ AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
+ AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
+ AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
+ }
+
+ [Test]
+ public void TestStackedHitObject()
+ {
+ AddStep("set stacking", () => hitCircle.StackHeight = 5);
+ AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition);
+ }
+
+ private class TestBlueprint : HitCircleSelectionBlueprint
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestBlueprint(DrawableHitCircle drawableCircle)
+ : base(drawableCircle)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
new file mode 100644
index 0000000000..a9a6097182
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -0,0 +1,176 @@
+// 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.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Events;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Compose.Components;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
+ {
+ private const double beat_length = 100;
+ private static readonly Vector2 grid_position = new Vector2(512, 384);
+
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(CircularDistanceSnapGrid)
+ };
+
+ [Cached(typeof(IEditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
+
+ [Cached]
+ private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
+
+ [Cached(typeof(IDistanceSnapProvider))]
+ private readonly SnapProvider snapProvider = new SnapProvider();
+
+ private readonly TestOsuDistanceSnapGrid grid;
+
+ public TestSceneOsuDistanceSnapGrid()
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.SlateGray
+ },
+ grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
+ new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
+ };
+ }
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
+ editorBeatmap.ControlPointInfo.Clear();
+ editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
+ });
+
+ [TestCase(1)]
+ [TestCase(2)]
+ [TestCase(3)]
+ [TestCase(4)]
+ [TestCase(6)]
+ [TestCase(8)]
+ [TestCase(12)]
+ [TestCase(16)]
+ public void TestBeatDivisor(int divisor)
+ {
+ AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
+ }
+
+ [Test]
+ public void TestCursorInCentre()
+ {
+ AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
+ assertSnappedDistance((float)beat_length);
+ }
+
+ [Test]
+ public void TestCursorBeforeMovementPoint()
+ {
+ AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f)));
+ assertSnappedDistance((float)beat_length);
+ }
+
+ [Test]
+ public void TestCursorAfterMovementPoint()
+ {
+ AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f)));
+ assertSnappedDistance((float)beat_length * 2);
+ }
+
+ private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
+ {
+ Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
+
+ return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position));
+ });
+
+ private class SnappingCursorContainer : CompositeDrawable
+ {
+ public Func GetSnapPosition;
+
+ private readonly Drawable cursor;
+
+ public SnappingCursorContainer()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = cursor = new Circle
+ {
+ Origin = Anchor.Centre,
+ Size = new Vector2(50),
+ Colour = Color4.Red
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ base.OnMouseMove(e);
+
+ updatePosition(e.ScreenSpaceMousePosition);
+ return true;
+ }
+
+ private void updatePosition(Vector2 screenSpacePosition)
+ {
+ cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
+ }
+ }
+
+ private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
+ {
+ public new float DistanceSpacing => base.DistanceSpacing;
+
+ public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+
+ private class SnapProvider : IDistanceSnapProvider
+ {
+ public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
+
+ public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
+
+ public float DurationToDistance(double referenceTime, double duration) => 0;
+
+ public double DistanceToDuration(double referenceTime, float distance) => 0;
+
+ public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
+
+ public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
index 1e72591b87..64e7632b1b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuFlashlight.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
@@ -7,7 +7,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseOsuFlashlight : TestCaseOsuPlayer
+ public class TestSceneOsuFlashlight : TestSceneOsuPlayer
{
protected override Player CreatePlayer(Ruleset ruleset)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
similarity index 78%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
index 720c3c66fe..0a33b09ba8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseOsuPlayer.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs
@@ -7,9 +7,9 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseOsuPlayer : PlayerTestCase
+ public class TestSceneOsuPlayer : PlayerTestScene
{
- public TestCaseOsuPlayer()
+ public TestSceneOsuPlayer()
: base(new OsuRuleset())
{
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
similarity index 92%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
index 5956f12146..8e73d6152f 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs
@@ -12,14 +12,14 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseResumeOverlay : ManualInputManagerTestCase
+ public class TestSceneResumeOverlay : ManualInputManagerTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(OsuResumeOverlay),
};
- public TestCaseResumeOverlay()
+ public TestSceneResumeOverlay()
{
ManualOsuInputManager osuInputManager;
CursorContainer cursor;
@@ -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
new file mode 100644
index 0000000000..863d0eda09
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.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 System.Diagnostics;
+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
+ {
+ protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
+ {
+ var drawableHitObject = base.CreateDrawableHitCircle(circle, auto);
+
+ Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
+
+ double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
+ Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay);
+
+ return drawableHitObject;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
new file mode 100644
index 0000000000..02c65db6ad
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs
@@ -0,0 +1,159 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Timing;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSkinFallbacks : PlayerTestScene
+ {
+ private readonly TestSource testUserSkin;
+ private readonly TestSource testBeatmapSkin;
+
+ public TestSceneSkinFallbacks()
+ : base(new OsuRuleset())
+ {
+ testUserSkin = new TestSource("user");
+ testBeatmapSkin = new TestSource("beatmap");
+ }
+
+ [Test]
+ public void TestBeatmapSkinDefault()
+ {
+ AddStep("enable user provider", () => testUserSkin.Enabled = true);
+
+ AddStep("enable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, true));
+ checkNextHitObject("beatmap");
+
+ AddStep("disable beatmap skin", () => LocalConfig.Set(OsuSetting.BeatmapSkins, false));
+ checkNextHitObject("user");
+
+ AddStep("disable user provider", () => testUserSkin.Enabled = false);
+ checkNextHitObject(null);
+ }
+
+ private void checkNextHitObject(string skin) =>
+ AddUntilStep($"check skin from {skin}", () =>
+ {
+ var firstObject = ((TestPlayer)Player).DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.OfType().FirstOrDefault();
+
+ if (firstObject == null)
+ return false;
+
+ var skinnable = firstObject.ApproachCircle.Child as SkinnableDrawable;
+
+ if (skin == null && skinnable?.Drawable is Sprite)
+ // check for default skin provider
+ return true;
+
+ var text = skinnable?.Drawable as SpriteText;
+
+ return text?.Text == skin;
+ });
+
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ protected override Player CreatePlayer(Ruleset ruleset) => new SkinProvidingPlayer(testUserSkin);
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => new CustomSkinWorkingBeatmap(beatmap, Clock, audio, testBeatmapSkin);
+
+ public class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly ISkinSource skin;
+
+ public CustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock frameBasedClock, AudioManager audio, ISkinSource skin)
+ : base(beatmap, frameBasedClock, audio)
+ {
+ this.skin = skin;
+ }
+
+ protected override ISkin GetSkin() => skin;
+ }
+
+ public class SkinProvidingPlayer : TestPlayer
+ {
+ private readonly TestSource userSkin;
+
+ public SkinProvidingPlayer(TestSource userSkin)
+ {
+ this.userSkin = userSkin;
+ }
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+
+ dependencies.CacheAs(userSkin);
+
+ return dependencies;
+ }
+ }
+
+ public class TestSource : ISkinSource
+ {
+ private readonly string identifier;
+
+ public TestSource(string identifier)
+ {
+ this.identifier = identifier;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (!enabled) return null;
+
+ return new SpriteText
+ {
+ Text = identifier,
+ Font = OsuFont.Default.With(size: 30),
+ };
+ }
+
+ public Texture GetTexture(string componentName) => null;
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
+
+ public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => default;
+ public IBindable GetConfig(TLookup lookup) => null;
+
+ public event Action SourceChanged;
+
+ private bool enabled = true;
+
+ public bool Enabled
+ {
+ get => enabled;
+ set
+ {
+ if (value == enabled)
+ return;
+
+ enabled = value;
+ SourceChanged?.Invoke();
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
new file mode 100644
index 0000000000..a955911bd5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -0,0 +1,357 @@
+// 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.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+using osu.Game.Rulesets.Mods;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestSceneSlider : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Slider),
+ typeof(SliderTick),
+ typeof(SliderTailCircle),
+ typeof(SliderBall),
+ typeof(SliderBody),
+ typeof(SnakingSliderBody),
+ typeof(DrawableSlider),
+ typeof(DrawableSliderTick),
+ typeof(DrawableSliderTail),
+ typeof(DrawableSliderHead),
+ typeof(DrawableRepeatPoint),
+ typeof(DrawableOsuHitObject)
+ };
+
+ private Container content;
+
+ protected override Container Content
+ {
+ get
+ {
+ if (content == null)
+ base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
+
+ return content;
+ }
+ }
+
+ private int depthIndex;
+
+ public TestSceneSlider()
+ {
+ AddStep("Big Single", () => SetContents(() => testSimpleBig()));
+ AddStep("Medium Single", () => SetContents(() => testSimpleMedium()));
+ AddStep("Small Single", () => SetContents(() => testSimpleSmall()));
+ AddStep("Big 1 Repeat", () => SetContents(() => testSimpleBig(1)));
+ AddStep("Medium 1 Repeat", () => SetContents(() => testSimpleMedium(1)));
+ AddStep("Small 1 Repeat", () => SetContents(() => testSimpleSmall(1)));
+ AddStep("Big 2 Repeats", () => SetContents(() => testSimpleBig(2)));
+ AddStep("Medium 2 Repeats", () => SetContents(() => testSimpleMedium(2)));
+ AddStep("Small 2 Repeats", () => SetContents(() => testSimpleSmall(2)));
+
+ AddStep("Slow Slider", () => SetContents(testSlowSpeed)); // slow long sliders take ages already so no repeat steps
+ AddStep("Slow Short Slider", () => SetContents(() => testShortSlowSpeed()));
+ AddStep("Slow Short Slider 1 Repeats", () => SetContents(() => testShortSlowSpeed(1)));
+ AddStep("Slow Short Slider 2 Repeats", () => SetContents(() => testShortSlowSpeed(2)));
+
+ AddStep("Fast Slider", () => SetContents(() => testHighSpeed()));
+ AddStep("Fast Slider 1 Repeat", () => SetContents(() => testHighSpeed(1)));
+ AddStep("Fast Slider 2 Repeats", () => SetContents(() => testHighSpeed(2)));
+ AddStep("Fast Short Slider", () => SetContents(() => testShortHighSpeed()));
+ AddStep("Fast Short Slider 1 Repeat", () => SetContents(() => testShortHighSpeed(1)));
+ AddStep("Fast Short Slider 2 Repeats", () => SetContents(() => testShortHighSpeed(2)));
+ AddStep("Fast Short Slider 6 Repeats", () => SetContents(() => testShortHighSpeed(6)));
+
+ AddStep("Perfect Curve", () => SetContents(() => testPerfect()));
+ AddStep("Perfect Curve 1 Repeat", () => SetContents(() => testPerfect(1)));
+ AddStep("Perfect Curve 2 Repeats", () => SetContents(() => testPerfect(2)));
+
+ AddStep("Linear Slider", () => SetContents(() => testLinear()));
+ AddStep("Linear Slider 1 Repeat", () => SetContents(() => testLinear(1)));
+ AddStep("Linear Slider 2 Repeats", () => SetContents(() => testLinear(2)));
+
+ AddStep("Bezier Slider", () => SetContents(() => testBezier()));
+ AddStep("Bezier Slider 1 Repeat", () => SetContents(() => testBezier(1)));
+ AddStep("Bezier Slider 2 Repeats", () => SetContents(() => testBezier(2)));
+
+ AddStep("Linear Overlapping", () => SetContents(() => testLinearOverlapping()));
+ AddStep("Linear Overlapping 1 Repeat", () => SetContents(() => testLinearOverlapping(1)));
+ AddStep("Linear Overlapping 2 Repeats", () => SetContents(() => testLinearOverlapping(2)));
+
+ AddStep("Catmull Slider", () => SetContents(() => testCatmull()));
+ AddStep("Catmull Slider 1 Repeat", () => SetContents(() => testCatmull(1)));
+ AddStep("Catmull Slider 2 Repeats", () => SetContents(() => testCatmull(2)));
+
+ AddStep("Big Single, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset()));
+ AddStep("Big 1 Repeat, Large StackOffset", () => SetContents(() => testSimpleBigLargeStackOffset(1)));
+
+ AddStep("Distance Overflow", () => SetContents(() => testDistanceOverflow()));
+ AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
+ }
+
+ [Test]
+ public void TestChangeStackHeight()
+ {
+ DrawableSlider slider = null;
+
+ AddStep("create slider", () =>
+ {
+ slider = (DrawableSlider)createSlider(repeats: 1);
+ Add(slider);
+ });
+
+ AddStep("change stack height", () => slider.HitObject.StackHeight = 10);
+ AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
+ }
+
+ private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
+
+ private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
+
+ private Drawable testDistanceOverflow(int repeats = 0)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(239, 176),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(154, 28),
+ new Vector2(52, -34)
+ }, 700),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats),
+ StackHeight = 10
+ };
+
+ return createDrawable(slider, 2, 2);
+ }
+
+ private Drawable testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
+
+ private Drawable testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
+
+ private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5);
+
+ private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5);
+
+ private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15);
+
+ private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
+
+ private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(-(distance / 2), 0),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(distance, 0),
+ }, distance),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats),
+ StackHeight = stackHeight
+ };
+
+ return createDrawable(slider, circleSize, speedMultiplier);
+ }
+
+ private Drawable testPerfect(int repeats = 0)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(-200, 0),
+ Path = new SliderPath(PathType.PerfectCurve, new[]
+ {
+ Vector2.Zero,
+ new Vector2(200, 200),
+ new Vector2(400, 0)
+ }, 600),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats)
+ };
+
+ return createDrawable(slider, 2, 3);
+ }
+
+ private Drawable testLinear(int repeats = 0) => createLinear(repeats);
+
+ private Drawable createLinear(int repeats)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(-200, 0),
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(150, 75),
+ new Vector2(200, 0),
+ new Vector2(300, -200),
+ new Vector2(400, 0),
+ new Vector2(430, 0)
+ }),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats)
+ };
+
+ return createDrawable(slider, 2, 3);
+ }
+
+ private Drawable testBezier(int repeats = 0) => createBezier(repeats);
+
+ private Drawable createBezier(int repeats)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(-200, 0),
+ Path = new SliderPath(PathType.Bezier, new[]
+ {
+ Vector2.Zero,
+ new Vector2(150, 75),
+ new Vector2(200, 100),
+ new Vector2(300, -200),
+ new Vector2(430, 0)
+ }),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats)
+ };
+
+ return createDrawable(slider, 2, 3);
+ }
+
+ private Drawable testLinearOverlapping(int repeats = 0) => createOverlapping(repeats);
+
+ private Drawable createOverlapping(int repeats)
+ {
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(0, 0),
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(-200, 0),
+ new Vector2(0, 0),
+ new Vector2(0, -200),
+ new Vector2(-200, -200),
+ new Vector2(0, -200)
+ }),
+ RepeatCount = repeats,
+ NodeSamples = createEmptySamples(repeats)
+ };
+
+ return createDrawable(slider, 2, 3);
+ }
+
+ private Drawable testCatmull(int repeats = 0) => createCatmull(repeats);
+
+ private Drawable createCatmull(int repeats = 0)
+ {
+ var repeatSamples = new List>();
+ for (int i = 0; i < repeats; i++)
+ repeatSamples.Add(new List());
+
+ var slider = new Slider
+ {
+ StartTime = Time.Current + 1000,
+ Position = new Vector2(-100, 0),
+ Path = new SliderPath(PathType.Catmull, new[]
+ {
+ Vector2.Zero,
+ new Vector2(50, -50),
+ new Vector2(150, 50),
+ new Vector2(200, 0)
+ }),
+ RepeatCount = repeats,
+ NodeSamples = repeatSamples
+ };
+
+ return createDrawable(slider, 3, 1);
+ }
+
+ private List> createEmptySamples(int repeats)
+ {
+ var repeatSamples = new List>();
+ for (int i = 0; i < repeats; i++)
+ repeatSamples.Add(new List());
+ return repeatSamples;
+ }
+
+ private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
+ {
+ var cpi = new ControlPointInfo();
+ cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
+
+ slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
+
+ var drawable = CreateDrawableSlider(slider);
+
+ foreach (var mod in Mods.Value.OfType())
+ mod.ApplyToDrawableHitObjects(new[] { drawable });
+
+ drawable.OnNewResult += onNewResult;
+
+ return drawable;
+ }
+
+ protected virtual DrawableSlider CreateDrawableSlider(Slider slider) => new DrawableSlider(slider)
+ {
+ Anchor = Anchor.Centre,
+ Depth = depthIndex++
+ };
+
+ private float judgementOffsetDirection = 1;
+
+ private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
+ {
+ var osuObject = judgedObject as DrawableOsuHitObject;
+ if (osuObject == null)
+ return;
+
+ OsuSpriteText text;
+ Add(text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = result.IsHit ? "Hit!" : "Miss!",
+ Colour = result.IsHit ? Color4.Green : Color4.Red,
+ Font = OsuFont.GetFont(size: 30),
+ Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45)
+ });
+
+ text.Delay(150)
+ .Then().FadeOut(200)
+ .Then().Expire();
+
+ judgementOffsetDirection *= -1;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
new file mode 100644
index 0000000000..13ced3019e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
@@ -0,0 +1,28 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneSliderComboChange : TestSceneSlider
+ {
+ private readonly Bindable comboIndex = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
+ }
+
+ protected override DrawableSlider CreateDrawableSlider(Slider slider)
+ {
+ slider.ComboIndexBindable.BindTo(comboIndex);
+ slider.IndexInCurrentComboBindable.BindTo(comboIndex);
+
+ return base.CreateDrawableSlider(slider);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
similarity index 85%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
index 65a8005407..2a9c1d167b 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
@@ -10,11 +10,11 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseSliderHidden : TestCaseSlider
+ public class TestSceneSliderHidden : TestSceneSlider
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestCaseSliderHidden()
+ public TestSceneSliderHidden()
{
Mods.Value = new[] { new OsuModHidden() };
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
similarity index 97%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 76bd9ef758..5f75cbabec 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -20,13 +20,12 @@ using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
-using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseSliderInput : RateAdjustedBeatmapTestCase
+ public class TestSceneSliderInput : RateAdjustedBeatmapTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -299,7 +298,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("load player", () =>
{
- Beatmap.Value = new TestWorkingBeatmap(new Beatmap
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
{
HitObjects =
{
@@ -314,16 +313,14 @@ namespace osu.Game.Rulesets.Osu.Tests
}, 25),
}
},
- ControlPointInfo =
- {
- DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
- },
BeatmapInfo =
{
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
- }, Clock);
+ });
+
+ Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
@@ -353,6 +350,8 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+ protected override bool PauseOnFocusLost => false;
+
public ScoreAccessibleReplayPlayer(Score score)
: base(score, false, false)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs
index f11d98613a..0522260150 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs
@@ -11,7 +11,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase
+ public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
new file mode 100644
index 0000000000..dde2aa53e0
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
@@ -0,0 +1,232 @@
+// 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.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SliderSelectionBlueprint),
+ typeof(SliderCircleSelectionBlueprint),
+ typeof(SliderBodyPiece),
+ typeof(SliderCircle),
+ typeof(PathControlPointVisualiser),
+ typeof(PathControlPointPiece)
+ };
+
+ private Slider slider;
+ private DrawableSlider drawableObject;
+ private TestSliderBlueprint blueprint;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Clear();
+
+ slider = new Slider
+ {
+ Position = new Vector2(256, 192),
+ Path = new SliderPath(PathType.Bezier, new[]
+ {
+ Vector2.Zero,
+ new Vector2(150, 150),
+ new Vector2(300, 0)
+ })
+ };
+
+ slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
+
+ Add(drawableObject = new DrawableSlider(slider));
+ AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
+ });
+
+ [Test]
+ public void TestInitialState()
+ {
+ checkPositions();
+ }
+
+ [Test]
+ public void TestMoveHitObject()
+ {
+ moveHitObject();
+ checkPositions();
+ }
+
+ [Test]
+ public void TestMoveAfterApplyingDefaults()
+ {
+ AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
+ moveHitObject();
+ checkPositions();
+ }
+
+ [Test]
+ public void TestStackedHitObject()
+ {
+ AddStep("set stacking", () => slider.StackHeight = 5);
+ checkPositions();
+ }
+
+ [Test]
+ public void TestSingleControlPointSelection()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkControlPointSelected(0, true);
+ checkControlPointSelected(1, false);
+ }
+
+ [Test]
+ public void TestSingleControlPointDeselectionViaOtherControlPoint()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+
+ moveMouseToControlPoint(1);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkControlPointSelected(0, false);
+ checkControlPointSelected(1, true);
+ }
+
+ [Test]
+ public void TestSingleControlPointDeselectionViaClickOutside()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkControlPointSelected(0, false);
+ checkControlPointSelected(1, false);
+ }
+
+ [Test]
+ public void TestMultipleControlPointSelection()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ moveMouseToControlPoint(1);
+ AddStep("ctrl + click", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Click(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+ checkControlPointSelected(0, true);
+ checkControlPointSelected(1, true);
+ }
+
+ [Test]
+ public void TestMultipleControlPointDeselectionViaOtherControlPoint()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ moveMouseToControlPoint(1);
+ AddStep("ctrl + click", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Click(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ moveMouseToControlPoint(2);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkControlPointSelected(0, false);
+ checkControlPointSelected(1, false);
+ }
+
+ [Test]
+ public void TestMultipleControlPointDeselectionViaClickOutside()
+ {
+ moveMouseToControlPoint(0);
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ moveMouseToControlPoint(1);
+ AddStep("ctrl + click", () =>
+ {
+ InputManager.PressKey(Key.ControlLeft);
+ InputManager.Click(MouseButton.Left);
+ InputManager.ReleaseKey(Key.ControlLeft);
+ });
+
+ AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
+ AddStep("click", () => InputManager.Click(MouseButton.Left));
+ checkControlPointSelected(0, false);
+ checkControlPointSelected(1, false);
+ }
+
+ private void moveHitObject()
+ {
+ AddStep("move hitobject", () =>
+ {
+ slider.Position = new Vector2(300, 225);
+ });
+ }
+
+ private void checkPositions()
+ {
+ AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
+
+ AddAssert("head positioned correctly",
+ () => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
+
+ AddAssert("tail positioned correctly",
+ () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
+ }
+
+ private void moveMouseToControlPoint(int index)
+ {
+ AddStep($"move mouse to control point {index}", () =>
+ {
+ Vector2 position = slider.Position + slider.Path.ControlPoints[index];
+ InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
+ });
+ }
+
+ private void checkControlPointSelected(int index, bool selected)
+ => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected);
+
+ private class TestSliderBlueprint : SliderSelectionBlueprint
+ {
+ public new SliderBodyPiece BodyPiece => base.BodyPiece;
+ public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
+ public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
+ public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
+
+ public TestSliderBlueprint(DrawableSlider slider)
+ : base(slider)
+ {
+ }
+
+ protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
+ }
+
+ private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
+ {
+ public new HitCirclePiece CirclePiece => base.CirclePiece;
+
+ public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
+ : base(slider, position)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
similarity index 97%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index ab33d1e518..3ed3f3e981 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -18,7 +18,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseSpinner : OsuTestCase
+ public class TestSceneSpinner : OsuTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private int depthIndex;
- public TestCaseSpinner()
+ public TestSceneSpinner()
{
base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 }));
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
similarity index 84%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
index 24e3bcb47b..a0ab1908d6 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
@@ -10,11 +10,11 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
- public class TestCaseSpinnerHidden : TestCaseSpinner
+ public class TestSceneSpinnerHidden : TestSceneSpinner
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestCaseSpinnerHidden()
+ public TestSceneSpinnerHidden()
{
Mods.Value = new[] { new OsuModHidden() };
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs
similarity index 89%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs
index 9001ad3596..d74d072857 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs
@@ -11,7 +11,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase
+ public class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
new file mode 100644
index 0000000000..cded7f0e95
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -0,0 +1,96 @@
+// 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.Audio;
+using osu.Framework.MathUtils;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+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.Linq;
+using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneSpinnerRotation : TestSceneOsuPlayer
+ {
+ [Resolved]
+ private AudioManager audioManager { get; set; }
+
+ private TrackVirtualManual track;
+
+ protected override bool Autoplay => true;
+
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
+ {
+ var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ track = (TrackVirtualManual)working.Track;
+ return working;
+ }
+
+ private DrawableSpinner drawableSpinner;
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First());
+ }
+
+ [Test]
+ public void TestSpinnerRewindingRotation()
+ {
+ addSeekStep(5000);
+ AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
+
+ addSeekStep(0);
+ AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
+ }
+
+ [Test]
+ public void TestSpinnerMiddleRewindingRotation()
+ {
+ double estimatedRotation = 0;
+
+ addSeekStep(5000);
+ AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute);
+
+ addSeekStep(2500);
+ addSeekStep(5000);
+ AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
+ }
+
+ private void addSeekStep(double time)
+ {
+ AddStep($"seek to {time}", () => track.Seek(time));
+
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Spinner
+ {
+ Position = new Vector2(256, 192),
+ EndTime = 5000,
+ },
+ // placeholder object to avoid hitting the results screen
+ new HitObject
+ {
+ StartTime = 99999,
+ }
+ }
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
similarity index 78%
rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs
rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
index 11f5ddf8b5..d777ca3610 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs
@@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
@@ -17,7 +16,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
- public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase
+ public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -25,9 +24,7 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SpinnerPiece)
};
- private readonly DrawableSpinner drawableSpinner;
-
- public TestCaseSpinnerSelectionBlueprint()
+ public TestSceneSpinnerSelectionBlueprint()
{
var spinner = new Spinner
{
@@ -35,16 +32,19 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = -1000,
EndTime = 2000
};
+
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
+ DrawableSpinner drawableSpinner;
+
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Child = drawableSpinner = new DrawableSpinner(spinner)
});
- }
- protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
+ AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
+ }
}
}
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 8c31db9a7d..3aea9e0387 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,14 +2,14 @@
-
-
-
+
+
+
WinExe
- netcoreapp2.2
+ netcoreapp3.0
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..b0d261a1cc 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -13,6 +13,8 @@ using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Difficulty
{
@@ -28,14 +30,17 @@ 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;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+ HitWindows hitWindows = new OsuHitWindows();
+ hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
- double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate;
+ double hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
@@ -50,7 +55,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/BlueprintPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs
new file mode 100644
index 0000000000..b9c77d3f56
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.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 osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Osu.Objects;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints
+{
+ ///
+ /// A piece of a selection or placement blueprint which visualises an .
+ ///
+ /// The type of which this visualises.
+ public abstract class BlueprintPiece : CompositeDrawable
+ where T : OsuHitObject
+ {
+ ///
+ /// Updates this using the properties of a .
+ ///
+ /// The to reference properties from.
+ public virtual void UpdateFrom(T hitObject)
+ {
+ Position = hitObject.StackedPosition;
+ }
+ }
+}
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..2b6b93a590 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs
@@ -10,18 +10,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
{
- public class HitCirclePiece : HitObjectPiece
+ public class HitCirclePiece : BlueprintPiece
{
- private readonly HitCircle hitCircle;
-
- public HitCirclePiece(HitCircle hitCircle)
- : base(hitCircle)
+ public HitCirclePiece()
{
- this.hitCircle = hitCircle;
Origin = Anchor.Centre;
- Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2);
- Scale = new Vector2(hitCircle.Scale);
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
CornerRadius = Size.X / 2;
InternalChild = new RingPiece();
@@ -31,12 +26,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
private void load(OsuColour colours)
{
Colour = colours.Yellow;
-
- PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
- StackHeightBindable.BindValueChanged(_ => UpdatePosition());
- ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
}
- protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
+ public override void UpdateFrom(HitCircle hitObject)
+ {
+ base.UpdateFrom(hitObject);
+
+ Scale = new Vector2(hitObject.Scale);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index a4050f0c31..bb47c7e464 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -13,31 +13,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
+ private readonly HitCirclePiece circlePiece;
+
public HitCirclePlacementBlueprint()
: base(new HitCircle())
{
- InternalChild = new HitCirclePiece(HitObject);
+ InternalChild = circlePiece = new HitCirclePiece();
}
- protected override void LoadComplete()
+ protected override void Update()
{
- base.LoadComplete();
+ base.Update();
- // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
- HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
+ circlePiece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)
{
- HitObject.StartTime = EditorClock.CurrentTime;
EndPlacement();
return true;
}
- protected override bool OnMouseMove(MouseMoveEvent e)
+ public override void UpdatePosition(Vector2 screenSpacePosition)
{
- HitObject.Position = e.MousePosition;
- return true;
+ HitObject.Position = ToLocalSpace(screenSpacePosition);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs
index 83787e2219..093bae854e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs
@@ -1,18 +1,35 @@
// 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.Primitives;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
- public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
+ public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
{
- public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
- : base(hitCircle)
+ protected new DrawableHitCircle DrawableObject => (DrawableHitCircle)base.DrawableObject;
+
+ protected readonly HitCirclePiece CirclePiece;
+
+ public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
+ : base(drawableCircle)
{
- InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
+ InternalChild = CirclePiece = new HitCirclePiece();
}
+
+ protected override void Update()
+ {
+ base.Update();
+
+ CirclePiece.UpdateFrom(HitObject);
+ }
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
+
+ public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs
deleted file mode 100644
index 315a5a2b9d..0000000000
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs
+++ /dev/null
@@ -1,36 +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.Bindables;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Rulesets.Osu.Objects;
-using osuTK;
-
-namespace osu.Game.Rulesets.Osu.Edit.Blueprints
-{
- ///
- /// A piece of a blueprint which responds to changes in the state of a .
- ///
- public abstract class HitObjectPiece : CompositeDrawable
- {
- protected readonly IBindable PositionBindable = new Bindable();
- protected readonly IBindable StackHeightBindable = new Bindable();
- protected readonly IBindable ScaleBindable = new Bindable();
-
- private readonly OsuHitObject hitObject;
-
- protected HitObjectPiece(OsuHitObject hitObject)
- {
- this.hitObject = hitObject;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- PositionBindable.BindTo(hitObject.PositionBindable);
- StackHeightBindable.BindTo(hitObject.StackHeightBindable);
- ScaleBindable.BindTo(hitObject.ScaleBindable);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
index dd524252f3..a864257274 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
@@ -7,12 +7,13 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
- public class OsuSelectionBlueprint : SelectionBlueprint
+ public abstract class OsuSelectionBlueprint : SelectionBlueprint
+ where T : OsuHitObject
{
- protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject;
+ protected T HitObject => (T)DrawableObject.HitObject;
- public OsuSelectionBlueprint(DrawableHitObject hitObject)
- : base(hitObject)
+ protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
+ : base(drawableObject)
{
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs
deleted file mode 100644
index 8fd1d6d6f9..0000000000
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs
+++ /dev/null
@@ -1,32 +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.Bindables;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Objects;
-
-namespace osu.Game.Rulesets.Osu.Edit.Blueprints
-{
- ///
- /// A piece of a blueprint which responds to changes in the state of a .
- ///
- public abstract class SliderPiece : HitObjectPiece
- {
- protected readonly IBindable PathBindable = new Bindable();
-
- private readonly Slider slider;
-
- protected SliderPiece(Slider slider)
- : base(slider)
- {
- this.slider = slider;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- PathBindable.BindTo(slider.PathBindable);
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index e257369ad9..e2ea6a12d7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -1,26 +1,35 @@
// 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;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
- public class PathControlPointPiece : CompositeDrawable
+ public class PathControlPointPiece : BlueprintPiece
{
- private readonly Slider slider;
- private readonly int index;
+ public Action RequestSelection;
+ public Action ControlPointsChanged;
+ public readonly BindableBool IsSelected = new BindableBool();
+ public readonly int Index;
+
+ private readonly Slider slider;
private readonly Path path;
- private readonly CircularContainer marker;
+ private readonly Container marker;
+ private readonly Drawable markerRing;
+
+ private bool isClicked;
[Resolved]
private OsuColour colours { get; set; }
@@ -28,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public PathControlPointPiece(Slider slider, int index)
{
this.slider = slider;
- this.index = index;
+ Index = index;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
@@ -40,13 +49,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
Anchor = Anchor.Centre,
PathRadius = 1
},
- marker = new CircularContainer
+ marker = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Size = new Vector2(10),
- Masking = true,
- Child = new Box { RelativeSizeAxes = Axes.Both }
+ AutoSizeAxes = Axes.Both,
+ Children = new[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(10),
+ },
+ markerRing = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(14),
+ Masking = true,
+ BorderThickness = 2,
+ BorderColour = Color4.White,
+ Alpha = 0,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ }
}
};
}
@@ -55,30 +87,69 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
base.Update();
- Position = slider.StackedPosition + slider.Path.ControlPoints[index];
+ Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
- marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
+ updateMarkerDisplay();
+ updateConnectingPath();
+ }
+ ///
+ /// Updates the state of the circular control point marker.
+ ///
+ private void updateMarkerDisplay()
+ {
+ markerRing.Alpha = IsSelected.Value ? 1 : 0;
+
+ Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
+ if (IsHovered || isClicked || IsSelected.Value)
+ colour = Color4.White;
+ marker.Colour = colour;
+ }
+
+ ///
+ /// Updates the path connecting this control point to the previous one.
+ ///
+ private void updateConnectingPath()
+ {
path.ClearVertices();
- if (index != slider.Path.ControlPoints.Length - 1)
+ if (Index != slider.Path.ControlPoints.Length - 1)
{
path.AddVertex(Vector2.Zero);
- path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]);
+ path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
}
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}
+ // The connecting path is excluded from positional input
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
+ protected override bool OnMouseDown(MouseDownEvent e)
+ {
+ isClicked = true;
+ return true;
+ }
+
+ protected override bool OnMouseUp(MouseUpEvent e)
+ {
+ isClicked = false;
+ return true;
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ RequestSelection?.Invoke(Index);
+ return true;
+ }
+
protected override bool OnDragStart(DragStartEvent e) => true;
protected override bool OnDrag(DragEvent e)
{
var newControlPoints = slider.Path.ControlPoints.ToArray();
- if (index == 0)
+ if (Index == 0)
{
// Special handling for the head - only the position of the slider changes
slider.Position += e.Delta;
@@ -88,15 +159,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
newControlPoints[i] -= e.Delta;
}
else
- newControlPoints[index] += e.Delta;
+ newControlPoints[Index] += e.Delta;
if (isSegmentSeparatorWithNext)
- newControlPoints[index + 1] = newControlPoints[index];
+ newControlPoints[Index + 1] = newControlPoints[Index];
if (isSegmentSeparatorWithPrevious)
- newControlPoints[index - 1] = newControlPoints[index];
+ newControlPoints[Index - 1] = newControlPoints[Index];
- slider.Path = new SliderPath(slider.Path.Type, newControlPoints);
+ ControlPointsChanged?.Invoke(newControlPoints);
return true;
}
@@ -105,8 +176,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
- private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index];
+ private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
- private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index];
+ private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index df846b5d5b..b70c11427a 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -1,39 +1,74 @@
// 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 System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+using osu.Framework.Input.Events;
using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
- public class PathControlPointVisualiser : SliderPiece
+ public class PathControlPointVisualiser : CompositeDrawable
{
+ public Action ControlPointsChanged;
+
+ internal readonly Container Pieces;
private readonly Slider slider;
- private readonly Container pieces;
+ private InputManager inputManager;
public PathControlPointVisualiser(Slider slider)
- : base(slider)
{
this.slider = slider;
- InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both };
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both };
}
- [BackgroundDependencyLoader]
- private void load()
+ protected override void LoadComplete()
{
- PathBindable.BindValueChanged(_ => updatePathControlPoints(), true);
+ base.LoadComplete();
+
+ inputManager = GetContainingInputManager();
}
- private void updatePathControlPoints()
+ protected override void Update()
{
- while (slider.Path.ControlPoints.Length > pieces.Count)
- pieces.Add(new PathControlPointPiece(slider, pieces.Count));
- while (slider.Path.ControlPoints.Length < pieces.Count)
- pieces.Remove(pieces[pieces.Count - 1]);
+ base.Update();
+
+ while (slider.Path.ControlPoints.Length > Pieces.Count)
+ {
+ Pieces.Add(new PathControlPointPiece(slider, Pieces.Count)
+ {
+ ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
+ RequestSelection = selectPiece
+ });
+ }
+
+ while (slider.Path.ControlPoints.Length < Pieces.Count)
+ Pieces.Remove(Pieces[Pieces.Count - 1]);
+ }
+
+ protected override bool OnClick(ClickEvent e)
+ {
+ foreach (var piece in Pieces)
+ piece.IsSelected.Value = false;
+ return false;
+ }
+
+ private void selectPiece(int index)
+ {
+ if (inputManager.CurrentState.Keyboard.ControlPressed)
+ Pieces[index].IsSelected.Toggle();
+ else
+ {
+ foreach (var piece in Pieces)
+ piece.IsSelected.Value = piece.Index == index;
+ }
}
}
}
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..d28cf7b492 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs
@@ -11,20 +11,15 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
- public class SliderBodyPiece : SliderPiece
+ public class SliderBodyPiece : BlueprintPiece
{
- private readonly Slider slider;
private readonly ManualSliderBody body;
- public SliderBodyPiece(Slider slider)
- : base(slider)
+ public SliderBodyPiece()
{
- this.slider = slider;
-
InternalChild = body = new ManualSliderBody
{
- AccentColour = Color4.Transparent,
- PathRadius = slider.Scale * 64
+ AccentColour = Color4.Transparent
};
}
@@ -32,19 +27,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void load(OsuColour colours)
{
body.BorderColour = colours.Yellow;
-
- PositionBindable.BindValueChanged(_ => updatePosition(), true);
- ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true);
}
- private void updatePosition() => Position = slider.StackedPosition;
-
- protected override void Update()
+ public override void UpdateFrom(Slider hitObject)
{
- base.Update();
+ base.UpdateFrom(hitObject);
+
+ body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
var vertices = new List();
- slider.Path.GetPathToProgress(vertices, 0, 1);
+ hitObject.Path.GetPathToProgress(vertices, 0, 1);
body.SetVertices(vertices);
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs
deleted file mode 100644
index 2ecfea2e3e..0000000000
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs
+++ /dev/null
@@ -1,47 +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.Bindables;
-using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
-using osu.Game.Rulesets.Osu.Objects;
-
-namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
-{
- public class SliderCirclePiece : HitCirclePiece
- {
- private readonly IBindable pathBindable = new Bindable();
-
- private readonly Slider slider;
- private readonly SliderPosition position;
-
- public SliderCirclePiece(Slider slider, SliderPosition position)
- : base(slider.HeadCircle)
- {
- this.slider = slider;
- this.position = position;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- pathBindable.BindTo(slider.PathBindable);
- pathBindable.BindValueChanged(_ => UpdatePosition(), true);
- }
-
- protected override void UpdatePosition()
- {
- switch (position)
- {
- case SliderPosition.Start:
- Position = slider.StackedPosition + slider.Path.PositionAt(0);
- break;
-
- case SliderPosition.End:
- Position = slider.StackedPosition + slider.Path.PositionAt(1);
- break;
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs
index c9f005495c..f09279ed73 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs
@@ -1,22 +1,34 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
- public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
+ public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
{
- public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
- : base(hitObject)
+ protected readonly HitCirclePiece CirclePiece;
+
+ private readonly SliderPosition position;
+
+ public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
+ : base(slider)
{
- InternalChild = new SliderCirclePiece(slider, position);
+ this.position = position;
+ InternalChild = CirclePiece = new HitCirclePiece();
Select();
}
+ protected override void Update()
+ {
+ base.Update();
+
+ CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle);
+ }
+
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
index 55de626d7d..6f5309c2c2 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs
@@ -6,11 +6,13 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osuTK;
using osuTK.Input;
@@ -21,11 +23,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
+ private SliderBodyPiece bodyPiece;
+ private HitCirclePiece headCirclePiece;
+ private HitCirclePiece tailCirclePiece;
+
private readonly List segments = new List();
private Vector2 cursor;
+ private InputManager inputManager;
private PlacementState state;
+ [Resolved(CanBeNull = true)]
+ private HitObjectComposer composer { get; set; }
+
public SliderPlacementBlueprint()
: base(new Objects.Slider())
{
@@ -38,10 +48,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
InternalChildren = new Drawable[]
{
- new SliderBodyPiece(HitObject),
- new SliderCirclePiece(HitObject, SliderPosition.Start),
- new SliderCirclePiece(HitObject, SliderPosition.End),
- new PathControlPointVisualiser(HitObject),
+ bodyPiece = new SliderBodyPiece(),
+ headCirclePiece = new HitCirclePiece(),
+ tailCirclePiece = new HitCirclePiece(),
+ new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
};
setState(PlacementState.Initial);
@@ -50,25 +60,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override void LoadComplete()
{
base.LoadComplete();
-
- // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
- HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
+ inputManager = GetContainingInputManager();
}
- protected override bool OnMouseMove(MouseMoveEvent e)
+ public override void UpdatePosition(Vector2 screenSpacePosition)
{
switch (state)
{
case PlacementState.Initial:
- HitObject.Position = e.MousePosition;
- return true;
+ HitObject.Position = ToLocalSpace(screenSpacePosition);
+ break;
case PlacementState.Body:
- cursor = e.MousePosition - HitObject.Position;
- return true;
+ // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
+ // is used instead since snapping control points doesn't make much sense
+ cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
+ break;
}
-
- return false;
}
protected override bool OnClick(ClickEvent e)
@@ -109,8 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve()
{
BeginPlacement();
-
- HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body);
}
@@ -128,8 +134,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider()
{
- var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
- HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
+ Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
+
+ var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
+ var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
+
+ HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
+
+ bodyPiece.UpdateFrom(HitObject);
+ headCirclePiece.UpdateFrom(HitObject.HeadCircle);
+ tailCirclePiece.UpdateFrom(HitObject.TailCircle);
}
private void setState(PlacementState newState)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
index fb8c081ff7..f612ba9dfc 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.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 osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@@ -9,9 +13,15 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
- public class SliderSelectionBlueprint : OsuSelectionBlueprint
+ public class SliderSelectionBlueprint : OsuSelectionBlueprint
{
- private readonly SliderCircleSelectionBlueprint headBlueprint;
+ protected readonly SliderBodyPiece BodyPiece;
+ protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
+ protected readonly SliderCircleSelectionBlueprint TailBlueprint;
+ protected readonly PathControlPointVisualiser ControlPointVisualiser;
+
+ [Resolved(CanBeNull = true)]
+ private HitObjectComposer composer { get; set; }
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
@@ -20,13 +30,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
InternalChildren = new Drawable[]
{
- new SliderBodyPiece(sliderObject),
- headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
- new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
- new PathControlPointVisualiser(sliderObject),
+ BodyPiece = new SliderBodyPiece(),
+ HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
+ TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
+ ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
};
}
- public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
+ protected override void Update()
+ {
+ base.Update();
+
+ BodyPiece.UpdateFrom(HitObject);
+ }
+
+ private void onNewControlPoints(Vector2[] controlPoints)
+ {
+ var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
+ var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
+
+ HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
+
+ UpdateHitObject();
+ }
+
+ public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
+
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
+
+ protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
index ae94848c81..65c8720031 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs
@@ -12,17 +12,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
- public class SpinnerPiece : HitObjectPiece
+ public class SpinnerPiece : BlueprintPiece
{
- private readonly Spinner spinner;
private readonly CircularContainer circle;
private readonly RingPiece ring;
- public SpinnerPiece(Spinner spinner)
- : base(spinner)
+ public SpinnerPiece()
{
- this.spinner = spinner;
-
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
@@ -44,21 +40,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
Origin = Anchor.Centre
}
};
-
- ring.Scale = new Vector2(spinner.Scale);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
-
- PositionBindable.BindValueChanged(_ => updatePosition(), true);
- StackHeightBindable.BindValueChanged(_ => updatePosition());
- ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
}
- private void updatePosition() => Position = spinner.Position;
+ public override void UpdateFrom(Spinner hitObject)
+ {
+ base.UpdateFrom(hitObject);
+
+ ring.Scale = new Vector2(hitObject.Scale);
+ }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
index 03d761c67f..5525b8936e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
@@ -21,7 +22,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
- InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
+ InternalChild = piece = new SpinnerPiece { Alpha = 0.5f };
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ piece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)
@@ -33,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
}
else
{
- HitObject.StartTime = EditorClock.CurrentTime;
-
isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint);
@@ -43,5 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return true;
}
+
+ public override void UpdatePosition(Vector2 screenSpacePosition)
+ {
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs
index 25cef3b251..f05d4f8435 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs
@@ -8,14 +8,21 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
- public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
+ public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
{
private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner)
{
- InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
+ InternalChild = piece = new SpinnerPiece();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ piece.UpdateFrom(HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
index bcb6099cfb..cc08d356f9 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class DrawableOsuEditRuleset : DrawableOsuRuleset
{
- public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
new file mode 100644
index 0000000000..79cd51a7f4
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
+ {
+ public OsuDistanceSnapGrid(OsuHitObject hitObject)
+ : base(hitObject, hitObject.StackedEndPosition)
+ {
+ Masking = true;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index c5452ae0aa..fcf2772219 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
@@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
=> new DrawableOsuEditRuleset(ruleset, beatmap, mods);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
@@ -52,5 +54,31 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateBlueprintFor(hitObject);
}
+
+ protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects)
+ {
+ var objects = selectedHitObjects.ToList();
+
+ if (objects.Count == 0)
+ {
+ var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
+
+ if (lastObject == null)
+ return null;
+
+ return new OsuDistanceSnapGrid(lastObject);
+ }
+ else
+ {
+ double minTime = objects.Min(h => h.StartTime);
+
+ var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
+
+ if (lastObject == null)
+ return null;
+
+ return new OsuDistanceSnapGrid(lastObject);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
index 1ab1219ab0..472267eb66 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using osu.Framework.Input.Events;
-using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
@@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : SelectionHandler
{
- public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
+ public override void HandleMovement(MoveSelectionEvent moveEvent)
{
foreach (var h in SelectedHitObjects.OfType())
{
@@ -21,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Edit
continue;
}
- h.Position += dragEvent.Delta;
+ h.Position += moveEvent.InstantDelta;
}
- base.HandleDrag(blueprint, dragEvent);
+ base.HandleMovement(moveEvent);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
index c7661bddb1..15444b847b 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu.Judgements
{
@@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Judgements
{
public ComboResult ComboType;
- public OsuJudgementResult(Judgement judgement)
- : base(judgement)
+ public OsuJudgementResult(HitObject hitObject, Judgement judgement)
+ : base(hitObject, judgement)
{
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 401bd28d7c..65d7acc911 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -2,13 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.StateChanges;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModAutopilot : Mod
+ public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset
{
public override string Name => "Autopilot";
public override string Acronym => "AP";
@@ -17,5 +23,41 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
+
+ public bool AllowFail => false;
+ public bool RestartOnFail => false;
+
+ private OsuInputManager inputManager;
+
+ private List replayFrames;
+
+ private int currentFrame;
+
+ public void Update(Playfield playfield)
+ {
+ if (currentFrame == replayFrames.Count - 1) return;
+
+ double time = playfield.Time.Current;
+
+ // Very naive implementation of autopilot based on proximity to replay frames.
+ // TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
+ if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time))
+ {
+ currentFrame++;
+ new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager);
+ }
+
+ // TODO: Implement the functionality to automatically spin spinners
+ }
+
+ public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
+ {
+ // Grab the input manager to disable the user's cursor, and for future use
+ inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
+ inputManager.AllowUserCursorMovement = false;
+
+ // Generate the replay frames the cursor should follow
+ replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast().ToList();
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 445f81c6d4..1eb37f8119 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- Texture = textures.Get("Play/osu/blinds-panel");
+ Texture = textures.Get("Gameplay/osu/blinds-panel");
}
}
}
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