diff --git a/.editorconfig b/.editorconfig
index b5333ad8e7..67f98f94eb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -135,7 +135,7 @@ csharp_preferred_modifier_order = public,private,protected,internal,new,abstract
csharp_style_expression_bodied_accessors = true:warning
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_indexers = true:warning
-csharp_style_expression_bodied_methods = true:silent
+csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = true:warning
csharp_style_expression_bodied_properties = true:warning
csharp_style_expression_bodied_local_functions = true:silent
@@ -176,8 +176,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
-csharp_style_prefer_index_operator = false:none
-csharp_style_prefer_range_operator = false:none
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers
diff --git a/.github/ISSUE_TEMPLATE/00-mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md
deleted file mode 100644
index f171e80b8b..0000000000
--- a/.github/ISSUE_TEMPLATE/00-mobile-issues.md
+++ /dev/null
@@ -1,8 +0,0 @@
----
-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/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
index c8c41e5a78..0aff276d03 100644
--- a/.github/ISSUE_TEMPLATE/01-bug-issues.md
+++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md
@@ -8,4 +8,7 @@ about: Issues regarding encountered bugs.
**osu!lazer version:**
-**Logs:**
+**Logs:**
+*please attach logs here, which are located at:*
+- `%AppData%/osu/logs` *(on Windows),*
+- `~/.local/share/osu/logs` *(on Linux & macOS).*
diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md
index 8ad27e9e31..9c3ae33161 100644
--- a/.github/ISSUE_TEMPLATE/02-crash-issues.md
+++ b/.github/ISSUE_TEMPLATE/02-crash-issues.md
@@ -8,6 +8,9 @@ about: Issues regarding crashes or permanent freezes.
**osu!lazer version:**
-**Logs:**
+**Logs:**
+*please attach logs here, which are located at:*
+- `%AppData%/osu/logs` *(on Windows),*
+- `~/.local/share/osu/logs` *(on Linux & macOS).*
**Computer Specifications:**
diff --git a/.gitignore b/.gitignore
index e6b5db5904..732b171f69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -331,3 +331,6 @@ fastlane/report.xml
# inspectcode
inspectcodereport.xml
inspectcode
+
+# BenchmarkDotNet
+/BenchmarkDotNet.Artifacts
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
new file mode 100644
index 0000000000..1815c271b4
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
index 5372b6f28a..a4154623b6 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index 45a94f37c0..080dc04001 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 1f09381e08..3de6a7e609 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index ba530f0ddf..da14c2a29e 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
index 89d5b45f67..45d1ce25e9 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
index e2628a1bb4..ba80f7c100 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
index f1d0957b8e..911c3ed9b7 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
new file mode 100644
index 0000000000..d85a0ae44c
--- /dev/null
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
index 23b49abcad..ec3c81f4cd 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
@@ -1,6 +1,6 @@
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5940df2191..6480612b2e 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,18 +1,19 @@
{
"version": "0.2.0",
- "configurations": [{
+ "configurations": [
+ {
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -23,13 +24,13 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -40,29 +41,30 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
- }, {
+ },
+ {
"name": "osu! (Tests, Release)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -73,14 +75,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -91,14 +93,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -109,14 +111,14 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@@ -127,18 +129,31 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)",
"linux": {
"env": {
- "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
+ "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
},
+ {
+ "name": "Benchmark",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll",
+ "args": [
+ "--filter",
+ "*"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build benchmarks",
+ "console": "internalConsole"
+ },
{
"name": "Cake: Debug Script",
"type": "coreclr",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 04ff7c1bea..e638dec767 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -2,7 +2,8 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
- "tasks": [{
+ "tasks": [
+ {
"label": "Build osu! (Debug)",
"type": "shell",
"command": "dotnet",
@@ -78,7 +79,8 @@
],
"group": "build",
"problemMatcher": "$msCompile"
- }, {
+ },
+ {
"label": "Build tournament tests (Release)",
"type": "shell",
"command": "dotnet",
@@ -95,7 +97,23 @@
"problemMatcher": "$msCompile"
},
{
- "label": "Restore (netcoreapp3.0)",
+ "label": "Build benchmarks",
+ "type": "shell",
+ "command": "dotnet",
+ "args": [
+ "build",
+ "--no-restore",
+ "osu.Game.Benchmarks",
+ "/p:Configuration=Release",
+ "/p:GenerateFullPaths=true",
+ "/m",
+ "/verbosity:m"
+ ],
+ "group": "build",
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "Restore (netcoreapp3.1)",
"type": "shell",
"command": "dotnet",
"args": [
diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
new file mode 100644
index 0000000000..d497365f87
--- /dev/null
+++ b/CodeAnalysis/osu.ruleset
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index c0d740bac1..21b8b402e0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,11 @@
+
+
+ $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
+
true
$(NoWarn);CS1591
@@ -36,7 +40,7 @@
https://github.com/ppy/osu
Automated release.
ppy Pty Ltd
- Copyright (c) 2019 ppy Pty Ltd
+ Copyright (c) 2020 ppy Pty Ltd
osu game
\ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
index ab594aee74..e3954c2681 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.1)
+ CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
@@ -18,8 +18,8 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.67.0)
- faraday (0.15.4)
+ excon (0.71.1)
+ faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
@@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
- fastlane (2.133.0)
+ fastlane (2.140.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@@ -36,13 +36,13 @@ GEM
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
- excon (>= 0.45.0, < 1.0.0)
- faraday (< 0.16.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (< 0.16.0)
+ faraday_middleware (~> 0.13.1)
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-api-client (>= 0.29.2, < 0.37.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
@@ -61,56 +61,58 @@ GEM
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
- xcodeproj (>= 1.8.1, < 2.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-clean_testflight_testers (0.3.0)
- fastlane-plugin-souyuz (0.8.1)
- souyuz (>= 0.8.1)
+ fastlane-plugin-souyuz (0.9.1)
+ souyuz (= 0.9.1)
fastlane-plugin-xamarin (0.6.3)
gh_inspector (1.1.3)
- google-api-client (0.23.9)
+ google-api-client (0.36.4)
addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.5, < 0.7.0)
+ googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
- mime-types (~> 3.0)
+ mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- signet (~> 0.9)
- google-cloud-core (1.3.1)
+ signet (~> 0.12)
+ google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
- google-cloud-env (1.2.1)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.3.0)
faraday (~> 0.11)
- google-cloud-storage (1.16.0)
+ google-cloud-errors (1.0.0)
+ google-cloud-storage (1.25.1)
+ addressable (~> 2.5)
digest-crc (~> 0.4)
- google-api-client (~> 0.23)
+ google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
- googleauth (>= 0.6.2, < 0.10.0)
- googleauth (0.6.7)
+ googleauth (~> 0.9)
+ mini_mime (~> 1.0)
+ googleauth (0.10.0)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
- signet (~> 0.7)
+ signet (~> 0.12)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
- json (2.2.0)
+ json (2.3.0)
jwt (2.1.0)
- memoist (0.16.0)
- mime-types (3.3)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2019.1009)
- mini_magick (4.9.5)
+ memoist (0.16.2)
+ mini_magick (4.10.1)
+ mini_mime (1.0.2)
mini_portile2 (2.4.0)
- multi_json (1.13.1)
+ multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
- nokogiri (1.10.4)
+ nokogiri (1.10.7)
mini_portile2 (~> 2.4.0)
os (1.0.1)
plist (3.5.0)
@@ -128,12 +130,12 @@ GEM
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- simctl (1.6.6)
+ simctl (1.6.7)
CFPropertyList
naturally
slack-notifier (2.3.2)
- souyuz (0.8.1)
- fastlane (>= 2.29.0)
+ souyuz (0.9.1)
+ fastlane (>= 1.103.0)
highline (~> 1.7)
nokogiri (~> 1.7)
terminal-notifier (2.0.0)
@@ -141,15 +143,15 @@ GEM
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.0)
tty-screen (0.7.0)
- tty-spinner (0.9.1)
+ tty-spinner (0.9.2)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.6)
- unicode-display_width (1.6.0)
+ unicode-display_width (1.6.1)
word_wrap (1.0.0)
- xcodeproj (1.12.0)
+ xcodeproj (1.14.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/LICENCE b/LICENCE
index 21c6a7090f..2435c23545 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2019 ppy Pty Ltd .
+Copyright (c) 2020 ppy Pty Ltd .
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index e2e854c755..77c7eb9d2d 100644
--- a/README.md
+++ b/README.md
@@ -13,35 +13,36 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the
## Status
-This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table.
+This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
-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.
+We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
-Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog).
-
-## Requirements
-
-- 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/install/dependencies?tabs=netcore30&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
-- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
-- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
+- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
+- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
+- Read peppy's [latest blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where lazer is currently and the roadmap going forward.
## Running osu!
-### Releases
-
-If you are not interested in developing the game, you can still consume our [binary releases](https://github.com/ppy/osu/releases).
+If you are looking to install or test osu! without setting up a development environment, you can consume our [binary releases](https://github.com/ppy/osu/releases). Handy links below will download the latest version for your operating system of choice:
**Latest build:**
-| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
-| ------------- | ------------- | ------------- | ------------- |
+| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
+| ------------- | ------------- | ------------- | ------------- | ------------- |
-- **Linux** users are recommended to self-compile until we have official deployment in place.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
+## Developing or debugging
+
+Please make sure you have the following prerequisites:
+
+- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
+- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
+- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
+- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
+
### Downloading the source code
Clone the repository:
diff --git a/build/InspectCode.cake b/build/InspectCode.cake
index bd3fdf5f93..2e7a1d1b28 100644
--- a/build/InspectCode.cake
+++ b/build/InspectCode.cake
@@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
+#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.2"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 28a83fbbae..510b53054b 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -111,7 +111,6 @@ platform :ios do
souyuz(
platform: "ios",
- build_target: "osu_iOS",
plist_path: "../osu.iOS/Info.plist"
)
end
diff --git a/global.json b/global.json
index 43bb34912a..6858d4044d 100644
--- a/global.json
+++ b/global.json
@@ -1,4 +1,9 @@
{
+ "sdk": {
+ "allowPrerelease": false,
+ "rollForward": "minor",
+ "version": "3.1.100"
+ },
"msbuild-sdks": {
"Microsoft.Build.Traversal": "2.0.24"
}
diff --git a/osu.Android.props b/osu.Android.props
index ff6d7396ee..651d1beda1 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -25,7 +25,6 @@
portable
False
DEBUG;TRACE
- false
false
true
false
@@ -34,7 +33,6 @@
false
None
True
- true
false
False
true
@@ -53,7 +51,7 @@
-
-
+
+
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index ac3905a372..0598a50530 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -13,6 +13,7 @@
osu.Android
Properties\AndroidManifest.xml
armeabi-v7a;x86;arm64-v8a
+ false
cjk;mideast;other;rare;west
@@ -52,4 +53,4 @@
-
\ No newline at end of file
+
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
index e6b6446f72..d2c14d321a 100644
--- a/osu.Desktop.slnf
+++ b/osu.Desktop.slnf
@@ -3,6 +3,7 @@
"path": "osu.sln",
"projects": [
"osu.Desktop\\osu.Desktop.csproj",
+ "osu.Game.Benchmarks\\osu.Game.Benchmarks.csproj",
"osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj",
"osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
"osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj",
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
new file mode 100644
index 0000000000..08cc0e7f5f
--- /dev/null
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -0,0 +1,149 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Text;
+using DiscordRPC;
+using DiscordRPC.Message;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Logging;
+using osu.Game.Online.API;
+using osu.Game.Rulesets;
+using osu.Game.Users;
+using LogLevel = osu.Framework.Logging.LogLevel;
+using User = osu.Game.Users.User;
+
+namespace osu.Desktop
+{
+ internal class DiscordRichPresence : Component
+ {
+ private const string client_id = "367827983903490050";
+
+ private DiscordRpcClient client;
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
+ private Bindable user;
+
+ private readonly IBindable status = new Bindable();
+ private readonly IBindable activity = new Bindable();
+
+ private readonly RichPresence presence = new RichPresence
+ {
+ Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(IAPIProvider provider)
+ {
+ client = new DiscordRpcClient(client_id)
+ {
+ SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
+ };
+
+ client.OnReady += onReady;
+
+ // safety measure for now, until we performance test / improve backoff for failed connections.
+ client.OnConnectionFailed += (_, __) => client.Deinitialize();
+
+ client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+
+ (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
+ {
+ status.UnbindBindings();
+ status.BindTo(u.NewValue.Status);
+
+ activity.UnbindBindings();
+ activity.BindTo(u.NewValue.Activity);
+ }, true);
+
+ ruleset.BindValueChanged(_ => updateStatus());
+ status.BindValueChanged(_ => updateStatus());
+ activity.BindValueChanged(_ => updateStatus());
+
+ client.Initialize();
+ }
+
+ private void onReady(object _, ReadyMessage __)
+ {
+ Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
+ updateStatus();
+ }
+
+ private void updateStatus()
+ {
+ if (!client.IsInitialized)
+ return;
+
+ if (status.Value is UserStatusOffline)
+ {
+ client.ClearPresence();
+ return;
+ }
+
+ if (status.Value is UserStatusOnline && activity.Value != null)
+ {
+ presence.State = truncate(activity.Value.Status);
+ presence.Details = truncate(getDetails(activity.Value));
+ }
+ else
+ {
+ presence.State = "Idle";
+ presence.Details = string.Empty;
+ }
+
+ // update user information
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+
+ // update ruleset
+ presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
+ presence.Assets.SmallImageText = ruleset.Value.Name;
+
+ client.SetPresence(presence);
+ }
+
+ private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
+
+ private string truncate(string str)
+ {
+ if (Encoding.UTF8.GetByteCount(str) <= 128)
+ return str;
+
+ ReadOnlyMemory strMem = str.AsMemory();
+
+ do
+ {
+ strMem = strMem[..^1];
+ } while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
+
+ return string.Create(strMem.Length + 1, strMem, (span, mem) =>
+ {
+ mem.Span.CopyTo(span);
+ span[^1] = '…';
+ });
+ }
+
+ private string getDetails(UserActivity activity)
+ {
+ switch (activity)
+ {
+ case UserActivity.SoloGame solo:
+ return solo.Beatmap.ToString();
+
+ case UserActivity.Editing edit:
+ return edit.Beatmap.ToString();
+ }
+
+ return string.Empty;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ client.Dispose();
+ base.Dispose(isDisposing);
+ }
+ }
+}
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 66e7bb381c..f70cc24159 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -60,6 +60,8 @@ namespace osu.Desktop
else
Add(new SimpleUpdateManager());
}
+
+ LoadComponentAsync(new DiscordRichPresence(), Add);
}
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 141b2cdbbc..bd91bcc933 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -22,8 +22,9 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
+ bool useSdl = args.Contains("--sdl");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/Properties/launchSettings.json b/osu.Desktop/Properties/launchSettings.json
new file mode 100644
index 0000000000..5e768ec9fa
--- /dev/null
+++ b/osu.Desktop/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "osu! Desktop": {
+ "commandName": "Project"
+ },
+ "osu! Tournament": {
+ "commandName": "Project",
+ "commandLineArgs": "--tournament"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Desktop/app.manifest b/osu.Desktop/app.manifest
new file mode 100644
index 0000000000..2e9127bf44
--- /dev/null
+++ b/osu.Desktop/app.manifest
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
\ No newline at end of file
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 453cf6f94d..c34e1e1221 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,6 +1,6 @@
- netcoreapp3.0
+ netcoreapp3.1
WinExe
true
click the circles. to the beat.
@@ -8,6 +8,7 @@
osu!lazer
osu!lazer
lazer.ico
+ app.manifest
0.0.0
0.0.0
@@ -23,11 +24,12 @@
-
+
-
+
+
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index a26b35fcd5..a919d54f38 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -12,7 +12,7 @@
click the circles. to the beat.
click the circles.
testing
- Copyright (c) 2019 ppy Pty Ltd
+ Copyright (c) 2020 ppy Pty Ltd
en-AU
diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
new file mode 100644
index 0000000000..394fd75488
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using BenchmarkDotNet.Attributes;
+using osu.Framework.IO.Stores;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.IO.Archives;
+using osu.Game.Resources;
+
+namespace osu.Game.Benchmarks
+{
+ public class BenchmarkBeatmapParsing : BenchmarkTest
+ {
+ private readonly MemoryStream beatmapStream = new MemoryStream();
+
+ public override void SetUp()
+ {
+ using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
+ using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
+ using (var reader = new ZipArchiveReader(archive))
+ reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
+ }
+
+ [Benchmark]
+ public Beatmap BenchmarkBundledBeatmap()
+ {
+ beatmapStream.Seek(0, SeekOrigin.Begin);
+ var reader = new LineBufferedReader(beatmapStream); // no disposal
+
+ var decoder = Decoder.GetDecoder(reader);
+ return decoder.Decode(reader);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs
new file mode 100644
index 0000000000..34f5edd084
--- /dev/null
+++ b/osu.Game.Benchmarks/BenchmarkTest.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Running;
+using NUnit.Framework;
+
+namespace osu.Game.Benchmarks
+{
+ [TestFixture]
+ [MemoryDiagnoser]
+ public abstract class BenchmarkTest
+ {
+ [GlobalSetup]
+ [OneTimeSetUp]
+ public virtual void SetUp()
+ {
+ }
+
+ [Test]
+ public void RunBenchmark() => BenchmarkRunner.Run(GetType());
+ }
+}
diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs
new file mode 100644
index 0000000000..c55075fea6
--- /dev/null
+++ b/osu.Game.Benchmarks/Program.cs
@@ -0,0 +1,17 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using BenchmarkDotNet.Running;
+
+namespace osu.Game.Benchmarks
+{
+ public static class Program
+ {
+ public static void Main(string[] args)
+ {
+ BenchmarkSwitcher
+ .FromAssembly(typeof(Program).Assembly)
+ .Run(args);
+ }
+ }
+}
diff --git a/osu.Game.Benchmarks/Properties/launchSettings.json b/osu.Game.Benchmarks/Properties/launchSettings.json
new file mode 100644
index 0000000000..c1868088f9
--- /dev/null
+++ b/osu.Game.Benchmarks/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "All Benchmarks": {
+ "commandName": "Project",
+ "commandLineArgs": "--filter *"
+ }
+ }
+}
\ No newline at end of file
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
new file mode 100644
index 0000000000..f2e1c0ec3b
--- /dev/null
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ netcoreapp3.1
+ Exe
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
index beca477943..f7f07ef938 100644
--- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Catch.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
index 4030d2d9e7..67d27c33eb 100644
--- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 493ac7ae39..f4749be370 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple-overlay@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple-overlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple-overlay@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-apple@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas-overlay@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas-overlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas-overlay@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-bananas@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-catcher-idle@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-catcher-idle@2x.png
new file mode 100644
index 0000000000..76949ccfcc
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-catcher-idle@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-drop@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-drop@2x.png
new file mode 100644
index 0000000000..ec2fdbdbdb
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-drop@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes-overlay@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes-overlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes-overlay@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-grapes@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange-overlay@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange-overlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange-overlay@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-orange@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear-overlay@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear-overlay@2x.png
new file mode 100644
index 0000000000..4233d9bb6e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear-overlay@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear@2x.png
new file mode 100644
index 0000000000..043bfbfae1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/metrics-skin/fruit-pear@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple-overlay.png
new file mode 100644
index 0000000000..8d9608cfc9
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple.png
new file mode 100644
index 0000000000..be1bda0383
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-apple.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png
new file mode 100644
index 0000000000..3a6612378e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png
new file mode 100644
index 0000000000..afb8698b2d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-bananas.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-drop.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-drop.png
new file mode 100644
index 0000000000..12c74f46e2
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-drop.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes-overlay.png
new file mode 100644
index 0000000000..bb37ba1920
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes.png
new file mode 100644
index 0000000000..10699b1f31
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-grapes.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange-overlay.png
new file mode 100644
index 0000000000..e86aa6e7e3
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange.png
new file mode 100644
index 0000000000..42cc80399f
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-orange.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear-overlay.png
new file mode 100644
index 0000000000..5c479da954
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear.png
new file mode 100644
index 0000000000..9fe400bdd1
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-pear.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-plate.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-plate.png
new file mode 100644
index 0000000000..1da1fdde85
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-plate.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-ryuuta.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-ryuuta.png
new file mode 100644
index 0000000000..f732092379
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/fruit-ryuuta.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit0.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit0.png
new file mode 100644
index 0000000000..2d312ceefd
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit0.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit100.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit100.png
new file mode 100644
index 0000000000..7884dc072d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit100.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit300.png
new file mode 100644
index 0000000000..3e4ec2e047
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit300.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit50.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit50.png
new file mode 100644
index 0000000000..f02ad11a17
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/hit50.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple-overlay.png
new file mode 100755
index 0000000000..fe567d158d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png
new file mode 100755
index 0000000000..17f3be9c26
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-apple.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png
new file mode 100755
index 0000000000..2c94ea78bf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas.png
new file mode 100755
index 0000000000..2c94ea78bf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-bananas.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png
new file mode 100755
index 0000000000..1eea5c2083
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-fail.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png
new file mode 100755
index 0000000000..17177f3246
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-idle.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png
new file mode 100755
index 0000000000..31be03b014
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-catcher-kiai.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop-overlay.png
new file mode 100755
index 0000000000..56bf4a92fb
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop.png
new file mode 100755
index 0000000000..f259684055
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-drop.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png
new file mode 100755
index 0000000000..17f3be9c26
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes.png
new file mode 100755
index 0000000000..3dc60464cf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-grapes.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange-overlay.png
new file mode 100755
index 0000000000..3dc60464cf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png
new file mode 100755
index 0000000000..3dc60464cf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-orange.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear-overlay.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear-overlay.png
new file mode 100755
index 0000000000..3dc60464cf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear-overlay.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png
new file mode 100755
index 0000000000..3dc60464cf
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/fruit-pear.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index ab3c040b4e..74a9c05bf9 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 0ad72412fc..20911b8d06 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
@@ -29,6 +29,12 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
+ [Test]
+ public void TestBananaShower()
+ {
+ AddUntilStep("player is done", () => !Player.ValidForResume);
+ }
+
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -40,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
- beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true });
+ beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 3000, NewCombo = true });
return beatmap;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 9b529a2e4c..4ff9f7a7fe 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -3,104 +3,31 @@
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 osuTK.Graphics;
-using osu.Framework.Audio.Sample;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics.Textures;
-using osu.Game.Audio;
-using osu.Game.Graphics.Sprites;
+using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatcher : OsuTestScene
+ public class TestSceneCatcher : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(CatcherSprite),
+ typeof(CatcherArea),
};
- 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", () =>
+ SetContents(() => new CatcherArea.Catcher
{
- container.Child = new CatchCustomSkinSourceContainer
- {
- Child = new CatcherSprite()
- };
+ RelativePositionAxes = Axes.None,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
});
}
-
- private class CatcherCustomSkin : Container
- {
- public CatcherCustomSkin()
- {
- RelativeSizeAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Blue
- },
- new OsuSpriteText
- {
- 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/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 3ae6886c31..df1ac4c725 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -3,20 +3,24 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatcherArea : OsuTestScene
+ public class TestSceneCatcherArea : SkinnableTestScene
{
private RulesetInfo catchRuleset;
- private TestCatcherArea catcherArea;
public override IReadOnlyList RequiredTypes => new[]
{
@@ -26,20 +30,26 @@ namespace osu.Game.Rulesets.Catch.Tests
public TestSceneCatcherArea()
{
AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
- AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
+ AddToggleStep("Hyperdash", t =>
+ CreatedDrawables.OfType().Select(i => i.Child)
+ .OfType().ForEach(c => c.ToggleHyperDash(t)));
+
+ AddRepeatStep("catch fruit", () =>
+ this.ChildrenOfType().ForEach(area =>
+ area.MovableCatcher.PlaceOnPlate(new DrawableFruit(new TestSceneFruitObjects.TestCatchFruit(FruitVisualRepresentation.Grape)))), 20);
}
private void createCatcher(float size)
{
- Child = new CatchInputManager(catchRuleset)
+ SetContents(() => new CatchInputManager(catchRuleset)
{
RelativeSizeAxes = Axes.Both,
- Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
+ Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.TopLeft
},
- };
+ });
}
[BackgroundDependencyLoader]
@@ -55,6 +65,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
+ public new Catcher MovableCatcher => base.MovableCatcher;
+
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index 0369b6db4e..070847c0c1 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -10,7 +10,7 @@ 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.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Catch.Tests
RelativeSizeAxes = Axes.Both,
Children = new[]
{
- drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap, Array.Empty())
+ drawableRuleset = new DrawableCatchRuleset(new CatchRuleset(), beatmap.GetPlayableBeatmap(new CatchRuleset().RulesetInfo))
}
});
@@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void addToPlayfield(DrawableCatchHitObject drawable)
{
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawableRuleset.Playfield.Add(drawable);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
index f6d26addaa..8c3dfef39c 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjectsHidden.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Rulesets.Catch.Mods;
namespace osu.Game.Rulesets.Catch.Tests
@@ -12,9 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
- public TestSceneDrawableHitObjectsHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new CatchModHidden() };
- }
+ SelectedMods.Value = new[] { new CatchModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index 44517382f7..82d5aa936f 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -5,78 +5,114 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
-using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneFruitObjects : OsuTestScene
+ public class TestSceneFruitObjects : SkinnableTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
typeof(CatchHitObject),
typeof(Fruit),
+ typeof(FruitPiece),
typeof(Droplet),
+ typeof(Banana),
+ typeof(BananaShower),
typeof(DrawableCatchHitObject),
typeof(DrawableFruit),
typeof(DrawableDroplet),
- typeof(BananaShower),
+ typeof(DrawableBanana),
+ typeof(DrawableBananaShower),
typeof(Pulp),
};
- public TestSceneFruitObjects()
+ protected override void LoadComplete()
{
- Add(new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- createDrawable(0),
- createDrawable(1),
- createDrawable(2),
- },
- new Drawable[]
- {
- createDrawable(3),
- createDrawable(4),
- createDrawable(5),
- },
- }
- });
+ base.LoadComplete();
+
+ foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
+ AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
+
+ AddStep("show droplet", () => SetContents(createDrawableDroplet));
+
+ AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
+
+ foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
+ AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
}
- private DrawableFruit createDrawable(int index)
+ private Drawable createDrawableTinyDroplet()
{
- Fruit fruit = index == 5
- ? new Banana
- {
- StartTime = 1000000000000,
- IndexInBeatmap = index,
- Scale = 1.5f,
- }
- : new Fruit
- {
- StartTime = 1000000000000,
- IndexInBeatmap = index,
- Scale = 1.5f,
- };
+ var droplet = new TinyDroplet
+ {
+ StartTime = Clock.CurrentTime,
+ Scale = 1.5f,
+ };
- return new DrawableFruit(fruit)
+ return new DrawableTinyDroplet(droplet)
{
Anchor = Anchor.Centre,
- RelativePositionAxes = Axes.Both,
+ RelativePositionAxes = Axes.None,
Position = Vector2.Zero,
Alpha = 1,
LifetimeStart = double.NegativeInfinity,
LifetimeEnd = double.PositiveInfinity,
};
}
+
+ private Drawable createDrawableDroplet()
+ {
+ var droplet = new Droplet
+ {
+ StartTime = Clock.CurrentTime,
+ Scale = 1.5f,
+ };
+
+ return new DrawableDroplet(droplet)
+ {
+ Anchor = Anchor.Centre,
+ RelativePositionAxes = Axes.None,
+ Position = Vector2.Zero,
+ Alpha = 1,
+ LifetimeStart = double.NegativeInfinity,
+ LifetimeEnd = double.PositiveInfinity,
+ };
+ }
+
+ private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false)
+ {
+ Fruit fruit = new TestCatchFruit(rep)
+ {
+ Scale = 1.5f,
+ HyperDashTarget = hyperdash ? new Banana() : null
+ };
+
+ return new DrawableFruit(fruit)
+ {
+ Anchor = Anchor.Centre,
+ RelativePositionAxes = Axes.None,
+ Position = Vector2.Zero,
+ Alpha = 1,
+ LifetimeStart = double.NegativeInfinity,
+ LifetimeEnd = double.PositiveInfinity,
+ };
+ }
+
+ public class TestCatchFruit : Fruit
+ {
+ public TestCatchFruit(FruitVisualRepresentation rep)
+ {
+ VisualRepresentation = rep;
+ StartTime = 1000000000000;
+ }
+
+ public override FruitVisualRepresentation VisualRepresentation { get; }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
new file mode 100644
index 0000000000..cbc87459e1
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -0,0 +1,56 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+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 TestSceneJuiceStream : PlayerTestScene
+ {
+ public TestSceneJuiceStream()
+ : base(new CatchRuleset())
+ {
+ }
+
+ [Test]
+ public void TestJuiceStreamEndingCombo()
+ {
+ AddUntilStep("player is done", () => !Player.ValidForResume);
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 5, SliderMultiplier = 2 },
+ Ruleset = ruleset
+ },
+ HitObjects = new List
+ {
+ new JuiceStream
+ {
+ X = 0.5f,
+ Path = new SliderPath(PathType.Linear, new[]
+ {
+ Vector2.Zero,
+ new Vector2(0, 100)
+ }),
+ StartTime = 200
+ },
+ new Banana
+ {
+ X = 0.5f,
+ StartTime = 1000,
+ NewCombo = true
+ }
+ }
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 1dbe9b39ee..8c371db257 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
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index b5497ea89f..90a6e609f0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -4,7 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -14,12 +14,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public class CatchBeatmapConverter : BeatmapConverter
{
- public CatchBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public CatchBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index db52fbac1b..1a5d0f983b 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -34,9 +34,14 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
foreach (var obj in Beatmap.HitObjects.OfType())
{
- obj.IndexInBeatmap = index++;
+ obj.IndexInBeatmap = index;
+ foreach (var nested in obj.NestedHitObjects.OfType())
+ nested.IndexInBeatmap = index;
+
if (obj.LastInCombo && obj.NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = true;
+
+ index++;
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 71d68ace94..b9d791fdb1 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -16,15 +16,24 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using System;
+using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
{
- public class CatchRuleset : Ruleset
+ public class CatchRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits";
@@ -51,7 +60,9 @@ namespace osu.Game.Rulesets.Catch
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new CatchModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -98,10 +109,16 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new CatchModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new CatchModAutoplay(), new ModCinema()),
+ new MultiMod(new CatchModAutoplay(), new CatchModCinema()),
new CatchModRelax(),
};
@@ -112,7 +129,7 @@ namespace osu.Game.Rulesets.Catch
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -120,19 +137,18 @@ namespace osu.Game.Rulesets.Catch
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Catching fruit";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new CatchLegacySkinTransformer(source);
+
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 2;
+ public int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
-
- public CatchRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index 7e482d4045..02c045f363 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -5,5 +5,11 @@ namespace osu.Game.Rulesets.Catch
{
public enum CatchSkinComponents
{
+ FruitBananas,
+ FruitApple,
+ FruitGrapes,
+ FruitOrange,
+ FruitPear,
+ Droplet
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
index 374dd50c11..fc030877f1 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 0;
case HitResult.Perfect:
- return 0.008;
+ return 0.01;
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
index f1399bb5c0..e87ecba749 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs
@@ -18,17 +18,5 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 30;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return base.HealthIncreaseFor(result);
-
- case HitResult.Perfect:
- return 0.007;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
index 8fd9ac92ba..2149ed9712 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs
@@ -23,18 +23,6 @@ namespace osu.Game.Rulesets.Catch.Judgements
}
}
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- default:
- return -0.02;
-
- case HitResult.Perfect:
- return 0.01;
- }
- }
-
///
/// Whether fruit on the platter should explode or drop.
/// Note that this is only checked if the owning object is also
diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
index 3829b5e94f..d607b49ea4 100644
--- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
+++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
return 0;
case HitResult.Perfect:
- return 0.004;
+ return 0.02;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
index c721ff862a..46e427e1b7 100644
--- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs
@@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
{
private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
- private const uint y = 842502087;
- private const uint z = 3579807591;
- private const uint w = 273326509;
- private uint _x, _y = y, _z = z, _w = w;
+ private const uint y_initial = 842502087;
+ private const uint z_initial = 3579807591;
+ private const uint w_initial = 273326509;
+ private uint x, y = y_initial, z = z_initial, w = w_initial;
public FastRandom(int seed)
{
- _x = (uint)seed;
+ x = (uint)seed;
}
public FastRandom()
@@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// The random value.
public uint NextUInt()
{
- uint t = _x ^ (_x << 11);
- _x = _y;
- _y = _z;
- _z = _w;
- return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8);
+ uint t = x ^ (x << 11);
+ x = y;
+ y = z;
+ z = w;
+ return w = w ^ (w >> 19) ^ t ^ (t >> 8);
}
///
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
new file mode 100644
index 0000000000..3bc1ee5bf5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
+ Replay = new CatchAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
new file mode 100644
index 0000000000..e2465d727e
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, difficulty.ApproachRate);
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index 606a935229..ee88edbea1 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
@@ -3,7 +3,7 @@
using System.Linq;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
index da2edcee44..c07087efaf 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
- public class CatchModNightcore : ModNightcore
+ public class CatchModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.06;
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
index a47efcc10a..4c72b9fd3e 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs
@@ -36,7 +36,10 @@ namespace osu.Game.Rulesets.Catch.Mods
//disable keyboard controls
public bool OnPressed(CatchAction action) => true;
- public bool OnReleased(CatchAction action) => true;
+
+ public void OnReleased(CatchAction action)
+ {
+ }
protected override bool OnMouseMove(MouseMoveEvent e)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 267e6d12c7..c3488aec11 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
@@ -11,6 +12,8 @@ namespace osu.Game.Rulesets.Catch.Objects
public override bool LastInCombo => true;
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
+
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
@@ -36,7 +39,11 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index e4ad49ea50..f3b566f340 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
{
- public const double OBJECT_RADIUS = 44;
+ public const float OBJECT_RADIUS = 64;
private float x;
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public int IndexInBeatmap { get; set; }
- public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(ComboIndex % 4);
+ public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; }
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Catch.Objects
TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
- Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
+ Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
@@ -100,8 +100,8 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Pear,
Grape,
- Raspberry,
Pineapple,
+ Raspberry,
Banana // banananananannaanana
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
deleted file mode 100644
index 5afdb14888..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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.Objects.Drawable
-{
- public class DrawableBanana : DrawableFruit
- {
- public DrawableBanana(Banana h)
- : base(h)
- {
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
deleted file mode 100644
index 059310d671..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
-using osuTK;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
-{
- public class DrawableDroplet : PalpableCatchHitObject
- {
- private Pulp pulp;
-
- public override bool StaysOnPlate => false;
-
- public DrawableDroplet(Droplet h)
- : base(h)
- {
- Origin = Anchor.Centre;
- Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4;
- Masking = false;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- AddInternal(pulp = new Pulp { Size = Size });
-
- 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
deleted file mode 100644
index 958cd19d50..0000000000
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ /dev/null
@@ -1,316 +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 osu.Framework.Allocation;
-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.Catch.Objects.Drawable.Pieces;
-using osuTK;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
-{
- public class DrawableFruit : PalpableCatchHitObject
- {
- private Circle border;
-
- private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust;
-
- ///
- /// Because we're adding a border around the fruit, we need to scale down some.
- ///
- private const float radius_adjust = 1.1f;
-
- public DrawableFruit(Fruit h)
- : base(h)
- {
- Origin = Anchor.Centre;
-
- Size = new Vector2(drawable_radius);
- Masking = false;
-
- Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- // todo: this should come from the skin.
- AccentColour.Value = colourForRepresentation(HitObject.VisualRepresentation);
-
- AddRangeInternal(new[]
- {
- createPulp(HitObject.VisualRepresentation),
- border = new Circle
- {
- EdgeEffect = new EdgeEffectParameters
- {
- Hollow = !HitObject.HyperDash,
- Type = EdgeEffectType.Glow,
- Radius = 4 * radius_adjust,
- Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Value.Darken(1).Opacity(0.6f)
- },
- Size = new Vector2(Height),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- BorderColour = Color4.White,
- BorderThickness = 3f * radius_adjust,
- Children = new Framework.Graphics.Drawable[]
- {
- new Box
- {
- AlwaysPresent = true,
- Colour = AccentColour.Value,
- Alpha = 0,
- RelativeSizeAxes = Axes.Both
- }
- }
- },
- });
-
- if (HitObject.HyperDash)
- {
- AddInternal(new Pulp
- {
- RelativePositionAxes = Axes.Both,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- AccentColour = Color4.Red,
- Blending = BlendingParameters.Additive,
- Alpha = 0.5f,
- Scale = new Vector2(1.333f)
- });
- }
- }
-
- private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
- {
- const float large_pulp_3 = 8f * radius_adjust;
- const float distance_from_centre_3 = 0.15f;
-
- const float large_pulp_4 = large_pulp_3 * 0.925f;
- const float distance_from_centre_4 = distance_from_centre_3 / 0.925f;
-
- const float small_pulp = large_pulp_3 / 2;
-
- static Vector2 positionAt(float angle, float distance) => new Vector2(
- distance * MathF.Sin(angle * MathF.PI / 180),
- distance * MathF.Cos(angle * MathF.PI / 180));
-
- switch (representation)
- {
- default:
- return new Container();
-
- case FruitVisualRepresentation.Raspberry:
- return new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Framework.Graphics.Drawable[]
- {
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(small_pulp),
- Y = -0.34f,
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_4),
- Position = positionAt(0, distance_from_centre_4),
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_4),
- Position = positionAt(90, distance_from_centre_4),
- },
- new Pulp
- {
- 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.Value,
- Position = positionAt(270, distance_from_centre_4),
- },
- }
- };
-
- case FruitVisualRepresentation.Pineapple:
- return new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Framework.Graphics.Drawable[]
- {
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(small_pulp),
- Y = -0.3f,
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_4),
- Position = positionAt(45, distance_from_centre_4),
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_4),
- Position = positionAt(135, distance_from_centre_4),
- },
- new Pulp
- {
- 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.Value,
- Position = positionAt(315, distance_from_centre_4),
- },
- }
- };
-
- case FruitVisualRepresentation.Pear:
- return new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Framework.Graphics.Drawable[]
- {
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(small_pulp),
- Y = -0.33f,
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_3),
- Position = positionAt(60, distance_from_centre_3),
- },
- new Pulp
- {
- 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.Value,
- Position = positionAt(300, distance_from_centre_3),
- },
- }
- };
-
- case FruitVisualRepresentation.Grape:
- return new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Framework.Graphics.Drawable[]
- {
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(small_pulp),
- Y = -0.25f,
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_3),
- Position = positionAt(0, distance_from_centre_3),
- },
- new Pulp
- {
- 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.Value,
- Position = positionAt(240, distance_from_centre_3),
- },
- }
- };
-
- case FruitVisualRepresentation.Banana:
- return new Container
- {
- RelativeSizeAxes = Axes.Both,
- Children = new Framework.Graphics.Drawable[]
- {
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(small_pulp),
- Y = -0.3f
- },
- new Pulp
- {
- AccentColour = AccentColour.Value,
- Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
- Y = 0.05f,
- },
- }
- };
- }
- }
-
- protected override void Update()
- {
- base.Update();
-
- border.Alpha = (float)Math.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
- }
-
- private Color4 colourForRepresentation(FruitVisualRepresentation representation)
- {
- switch (representation)
- {
- default:
- case FruitVisualRepresentation.Pear:
- return new Color4(17, 136, 170, 255);
-
- case FruitVisualRepresentation.Grape:
- return new Color4(204, 102, 0, 255);
-
- case FruitVisualRepresentation.Raspberry:
- return new Color4(121, 9, 13, 255);
-
- case FruitVisualRepresentation.Pineapple:
- return new Color4(102, 136, 0, 255);
-
- case FruitVisualRepresentation.Banana:
- switch (RNG.Next(0, 3))
- {
- default:
- return new Color4(255, 240, 0, 255);
-
- case 1:
- return new Color4(255, 192, 0, 255);
-
- case 2:
- return new Color4(214, 221, 28, 255);
- }
- }
- }
- }
-}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs
new file mode 100644
index 0000000000..ebb0bf0f2c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/BananaPiece.cs
@@ -0,0 +1,31 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class BananaPiece : PulpFormation
+ {
+ public BananaPiece()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(SMALL_PULP),
+ Y = -0.3f
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f),
+ Y = 0.05f,
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.cs
new file mode 100644
index 0000000000..cf7231ebb2
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBanana.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.Collections.Generic;
+using osu.Framework.Utils;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class DrawableBanana : DrawableFruit
+ {
+ public DrawableBanana(Banana h)
+ : base(h)
+ {
+ }
+
+ private Color4? colour;
+
+ protected override Color4 GetComboColour(IReadOnlyList comboColours)
+ {
+ // override any external colour changes with banananana
+ return colour ??= getBananaColour();
+ }
+
+ private Color4 getBananaColour()
+ {
+ switch (RNG.Next(0, 3))
+ {
+ default:
+ return new Color4(255, 240, 0, 255);
+
+ case 1:
+ return new Color4(255, 192, 0, 255);
+
+ case 2:
+ return new Color4(214, 221, 28, 255);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
similarity index 97%
rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
index ea415e18fa..4ce80aceb8 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableBananaShower.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableBananaShower : DrawableCatchHitObject
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
similarity index 70%
rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
index b7c05392f3..5bfe0515a1 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs
@@ -2,24 +2,51 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using osuTK;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public abstract class PalpableCatchHitObject : DrawableCatchHitObject
where TObject : CatchHitObject
{
public override bool CanBePlated => true;
+ protected Container ScaleContainer { get; private set; }
+
protected PalpableCatchHitObject(TObject hitObject)
: base(hitObject)
{
- Scale = new Vector2(HitObject.Scale);
+ Origin = Anchor.Centre;
+ Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
+ Masking = false;
}
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ ScaleContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ }
+ });
+
+ ScaleContainer.Scale = new Vector2(HitObject.Scale);
+ }
+
+ protected override Color4 GetComboColour(IReadOnlyList comboColours) =>
+ comboColours[(HitObject.IndexInBeatmap + 1) % comboColours.Count];
}
public abstract class DrawableCatchHitObject : DrawableCatchHitObject
@@ -41,6 +68,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
public virtual bool StaysOnPlate => CanBePlated;
+ public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
+
protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject)
{
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
new file mode 100644
index 0000000000..0a8e830af9
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableDroplet.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class DrawableDroplet : PalpableCatchHitObject
+ {
+ public override bool StaysOnPlate => false;
+
+ public DrawableDroplet(Droplet h)
+ : base(h)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScaleContainer.Child = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.Droplet), _ => new Pulp
+ {
+ Size = Size / 4,
+ AccentColour = { BindTarget = AccentColour }
+ });
+ }
+
+ protected override void UpdateInitialTransforms()
+ {
+ base.UpdateInitialTransforms();
+
+ // roughly matches osu-stable
+ float startRotation = RNG.NextSingle() * 20;
+ double duration = HitObject.TimePreempt + 2000;
+
+ this.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
new file mode 100644
index 0000000000..197ad41247
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableFruit.cs
@@ -0,0 +1,50 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Utils;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class DrawableFruit : PalpableCatchHitObject
+ {
+ public DrawableFruit(Fruit h)
+ : base(h)
+ {
+ Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScaleContainer.Child = new SkinnableDrawable(
+ new CatchSkinComponent(getComponent(HitObject.VisualRepresentation)), _ => new FruitPiece());
+ }
+
+ private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
+ {
+ switch (hitObjectVisualRepresentation)
+ {
+ case FruitVisualRepresentation.Pear:
+ return CatchSkinComponents.FruitPear;
+
+ case FruitVisualRepresentation.Grape:
+ return CatchSkinComponents.FruitGrapes;
+
+ case FruitVisualRepresentation.Pineapple:
+ return CatchSkinComponents.FruitApple;
+
+ case FruitVisualRepresentation.Raspberry:
+ return CatchSkinComponents.FruitOrange;
+
+ case FruitVisualRepresentation.Banana:
+ return CatchSkinComponents.FruitBananas;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(hitObjectVisualRepresentation), hitObjectVisualRepresentation, null);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
similarity index 87%
rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
index a24821b3ce..932464cfd1 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableJuiceStream.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableJuiceStream : DrawableCatchHitObject
{
@@ -42,10 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
switch (hitObject)
{
case CatchHitObject catchObject:
- return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
+ return createDrawableRepresentation?.Invoke(catchObject)?.With(o =>
+ ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
}
- return base.CreateNestedHitObject(hitObject);
+ throw new ArgumentException($"{nameof(hitObject)} must be of type {nameof(CatchHitObject)}.");
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
similarity index 60%
rename from osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
rename to osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
index d41aea1e7b..ae775684d8 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableTinyDroplet.cs
@@ -1,16 +1,21 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK;
+using osu.Framework.Allocation;
-namespace osu.Game.Rulesets.Catch.Objects.Drawable
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableTinyDroplet : DrawableDroplet
{
public DrawableTinyDroplet(TinyDroplet h)
: base(h)
{
- Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 8;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ ScaleContainer.Scale /= 2;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
new file mode 100644
index 0000000000..5797588ded
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/FruitPiece.cs
@@ -0,0 +1,116 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Rulesets.Objects.Drawables;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ internal class FruitPiece : CompositeDrawable
+ {
+ ///
+ /// Because we're adding a border around the fruit, we need to scale down some.
+ ///
+ public const float RADIUS_ADJUST = 1.1f;
+
+ private Circle border;
+
+ private CatchHitObject hitObject;
+
+ private readonly IBindable accentColour = new Bindable();
+
+ public FruitPiece()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject)
+ {
+ DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
+ hitObject = drawableCatchObject.HitObject;
+
+ accentColour.BindTo(drawableCatchObject.AccentColour);
+
+ AddRangeInternal(new[]
+ {
+ getFruitFor(drawableCatchObject.HitObject.VisualRepresentation),
+ border = new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BorderColour = Color4.White,
+ BorderThickness = 6f * RADIUS_ADJUST,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ }
+ }
+ },
+ });
+
+ if (hitObject.HyperDash)
+ {
+ AddInternal(new Circle
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ BorderColour = Color4.Red,
+ BorderThickness = 12f * RADIUS_ADJUST,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0.3f,
+ Blending = BlendingParameters.Additive,
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Red,
+ }
+ }
+ });
+ }
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+ border.Alpha = (float)Math.Clamp((hitObject.StartTime - Time.Current) / 500, 0, 1);
+ }
+
+ private Drawable getFruitFor(FruitVisualRepresentation representation)
+ {
+ switch (representation)
+ {
+ case FruitVisualRepresentation.Pear:
+ return new PearPiece();
+
+ case FruitVisualRepresentation.Grape:
+ return new GrapePiece();
+
+ case FruitVisualRepresentation.Pineapple:
+ return new PineapplePiece();
+
+ case FruitVisualRepresentation.Banana:
+ return new BananaPiece();
+
+ case FruitVisualRepresentation.Raspberry:
+ return new RaspberryPiece();
+ }
+
+ return Empty();
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs
new file mode 100644
index 0000000000..1d1faf893b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/GrapePiece.cs
@@ -0,0 +1,43 @@
+// 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.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class GrapePiece : PulpFormation
+ {
+ public GrapePiece()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(SMALL_PULP),
+ Y = -0.25f,
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_3),
+ Position = PositionAt(0, DISTANCE_FROM_CENTRE_3),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_3),
+ Position = PositionAt(120, DISTANCE_FROM_CENTRE_3),
+ },
+ new Pulp
+ {
+ Size = new Vector2(LARGE_PULP_3),
+ AccentColour = { BindTarget = AccentColour },
+ Position = PositionAt(240, DISTANCE_FROM_CENTRE_3),
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs
new file mode 100644
index 0000000000..7f14217cda
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PearPiece.cs
@@ -0,0 +1,43 @@
+// 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.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class PearPiece : PulpFormation
+ {
+ public PearPiece()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(SMALL_PULP),
+ Y = -0.33f,
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_3),
+ Position = PositionAt(60, DISTANCE_FROM_CENTRE_3),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_3),
+ Position = PositionAt(180, DISTANCE_FROM_CENTRE_3),
+ },
+ new Pulp
+ {
+ Size = new Vector2(LARGE_PULP_3),
+ AccentColour = { BindTarget = AccentColour },
+ Position = PositionAt(300, DISTANCE_FROM_CENTRE_3),
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
similarity index 62%
rename from osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
rename to osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
index 1e9daf18db..1e7506a257 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/Pieces/Pulp.cs
@@ -1,16 +1,16 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
using osuTK.Graphics;
-namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
+namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
- public class Pulp : Circle, IHasAccentColour
+ public class Pulp : Circle
{
public Pulp()
{
@@ -22,32 +22,23 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
Colour = Color4.White.Opacity(0.9f);
}
- private Color4 accentColour;
+ public readonly Bindable AccentColour = new Bindable();
- public Color4 AccentColour
+ protected override void LoadComplete()
{
- get => accentColour;
- set
- {
- accentColour = value;
- if (IsLoaded) updateAccentColour();
- }
+ base.LoadComplete();
+
+ AccentColour.BindValueChanged(updateAccentColour, true);
}
- private void updateAccentColour()
+ private void updateAccentColour(ValueChangedEvent colour)
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = Size.X / 2,
- Colour = accentColour.Darken(0.2f).Opacity(0.75f)
+ Colour = colour.NewValue.Darken(0.2f).Opacity(0.75f)
};
}
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateAccentColour();
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs
new file mode 100644
index 0000000000..c328ba1837
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PineapplePiece.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class PineapplePiece : PulpFormation
+ {
+ public PineapplePiece()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(SMALL_PULP),
+ Y = -0.3f,
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(45, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(135, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(225, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ Size = new Vector2(LARGE_PULP_4),
+ AccentColour = { BindTarget = AccentColour },
+ Position = PositionAt(315, DISTANCE_FROM_CENTRE_4),
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs
new file mode 100644
index 0000000000..be70c3400c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/PulpFormation.cs
@@ -0,0 +1,43 @@
+// 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.Game.Rulesets.Objects.Drawables;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public abstract class PulpFormation : CompositeDrawable
+ {
+ protected readonly IBindable AccentColour = new Bindable();
+
+ protected const float LARGE_PULP_3 = 16f * FruitPiece.RADIUS_ADJUST;
+ protected const float DISTANCE_FROM_CENTRE_3 = 0.15f;
+
+ protected const float LARGE_PULP_4 = LARGE_PULP_3 * 0.925f;
+ protected const float DISTANCE_FROM_CENTRE_4 = DISTANCE_FROM_CENTRE_3 / 0.925f;
+
+ protected const float SMALL_PULP = LARGE_PULP_3 / 2;
+
+ protected PulpFormation()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ protected static Vector2 PositionAt(float angle, float distance) => new Vector2(
+ distance * MathF.Sin(angle * MathF.PI / 180),
+ distance * MathF.Cos(angle * MathF.PI / 180));
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject)
+ {
+ DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
+ AccentColour.BindTo(drawableCatchObject.AccentColour);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs
new file mode 100644
index 0000000000..22ce3ba5b3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Objects/Drawables/RaspberryPiece.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Objects.Drawables
+{
+ public class RaspberryPiece : PulpFormation
+ {
+ public RaspberryPiece()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(SMALL_PULP),
+ Y = -0.34f,
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(0, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(90, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ AccentColour = { BindTarget = AccentColour },
+ Size = new Vector2(LARGE_PULP_4),
+ Position = PositionAt(180, DISTANCE_FROM_CENTRE_4),
+ },
+ new Pulp
+ {
+ Size = new Vector2(LARGE_PULP_4),
+ AccentColour = { BindTarget = AccentColour },
+ Position = PositionAt(270, DISTANCE_FROM_CENTRE_4),
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index 33780427b6..642ff0246e 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -7,6 +7,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -19,6 +20,8 @@ namespace osu.Game.Rulesets.Catch.Objects
///
private const float base_scoring_distance = 100;
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
+
public int RepeatCount { get; set; }
public double Velocity;
@@ -75,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects
Samples = tickSamples,
StartTime = t + lastEvent.Value.Time,
X = X + Path.PositionAt(
- lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
+ lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
});
}
}
@@ -110,13 +113,33 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
- public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ public double EndTime
+ {
+ get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
+ }
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime;
- public SliderPath Path { get; set; }
+ private readonly SliderPath path = new SliderPath();
+
+ public SliderPath Path
+ {
+ get => path;
+ set
+ {
+ path.ControlPoints.Clear();
+ path.ExpectedDistance.Value = null;
+
+ if (value != null)
+ {
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
+ path.ExpectedDistance.Value = value.ExpectedDistance.Value;
+ }
+ }
+ }
public double Distance => Path.Distance;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index 6c8515eb90..4649dcae90 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -3,7 +3,7 @@
using System;
using System.Linq;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Beatmaps;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 22532bc9ec..f122588a2b 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Input.StateChanges;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
index 18785d65ea..4c7bc4ab73 100644
--- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
@@ -1,42 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring
{
- public class CatchScoreProcessor : ScoreProcessor
+ public class CatchScoreProcessor : ScoreProcessor
{
- public CatchScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- {
- switch (result.Type)
- {
- case HitResult.Miss:
- return hpDrainRate;
-
- default:
- return 10.2 - hpDrainRate; // Award less HP as drain rate is increased
- }
- }
-
public override HitWindows CreateHitWindows() => new CatchHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
new file mode 100644
index 0000000000..36164c5543
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -0,0 +1,58 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Humanizer;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ public class CatchLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ public CatchLegacySkinTransformer(ISkinSource source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ if (!(component is CatchSkinComponent catchSkinComponent))
+ return null;
+
+ switch (catchSkinComponent.Component)
+ {
+ case CatchSkinComponents.FruitApple:
+ case CatchSkinComponents.FruitBananas:
+ case CatchSkinComponents.FruitOrange:
+ case CatchSkinComponents.FruitGrapes:
+ case CatchSkinComponents.FruitPear:
+ var lookupName = catchSkinComponent.Component.ToString().Kebaberize();
+ if (GetTexture(lookupName) != null)
+ return new LegacyFruitPiece(lookupName);
+
+ break;
+
+ case CatchSkinComponents.Droplet:
+ if (GetTexture("fruit-drop") != null)
+ return new LegacyFruitPiece("fruit-drop") { Scale = new Vector2(0.8f) };
+
+ break;
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
new file mode 100644
index 0000000000..25ee0811d0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
@@ -0,0 +1,79 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Skinning;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning
+{
+ internal class LegacyFruitPiece : CompositeDrawable
+ {
+ private readonly string lookupName;
+
+ private readonly IBindable accentColour = new Bindable();
+ private Sprite colouredSprite;
+
+ public LegacyFruitPiece(string lookupName)
+ {
+ this.lookupName = lookupName;
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(DrawableHitObject drawableObject, ISkinSource skin)
+ {
+ DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
+
+ accentColour.BindTo(drawableCatchObject.AccentColour);
+
+ InternalChildren = new Drawable[]
+ {
+ colouredSprite = new Sprite
+ {
+ Texture = skin.GetTexture(lookupName),
+ Colour = drawableObject.AccentColour.Value,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new Sprite
+ {
+ Texture = skin.GetTexture($"{lookupName}-overlay"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ };
+
+ if (drawableCatchObject.HitObject.HyperDash)
+ {
+ var hyperDash = new Sprite
+ {
+ Texture = skin.GetTexture(lookupName),
+ Colour = Color4.Red,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Blending = BlendingParameters.Additive,
+ Depth = 1,
+ Alpha = 0.7f,
+ Scale = new Vector2(1.2f)
+ };
+
+ AddInternal(hyperDash);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 589503c35b..2319c5ac1f 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea;
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || CatcherArea.ReceivePositionalInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
+ // only check the X position; handle all vertical space.
+ base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation)
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 2d6ce02e45..b977d46611 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -4,14 +4,16 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
using osu.Framework.Input.Bindings;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@@ -48,6 +50,9 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
{
+ if (result.Judgement is IgnoreJudgement)
+ return;
+
void runAfterLoaded(Action action)
{
if (lastPlateableFruit == null)
@@ -63,6 +68,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (result.IsHit && fruit.CanBePlated)
{
+ // create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject);
if (caughtFruit == null) return;
@@ -73,11 +79,11 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.Anchor = Anchor.TopCentre;
caughtFruit.Origin = Anchor.Centre;
- caughtFruit.Scale *= 0.7f;
+ caughtFruit.Scale *= 0.5f;
caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
caughtFruit.LifetimeEnd = double.MaxValue;
- MovableCatcher.Add(caughtFruit);
+ MovableCatcher.PlaceOnPlate(caughtFruit);
lastPlateableFruit = caughtFruit;
if (!fruit.StaysOnPlate)
@@ -86,7 +92,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (fruit.HitObject.LastInCombo)
{
- if (((CatchJudgement)result.Judgement).ShouldExplodeFor(result))
+ if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
runAfterLoaded(() => MovableCatcher.Explode());
else
MovableCatcher.Drop();
@@ -103,7 +109,9 @@ namespace osu.Game.Rulesets.Catch.UI
MovableCatcher.X = state.CatcherX.Value;
}
- public bool OnReleased(CatchAction action) => false;
+ public void OnReleased(CatchAction action)
+ {
+ }
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
@@ -131,7 +139,6 @@ namespace osu.Game.Rulesets.Catch.UI
X = 0.5f;
Origin = Anchor.TopCentre;
- Anchor = Anchor.TopLeft;
Size = new Vector2(CATCHER_SIZE);
if (difficulty != null)
@@ -219,9 +226,9 @@ namespace osu.Game.Rulesets.Catch.UI
/// Add a caught fruit to the catcher's stack.
///
/// The fruit that was caught.
- public void Add(DrawableHitObject fruit)
+ public void PlaceOnPlate(DrawableCatchHitObject fruit)
{
- float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X;
+ float ourRadius = fruit.DisplayRadius;
float theirRadius = 0;
const float allowance = 6;
@@ -238,6 +245,12 @@ namespace osu.Game.Rulesets.Catch.UI
fruit.X = Math.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
caughtFruit.Add(fruit);
+
+ Add(new HitExplosion(fruit)
+ {
+ X = fruit.X,
+ Scale = new Vector2(fruit.HitObject.Scale)
+ });
}
///
@@ -341,24 +354,22 @@ namespace osu.Game.Rulesets.Catch.UI
return false;
}
- public bool OnReleased(CatchAction action)
+ public void OnReleased(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection++;
- return true;
+ break;
case CatchAction.MoveRight:
currentDirection--;
- return true;
+ break;
case CatchAction.Dash:
Dashing = false;
- return true;
+ break;
}
-
- return false;
}
///
@@ -388,32 +399,24 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
+ public void UpdatePosition(float position)
+ {
+ position = Math.Clamp(position, 0, 1);
+
+ if (position == X)
+ return;
+
+ Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
+ X = position;
+ }
+
///
/// Drop any fruit off the plate.
///
public void Drop()
{
- var fruit = caughtFruit.ToArray();
-
- foreach (var f in fruit)
- {
- if (ExplodingFruitTarget != null)
- {
- f.Anchor = Anchor.TopLeft;
- f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
-
- caughtFruit.Remove(f);
-
- ExplodingFruitTarget.Add(f);
- }
-
- 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();
- }
+ foreach (var f in caughtFruit.ToArray())
+ Drop(f);
}
///
@@ -425,10 +428,26 @@ namespace osu.Game.Rulesets.Catch.UI
Explode(f);
}
+ public void Drop(DrawableHitObject fruit) => removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y + 75, 750, Easing.InSine);
+ f.FadeOut(750);
+ });
+
public void Explode(DrawableHitObject fruit)
{
var originalX = fruit.X * Scale.X;
+ removeFromPlateWithTransform(fruit, f =>
+ {
+ f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
+ f.MoveToX(f.X + originalX * 6, 1000);
+ f.FadeOut(750);
+ });
+ }
+
+ private void removeFromPlateWithTransform(DrawableHitObject fruit, Action action)
+ {
if (ExplodingFruitTarget != null)
{
fruit.Anchor = Anchor.TopLeft;
@@ -442,26 +461,127 @@ 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);
+ double actionTime = Clock.CurrentTime;
- // todo: this shouldn't exist once DrawableHitObject's ClearTransformsAfter overrides are repaired.
- fruit.LifetimeStart = Time.Current;
- fruit.Expire();
- }
+ fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
+ onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
- public void UpdatePosition(float position)
- {
- position = Math.Clamp(position, 0, 1);
+ void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
+ {
+ using (fruit.BeginAbsoluteSequence(actionTime))
+ action(fruit);
- if (position == X)
- return;
-
- Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
- X = position;
+ fruit.Expire();
+ }
}
}
}
+
+ public class HitExplosion : CompositeDrawable
+ {
+ private readonly CircularContainer largeFaint;
+
+ public HitExplosion(DrawableCatchHitObject fruit)
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.BottomCentre;
+
+ Color4 objectColour = fruit.AccentColour.Value;
+
+ // scale roughly in-line with visual appearance of notes
+
+ const float angle_variangle = 15; // should be less than 45
+
+ const float roundness = 100;
+
+ const float initial_height = 10;
+
+ var colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1);
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ 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.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,
+ },
+ },
+ 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()
+ {
+ base.LoadComplete();
+
+ const double duration = 400;
+
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ Expire(true);
+ }
+ }
}
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
index e3c6c93d01..025fa9c56e 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle")
+ InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 6b7f00c5d0..fd8a1d175d 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -8,12 +8,10 @@ using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.Objects.Drawable;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
-using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
@@ -25,15 +23,13 @@ namespace osu.Game.Rulesets.Catch.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableCatchRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableCatchRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
- public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
-
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
index 0362402320..c381ea585d 100644
--- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Mania.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
index 779eb4f277..0811c2724c 100644
--- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index 12865385b6..d0ff1fab43 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
new file mode 100644
index 0000000000..692d079c16
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Game.Tests.Visual;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneDrawableJudgement : SkinnableTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableJudgement),
+ typeof(DrawableManiaJudgement)
+ };
+
+ public TestSceneDrawableJudgement()
+ {
+ foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1))
+ {
+ AddStep("Show " + result.GetDescription(), () => SetContents(() =>
+ new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }));
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
new file mode 100644
index 0000000000..7b0cf40d45
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
@@ -0,0 +1,314 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
+ {
+ private const double time_before_head = 250;
+ private const double time_head = 1500;
+ private const double time_during_hold_1 = 2500;
+ private const double time_tail = 4000;
+ private const double time_after_tail = 5250;
+
+ private List judgementResults;
+ private bool allJudgedFired;
+
+ ///
+ /// -----[ ]-----
+ /// o o
+ ///
+ [Test]
+ public void TestNoInput()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ assertNoteJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressTooEarlyAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_before_head + 10),
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Perfect);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAtStartAndBreak()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo x o o
+ ///
+ [Test]
+ public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_head, ManiaAction.Key1),
+ new ManiaReplayFrame(time_head + 10),
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Perfect);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAfterTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_after_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Miss);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// x o o
+ ///
+ [Test]
+ public void TestPressDuringNoteAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Perfect);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ ///
+ /// -----[ ]-----
+ /// xo o
+ ///
+ [Test]
+ public void TestPressAndReleaseAtTail()
+ {
+ performTest(new List
+ {
+ new ManiaReplayFrame(time_tail, ManiaAction.Key1),
+ new ManiaReplayFrame(time_tail + 10),
+ });
+
+ assertHeadJudgement(HitResult.Miss);
+ assertTickJudgement(HitResult.Miss);
+ assertTailJudgement(HitResult.Meh);
+ }
+
+ private void assertHeadJudgement(HitResult result)
+ => AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
+
+ private void assertTailJudgement(HitResult result)
+ => AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result);
+
+ private void assertNoteJudgement(HitResult result)
+ => AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result);
+
+ private void assertTickJudgement(HitResult result)
+ => AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick
+
+ private ScoreAccessibleReplayPlayer currentPlayer;
+
+ private void performTest(List frames)
+ {
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new Beatmap
+ {
+ HitObjects =
+ {
+ new HoldNote
+ {
+ StartTime = time_head,
+ Duration = time_tail - time_head,
+ Column = 0,
+ }
+ },
+ BeatmapInfo =
+ {
+ BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ });
+
+ Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ p.ScoreProcessor.AllJudged += () =>
+ {
+ if (currentPlayer == p) allJudgedFired = true;
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ allJudgedFired = false;
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for all judged", () => allJudgedFired);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
new file mode 100644
index 0000000000..cd25d162d0
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestScenePlayer : PlayerTestScene
+ {
+ public TestScenePlayer()
+ : base(new ManiaRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index 8fc4dbfe72..6855b99f28 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
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9069c09ae4..d904474815 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -5,7 +5,7 @@ using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Linq;
using System.Collections.Generic;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private const int max_notes_for_density = 7;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
-
public int TargetColumns;
public bool Dual;
public readonly bool IsForCurrentRuleset;
@@ -37,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private ManiaBeatmap beatmap;
- public ManiaBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public ManiaBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
+ IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
@@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
}
}
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject);
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
@@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime);
- density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
+ density = (prevNoteTimes[^1] - prevNoteTimes[0]) / prevNoteTimes.Count;
}
private double lastTime;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 9565ac8994..315ef96e49 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
index 059cd39641..4f7ab87fad 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs
@@ -5,7 +5,7 @@ using System.Linq;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
try
{
diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
index ed25173d38..bbbb93fd8b 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs
@@ -4,7 +4,7 @@
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mania.Difficulty.Preprocessing;
-using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
{
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
protected override double StrainValueOf(DifficultyHitObject current)
{
var maniaCurrent = (ManiaDifficultyHitObject)current;
- var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime;
+ var endTime = maniaCurrent.BaseObject.GetEndTime();
double holdFactor = 1.0; // Factor in case something else is held
double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
index acce41db6f..4e73883de0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteNoteSelectionBlueprint.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
if (DrawableObject.IsLoaded)
{
- DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail;
+ DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
Anchor = note.Anchor;
Origin = note.Origin;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
index bcbc1ee527..7bbde400ea 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.UpdatePosition(screenSpacePosition);
- if (PlacementBegun)
+ if (PlacementActive)
{
var endTime = TimeAt(screenSpacePosition);
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
index b28d8bb0e6..a3657d3bb9 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs
@@ -54,15 +54,15 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return true;
}
- protected override bool OnMouseUp(MouseUpEvent e)
+ protected override void OnMouseUp(MouseUpEvent e)
{
- EndPlacement();
- return base.OnMouseUp(e);
+ EndPlacement(true);
+ base.OnMouseUp(e);
}
public override void UpdatePosition(Vector2 screenSpacePosition)
{
- if (!PlacementBegun)
+ if (!PlacementActive)
Column = ColumnAt(screenSpacePosition);
if (Column == null) return;
diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
index 3bd7fb2d49..9f57160f99 100644
--- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs
@@ -13,7 +13,7 @@ using osuTK;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
- public class ManiaSelectionBlueprint : SelectionBlueprint
+ public class ManiaSelectionBlueprint : OverlaySelectionBlueprint
{
public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; }
@@ -55,14 +55,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return base.OnMouseDown(e);
}
- protected override bool OnDrag(DragEvent e)
+ protected override void OnDrag(DragEvent e)
{
- var result = base.OnDrag(e);
+ base.OnDrag(e);
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
-
- return result;
}
public override void Show()
diff --git a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
index 97d8aaa052..445df79f6f 100644
--- a/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
+++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Edit
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
- public DrawableManiaEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
new file mode 100644
index 0000000000..d744036b4c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Mania.Edit.Blueprints;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Mania.Edit
+{
+ public class ManiaBlueprintContainer : ComposeBlueprintContainer
+ {
+ public ManiaBlueprintContainer(IEnumerable drawableHitObjects)
+ : base(drawableHitObjects)
+ {
+ }
+
+ public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case DrawableNote note:
+ return new NoteSelectionBlueprint(note);
+
+ case DrawableHoldNote holdNote:
+ return new HoldNoteSelectionBlueprint(holdNote);
+ }
+
+ return base.CreateBlueprintFor(hitObject);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
index 0bfe6f9517..62b609610f 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
@@ -5,11 +5,8 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic;
using osu.Framework.Allocation;
-using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -42,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Edit
public int TotalColumns => ((ManiaPlayfield)drawableRuleset.Playfield).TotalColumns;
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
{
drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods);
@@ -52,26 +49,12 @@ namespace osu.Game.Rulesets.Mania.Edit
return drawableRuleset;
}
+ protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects);
+
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
{
new NoteCompositionTool(),
new HoldNoteCompositionTool()
};
-
- public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler();
-
- public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
- {
- switch (hitObject)
- {
- case DrawableNote note:
- return new NoteSelectionBlueprint(note);
-
- case DrawableHoldNote holdNote:
- return new HoldNoteSelectionBlueprint(holdNote);
- }
-
- return base.CreateBlueprintFor(hitObject);
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
index 618af3e772..9069a636a8 100644
--- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
+++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using osu.Framework.Allocation;
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;
@@ -70,10 +71,12 @@ namespace osu.Game.Rulesets.Mania.Edit
// 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;
+ delta -= moveEvent.Blueprint.Parent.DrawHeight; // todo: probably wrong
- foreach (var b in SelectedBlueprints)
+ foreach (var selectionBlueprint in SelectedBlueprints)
{
+ var b = (OverlaySelectionBlueprint)selectionBlueprint;
+
var hitObject = b.DrawableObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
index ff8882124f..433db79ae0 100644
--- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Masks
{
- public abstract class ManiaSelectionBlueprint : SelectionBlueprint
+ public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint
{
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
deleted file mode 100644
index e8b48768a1..0000000000
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs
+++ /dev/null
@@ -1,14 +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.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Mania.Judgements
-{
- public class HoldNoteJudgement : ManiaJudgement
- {
- public override bool AffectsCombo => false;
-
- protected override int NumericResultFor(HitResult result) => 0;
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
index b9c6e3a7f7..00b839f8ec 100644
--- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs
@@ -15,11 +15,11 @@ namespace osu.Game.Rulesets.Mania.Judgements
{
switch (result)
{
- case HitResult.Miss:
+ default:
return 0;
- default:
- return 0.040;
+ case HitResult.Perfect:
+ return 0.01;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
index 0e4c811945..c2f8fb8678 100644
--- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs
@@ -29,32 +29,5 @@ namespace osu.Game.Rulesets.Mania.Judgements
return 300;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- case HitResult.Miss:
- return -0.125;
-
- case HitResult.Meh:
- return 0.005;
-
- case HitResult.Ok:
- return 0.010;
-
- case HitResult.Good:
- return 0.035;
-
- case HitResult.Great:
- return 0.055;
-
- case HitResult.Perfect:
- return 0.065;
-
- default:
- return 0;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index c74a292331..b7b523a94d 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -25,20 +25,30 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit;
+using osu.Game.Rulesets.Mania.Scoring;
+using osu.Game.Rulesets.Mania.Skinning;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania
{
- public class ManiaRuleset : Ruleset
+ public class ManiaRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
+
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
public const string SHORT_NAME = "mania";
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
+
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
{
if (mods.HasFlag(LegacyMods.Nightcore))
@@ -51,7 +61,9 @@ namespace osu.Game.Rulesets.Mania
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new ManiaModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new ManiaModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new ManiaModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -143,12 +155,13 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
+ new ManiaModDifficultyAdjust(),
};
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new ManiaModAutoplay(), new ModCinema()),
+ new MultiMod(new ManiaModAutoplay(), new ManiaModCinema()),
};
case ModType.Fun:
@@ -158,7 +171,7 @@ namespace osu.Game.Rulesets.Mania
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -166,11 +179,13 @@ namespace osu.Game.Rulesets.Mania
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Smashing keys";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
- public override int? LegacyID => 3;
+ public int LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
@@ -178,11 +193,6 @@ namespace osu.Game.Rulesets.Mania
public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
- public ManiaRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
-
public override IEnumerable AvailableVariants
{
get
@@ -227,19 +237,19 @@ namespace osu.Game.Rulesets.Mania
{
LeftKeys = new[]
{
- InputKey.Number1,
- InputKey.Number2,
- InputKey.Number3,
- InputKey.Number4,
+ InputKey.Q,
+ InputKey.W,
+ InputKey.E,
+ InputKey.R,
},
RightKeys = new[]
{
- InputKey.Z,
InputKey.X,
InputKey.C,
- InputKey.V
+ InputKey.V,
+ InputKey.B
},
- SpecialKey = InputKey.Tilde,
+ SpecialKey = InputKey.S,
SpecialAction = ManiaAction.Special1,
NormalActionStart = ManiaAction.Key1
}.GenerateKeyBindingsFor(keys, out var nextNormal);
@@ -255,12 +265,12 @@ namespace osu.Game.Rulesets.Mania
},
RightKeys = new[]
{
- InputKey.O,
- InputKey.P,
- InputKey.BracketLeft,
- InputKey.BracketRight
+ InputKey.K,
+ InputKey.L,
+ InputKey.Semicolon,
+ InputKey.Quote
},
- SpecialKey = InputKey.BackSlash,
+ SpecialKey = InputKey.I,
SpecialAction = ManiaAction.Special2,
NormalActionStart = nextNormal
}.GenerateKeyBindingsFor(keys, out _);
@@ -268,7 +278,7 @@ namespace osu.Game.Rulesets.Mania
return stage1Bindings.Concat(stage2Bindings);
}
- return new KeyBinding[0];
+ return Array.Empty();
}
public override string GetVariantName(int variant)
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
new file mode 100644
index 0000000000..02c1fc1b79
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs
@@ -0,0 +1,22 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
+ Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
similarity index 50%
rename from osu.Game/Online/API/Requests/Responses/APIMod.cs
rename to osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
index b9da4f49ee..0817f8f9fc 100644
--- a/osu.Game/Online/API/Requests/Responses/APIMod.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
@@ -3,12 +3,9 @@
using osu.Game.Rulesets.Mods;
-namespace osu.Game.Online.API.Requests.Responses
+namespace osu.Game.Rulesets.Mania.Mods
{
- public class APIMod : IMod
+ public class ManiaModDifficultyAdjust : ModDifficultyAdjust
{
- public string Acronym { get; set; }
-
- public bool Equals(IMod other) => Acronym == other?.Acronym;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index 39185e6a57..4c125ad6ef 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override string Name => "Fade In";
public override string Acronym => "FI";
- public override IconUsage Icon => OsuIcon.ModHidden;
+ public override IconUsage? Icon => OsuIcon.ModHidden;
public override ModType Type => ModType.DifficultyIncrease;
public override string Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index 2d94fb6af5..4cc712060c 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
- public class ManiaModNightcore : ModNightcore
+ public class ManiaModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1;
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index 9275371a61..14b36fb765 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -4,7 +4,7 @@
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Random";
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
- public override IconUsage Icon => OsuIcon.Dice;
+ public override IconUsage? Icon => OsuIcon.Dice;
public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 1;
diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs
index 0981b028b2..09a746042b 100644
--- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/BarLine.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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Objects
@@ -8,5 +9,7 @@ namespace osu.Game.Rulesets.Mania.Objects
public class BarLine : ManiaHitObject, IBarLine
{
public bool Major { get; set; }
+
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 56bc797c7f..08b5b75f9c 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
}
- protected override void UpdateStateTransforms(ArmedState state)
- {
- }
+ protected override void UpdateStateTransforms(ArmedState state) => this.FadeOut(150);
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 87b9633c80..14a7c5fda3 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@@ -21,11 +20,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
- public DrawableNote Head => headContainer.Child;
- public DrawableNote Tail => tailContainer.Child;
+ public DrawableHoldNoteHead Head => headContainer.Child;
+ public DrawableHoldNoteTail Tail => tailContainer.Child;
- private readonly Container headContainer;
- private readonly Container tailContainer;
+ private readonly Container headContainer;
+ private readonly Container tailContainer;
private readonly Container tickContainer;
private readonly BodyPiece bodyPiece;
@@ -33,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
///
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
///
- private double? holdStartTime;
+ public double? HoldStartTime { get; private set; }
///
/// Whether the hold note has been released too early and shouldn't give full score for the release.
///
- private bool hasBroken;
+ public bool HasBroken { get; private set; }
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
@@ -49,8 +48,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
- headContainer = new Container { RelativeSizeAxes = Axes.Both },
- tailContainer = new Container { RelativeSizeAxes = Axes.Both },
+ headContainer = new Container { RelativeSizeAxes = Axes.Both },
+ tailContainer = new Container { RelativeSizeAxes = Axes.Both },
});
AccentColour.BindValueChanged(colour =>
@@ -65,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
- case DrawableHeadNote head:
+ case DrawableHoldNoteHead head:
headContainer.Child = head;
break;
- case DrawableTailNote tail:
+ case DrawableHoldNoteTail tail:
tailContainer.Child = tail;
break;
@@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject)
{
case TailNote _:
- return new DrawableTailNote(this)
+ return new DrawableHoldNoteTail(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
};
case Note _:
- return new DrawableHeadNote(this)
+ return new DrawableHoldNoteHead(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
@@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick)
{
- HoldStartTime = () => holdStartTime,
+ HoldStartTime = () => HoldStartTime,
AccentColour = { BindTarget = AccentColour }
};
}
@@ -125,12 +124,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
}
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- if (Tail.AllJudged)
- ApplyResult(r => r.Type = HitResult.Perfect);
- }
-
protected override void Update()
{
base.Update();
@@ -146,146 +139,62 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.UpdateStateTransforms(state);
}
- protected void BeginHold()
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
{
- holdStartTime = Time.Current;
- bodyPiece.Hitting = true;
- }
+ if (Tail.AllJudged)
+ ApplyResult(r => r.Type = HitResult.Perfect);
- protected void EndHold()
- {
- holdStartTime = null;
- bodyPiece.Hitting = false;
+ if (Tail.Result.Type == HitResult.Miss)
+ HasBroken = true;
}
public bool OnPressed(ManiaAction action)
{
- // Make sure the action happened within the body of the hold note
- if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
+ if (AllJudged)
return false;
if (action != Action.Value)
return false;
- // The user has pressed during the body of the hold note, after the head note and its hit windows have passed
- // and within the limited range of the above if-statement. This state will be managed by the head note if the
- // user has pressed during the hit windows of the head note.
- BeginHold();
+ beginHoldAt(Time.Current - Head.HitObject.StartTime);
+ Head.UpdateResult();
+
return true;
}
- public bool OnReleased(ManiaAction action)
+ private void beginHoldAt(double timeOffset)
{
- // Make sure that the user started holding the key during the hold note
- if (!holdStartTime.HasValue)
- return false;
+ if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
+ return;
+
+ HoldStartTime = Time.Current;
+ bodyPiece.Hitting = true;
+ }
+
+ public void OnReleased(ManiaAction action)
+ {
+ if (AllJudged)
+ return;
if (action != Action.Value)
- return false;
+ return;
- EndHold();
+ // Make sure a hold was started
+ if (HoldStartTime == null)
+ return;
+
+ Tail.UpdateResult();
+ endHold();
// If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit)
- hasBroken = true;
-
- return true;
+ HasBroken = true;
}
- ///
- /// The head note of a hold.
- ///
- private class DrawableHeadNote : DrawableNote
+ private void endHold()
{
- private readonly DrawableHoldNote holdNote;
-
- public DrawableHeadNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Head)
- {
- this.holdNote = holdNote;
- }
-
- public override bool OnPressed(ManiaAction action)
- {
- if (!base.OnPressed(action))
- return false;
-
- // If the key has been released too early, the user should not receive full score for the release
- if (Result.Type == HitResult.Miss)
- holdNote.hasBroken = true;
-
- // The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
- // The body doesn't handle these early early hits, so we have to explicitly set the holding state here
- holdNote.BeginHold();
-
- return true;
- }
- }
-
- ///
- /// The tail note of a hold.
- ///
- private class DrawableTailNote : DrawableNote
- {
- ///
- /// Lenience of release hit windows. This is to make cases where the hold note release
- /// is timed alongside presses of other hit objects less awkward.
- /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
- ///
- private const double release_window_lenience = 1.5;
-
- private readonly DrawableHoldNote holdNote;
-
- public DrawableTailNote(DrawableHoldNote holdNote)
- : base(holdNote.HitObject.Tail)
- {
- this.holdNote = holdNote;
- }
-
- protected override void CheckForResult(bool userTriggered, double timeOffset)
- {
- Debug.Assert(HitObject.HitWindows != null);
-
- // Factor in the release lenience
- timeOffset /= release_window_lenience;
-
- if (!userTriggered)
- {
- if (!HitObject.HitWindows.CanBeHit(timeOffset))
- ApplyResult(r => r.Type = HitResult.Miss);
-
- return;
- }
-
- var result = HitObject.HitWindows.ResultFor(timeOffset);
- if (result == HitResult.None)
- return;
-
- ApplyResult(r =>
- {
- if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
- result = HitResult.Good;
-
- r.Type = result;
- });
- }
-
- public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
-
- public override bool OnReleased(ManiaAction action)
- {
- // Make sure that the user started holding the key during the hold note
- if (!holdNote.holdStartTime.HasValue)
- return false;
-
- if (action != Action.Value)
- return false;
-
- UpdateResult(true);
-
- // Handled by the hold note, which will set holding = false
- return false;
- }
+ HoldStartTime = null;
+ bodyPiece.Hitting = false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
new file mode 100644
index 0000000000..390c64c5e2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteHead.cs
@@ -0,0 +1,24 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// The head of a .
+ ///
+ public class DrawableHoldNoteHead : DrawableNote
+ {
+ public DrawableHoldNoteHead(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Head)
+ {
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override void OnReleased(ManiaAction action)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
new file mode 100644
index 0000000000..568b07c958
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Mania.Objects.Drawables
+{
+ ///
+ /// The tail of a .
+ ///
+ public class DrawableHoldNoteTail : DrawableNote
+ {
+ ///
+ /// Lenience of release hit windows. This is to make cases where the hold note release
+ /// is timed alongside presses of other hit objects less awkward.
+ /// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
+ ///
+ private const double release_window_lenience = 1.5;
+
+ private readonly DrawableHoldNote holdNote;
+
+ public DrawableHoldNoteTail(DrawableHoldNote holdNote)
+ : base(holdNote.HitObject.Tail)
+ {
+ this.holdNote = holdNote;
+ }
+
+ public void UpdateResult() => base.UpdateResult(true);
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ Debug.Assert(HitObject.HitWindows != null);
+
+ // Factor in the release lenience
+ timeOffset /= release_window_lenience;
+
+ if (!userTriggered)
+ {
+ if (!HitObject.HitWindows.CanBeHit(timeOffset))
+ ApplyResult(r => r.Type = HitResult.Miss);
+
+ return;
+ }
+
+ var result = HitObject.HitWindows.ResultFor(timeOffset);
+ if (result == HitResult.None)
+ return;
+
+ ApplyResult(r =>
+ {
+ // If the head wasn't hit or the hold note was broken, cap the max score to Meh.
+ if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
+ result = HitResult.Meh;
+
+ r.Type = result;
+ });
+ }
+
+ public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
+
+ public override void OnReleased(ManiaAction action)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 8f353ae138..85613d3afb 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -77,6 +77,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return UpdateResult(true);
}
- public virtual bool OnReleased(ManiaAction action) => false;
+ public virtual void OnReleased(ManiaAction action)
+ {
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
deleted file mode 100644
index 1d25a0c966..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-using osuTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class GlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float glow_alpha = 0.7f;
- private const float glow_radius = 5;
-
- public GlowPiece()
- {
- RelativeSizeAxes = Axes.Both;
- Masking = true;
-
- InternalChild = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- };
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateGlow();
- }
-
- private Color4 accentColour;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- updateGlow();
- }
- }
-
- private void updateGlow()
- {
- if (!IsLoaded)
- return;
-
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = AccentColour.Opacity(glow_alpha),
- Radius = glow_radius,
- Hollow = true
- };
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
deleted file mode 100644
index 48c7ea7b7f..0000000000
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osuTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
-{
- public class LaneGlowPiece : CompositeDrawable, IHasAccentColour
- {
- private const float total_height = 100;
- private const float glow_height = 50;
- private const float glow_alpha = 0.4f;
- private const float edge_alpha = 0.3f;
-
- public LaneGlowPiece()
- {
- BypassAutoSizeAxes = Axes.Both;
- RelativeSizeAxes = Axes.X;
- Height = total_height;
-
- InternalChildren = new[]
- {
- new Container
- {
- Name = "Left edge",
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Right edge",
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- Width = 1,
- Children = createGradient(edge_alpha)
- },
- new Container
- {
- Name = "Glow",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Height = glow_height,
- Children = createGradient(glow_alpha)
- }
- };
- }
-
- private Drawable[] createGradient(float alpha) => new Drawable[]
- {
- new Box
- {
- Name = "Top",
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.Transparent, Color4.White.Opacity(alpha))
- },
- new Box
- {
- Name = "Bottom",
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Blending = BlendingParameters.Additive,
- Colour = ColourInfo.GradientVertical(Color4.White.Opacity(alpha), Color4.Transparent)
- }
- };
-
- public Color4 AccentColour
- {
- get => Colour;
- set => Colour = value;
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index bdba813eed..049bf55f90 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
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;
@@ -15,7 +14,11 @@ namespace osu.Game.Rulesets.Mania.Objects
///
public class HoldNote : ManiaHitObject, IHasEndTime
{
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
private double duration;
@@ -99,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Objects
}
}
- public override Judgement CreateJudgement() => new HoldNoteJudgement();
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index 70ba5cd938..877a9ee410 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{
// We don't need to fully convert, just create the converter
- var converter = new ManiaBeatmapConverter(beatmap);
+ var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
index 49894a644c..9b54b48de3 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs
@@ -1,89 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Scoring
{
- internal class ManiaScoreProcessor : ScoreProcessor
+ internal class ManiaScoreProcessor : ScoreProcessor
{
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_min = 0.75;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_mid = 0.85;
-
- ///
- /// The hit HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_max = 1;
-
- ///
- /// The MISS HP multiplier at OD = 0.
- ///
- private const double hp_multiplier_miss_min = 0.5;
-
- ///
- /// The MISS HP multiplier at OD = 5.
- ///
- private const double hp_multiplier_miss_mid = 0.75;
-
- ///
- /// The MISS HP multiplier at OD = 10.
- ///
- private const double hp_multiplier_miss_max = 1;
-
- ///
- /// The MISS HP multiplier. This is multiplied to the miss hp increase.
- ///
- private double hpMissMultiplier = 1;
-
- ///
- /// The HIT HP multiplier. This is multiplied to hit hp increases.
- ///
- private double hpMultiplier = 1;
-
- public ManiaScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
- hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
- }
-
- protected override void SimulateAutoplay(Beatmap beatmap)
- {
- while (true)
- {
- base.SimulateAutoplay(beatmap);
-
- if (!HasFailed)
- break;
-
- hpMultiplier *= 1.01;
- hpMissMultiplier *= 0.98;
-
- Reset(false);
- }
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
public override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
new file mode 100644
index 0000000000..f3739ce7c2
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs
@@ -0,0 +1,67 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class ManiaLegacySkinTransformer : ISkin
+ {
+ private readonly ISkin source;
+
+ public ManiaLegacySkinTransformer(ISkin source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component)
+ {
+ switch (component)
+ {
+ case GameplaySkinComponent resultComponent:
+ return getResult(resultComponent);
+ }
+
+ return null;
+ }
+
+ private Drawable getResult(GameplaySkinComponent resultComponent)
+ {
+ switch (resultComponent.Component)
+ {
+ case HitResult.Miss:
+ return this.GetAnimation("mania-hit0", true, true);
+
+ case HitResult.Meh:
+ return this.GetAnimation("mania-hit50", true, true);
+
+ case HitResult.Ok:
+ return this.GetAnimation("mania-hit100", true, true);
+
+ case HitResult.Good:
+ return this.GetAnimation("mania-hit200", true, true);
+
+ case HitResult.Great:
+ return this.GetAnimation("mania-hit300", true, true);
+
+ case HitResult.Perfect:
+ return this.GetAnimation("mania-hit300g", true, true);
+ }
+
+ return null;
+ }
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
+
+ public IBindable GetConfig(TLookup lookup) =>
+ source.GetConfig(lookup);
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 3d2a070b0f..63c573d344 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -191,7 +191,9 @@ namespace osu.Game.Rulesets.Mania.UI
return true;
}
- public bool OnReleased(ManiaAction action) => false;
+ public void OnReleased(ManiaAction action)
+ {
+ }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
// This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
index 57241da564..75cc351310 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -98,11 +98,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
return false;
}
- public bool OnReleased(ManiaAction action)
+ public void OnReleased(ManiaAction action)
{
if (action == this.action.Value)
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
- return false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
index 386bcbb724..90e78c3899 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -18,30 +18,17 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
public class ColumnHitObjectArea : CompositeDrawable, IHasAccentColour
{
- private const float hit_target_bar_height = 2;
-
private readonly IBindable direction = new Bindable();
- private readonly Container hitTargetLine;
- private readonly Drawable hitTargetBar;
+ private readonly Drawable hitTarget;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
InternalChildren = new[]
{
- hitTargetBar = new Box
+ hitTarget = new DefaultHitTarget
{
RelativeSizeAxes = Axes.X,
- Height = NotePiece.NOTE_HEIGHT,
- Alpha = 0.6f,
- Colour = Color4.Black
- },
- hitTargetLine = new Container
- {
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Child = new Box { RelativeSizeAxes = Axes.Both }
},
hitObjectContainer
};
@@ -55,17 +42,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
- hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
- hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ hitTarget.Anchor = hitTarget.Origin = anchor;
}, true);
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
- updateColours();
- }
-
private Color4 accentColour;
public Color4 AccentColour
@@ -78,21 +58,88 @@ namespace osu.Game.Rulesets.Mania.UI.Components
accentColour = value;
- updateColours();
+ if (hitTarget is IHasAccentColour colouredHitTarget)
+ colouredHitTarget.AccentColour = accentColour;
}
}
- private void updateColours()
+ private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
{
- if (!IsLoaded)
- return;
+ private const float hit_target_bar_height = 2;
- hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ private readonly IBindable direction = new Bindable();
+
+ private readonly Container hitTargetLine;
+ private readonly Drawable hitTargetBar;
+
+ public DefaultHitTarget()
{
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = NotePiece.NOTE_HEIGHT,
+ Alpha = 0.6f,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(dir =>
+ {
+ Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+
+ hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
+ hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
index 85880222d7..60fc2713b3 100644
--- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
@@ -115,11 +115,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
return false;
}
- public bool OnReleased(ManiaAction action)
+ public void OnReleased(ManiaAction action)
{
if (action == this.action.Value)
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
- return false;
}
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index d371c1f7a8..2c497541a8 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -14,11 +14,9 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
-using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Bindable configDirection = new Bindable();
- public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator(Beatmap).BarLines;
@@ -67,8 +65,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages);
- public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
-
public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns;
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index ccbff226a9..35de47e208 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -5,7 +5,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osuTK;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
index 3718264a42..b36d0b5728 100644
--- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Osu.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
index 67338b7bbe..94568e3852 100644
--- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index 450f7de6d2..cd3daf18a9 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
index 693faee3b7..85a41137d4 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
- [TestCase(6.931145117263422, "diffcalc-test")]
+ [TestCase(6.9311451172608853d, "diffcalc-test")]
[TestCase(1.0736587013228804d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png
new file mode 100644
index 0000000000..ff8b02ce80
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/approachcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png
new file mode 100644
index 0000000000..2af0569bcb
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png
new file mode 100644
index 0000000000..e8b674d62a
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-1.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png
new file mode 100644
index 0000000000..dbf7bc73bc
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-2.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png
new file mode 100644
index 0000000000..43990c46e7
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-3.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png
new file mode 100644
index 0000000000..4564f6d8bf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-4.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png
new file mode 100644
index 0000000000..dcef35eb59
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-5.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png
new file mode 100644
index 0000000000..bfc0a01be5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-6.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png
new file mode 100644
index 0000000000..b9079ad5d5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-7.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png
new file mode 100644
index 0000000000..3a3d61b947
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-8.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png
new file mode 100644
index 0000000000..3e703cd1cf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/default-9.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png
new file mode 100644
index 0000000000..3c3ebbfd0b
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit0.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png
new file mode 100644
index 0000000000..9ecc302910
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit100.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png
new file mode 100644
index 0000000000..24945f7d92
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit300.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png
new file mode 100644
index 0000000000..d3f7eec5ee
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hit50.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png
new file mode 100644
index 0000000000..a5a3545abf
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircle.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png
new file mode 100644
index 0000000000..b4062b8c62
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/hitcircleoverlay.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
new file mode 100644
index 0000000000..5369de24e9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/old-skin/skin.ini
@@ -0,0 +1,2 @@
+[General]
+Version: 1.0
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
deleted file mode 100644
index 38aac50df6..0000000000
--- a/osu.Game.Rulesets.Osu.Tests/SkinnableTestScene.cs
+++ /dev/null
@@ -1,82 +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.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/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
index ac627aa23e..02d4406809 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index aa170eae1e..7b96e2ec6a 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -7,7 +7,10 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing.Input;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
@@ -21,12 +24,50 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CursorTrail)
};
- [BackgroundDependencyLoader]
- private void load()
+ [Cached]
+ private GameplayBeatmap gameplayBeatmap;
+
+ private ClickingCursorContainer lastContainer;
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ public TestSceneGameplayCursor()
+ {
+ gameplayBeatmap = new GameplayBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
+ }
+
+ [TestCase(1, 1)]
+ [TestCase(5, 1)]
+ [TestCase(10, 1)]
+ [TestCase(1, 1.5f)]
+ [TestCase(5, 1.5f)]
+ [TestCase(10, 1.5f)]
+ public void TestSizing(int circleSize, float userScale)
+ {
+ AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
+ AddStep("turn on autosizing", () => config.Set(OsuSetting.AutoCursorSize, true));
+
+ AddStep("load content", loadContent);
+
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize) * userScale);
+
+ AddStep("set user scale to 1", () => config.Set(OsuSetting.GameplayCursorSize, 1f));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == OsuCursorContainer.GetScaleForCircleSize(circleSize));
+
+ AddStep("turn off autosizing", () => config.Set(OsuSetting.AutoCursorSize, false));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == 1);
+
+ AddStep($"set user scale to {userScale}", () => config.Set(OsuSetting.GameplayCursorSize, userScale));
+ AddUntilStep("cursor size correct", () => lastContainer.ActiveCursor.Scale.X == userScale);
+ }
+
+ private void loadContent()
{
SetContents(() => new MovingCursorInputManager
{
- Child = new ClickingCursorContainer
+ Child = lastContainer = new ClickingCursorContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
index 64f353c4d9..ae5a28217c 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -57,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableHitCircle(circle, auto);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
return drawable;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
index 55c6b22146..21ebce8c23 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneHitCircleHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
new file mode 100644
index 0000000000..3ff37c4147
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
@@ -0,0 +1,154 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Play;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestSceneLegacyBeatmapSkin : ScreenTestScene
+ {
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapComboColours(bool customSkinColoursPresent)
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColours()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [Test]
+ public void TestBeatmapNoComboColoursSkinOverride()
+ {
+ ExposedPlayer player = null;
+
+ AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
+ AddUntilStep("wait for player", () => player.IsLoaded);
+
+ AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
+ {
+ ExposedPlayer player;
+
+ Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
+
+ LoadScreen(player = new ExposedPlayer(userHasCustomColours));
+
+ return player;
+ }
+
+ private class ExposedPlayer : Player
+ {
+ private readonly bool userHasCustomColours;
+
+ public ExposedPlayer(bool userHasCustomColours)
+ : base(false, false)
+ {
+ this.userHasCustomColours = userHasCustomColours;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ dependencies.CacheAs(new TestSkin(userHasCustomColours));
+ return dependencies;
+ }
+
+ public IReadOnlyList UsableComboColours =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig>(GlobalSkinColours.ComboColours)?.Value;
+ }
+
+ private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
+ {
+ private readonly bool hasColours;
+
+ public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
+ : base(new Beatmap
+ {
+ BeatmapInfo =
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Ruleset = new OsuRuleset().RulesetInfo,
+ },
+ HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
+ }, null, null, audio)
+ {
+ this.hasColours = hasColours;
+ }
+
+ protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
+ }
+
+ private class TestBeatmapSkin : LegacyBeatmapSkin
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(50, 100, 150, 255),
+ new Color4(40, 80, 120, 255),
+ };
+
+ public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
+ : base(beatmap, new ResourceStore(), null)
+ {
+ if (hasColours)
+ Configuration.AddComboColours(Colours);
+ }
+ }
+
+ private class TestSkin : LegacySkin, ISkinSource
+ {
+ public static Color4[] Colours { get; } =
+ {
+ new Color4(150, 100, 50, 255),
+ new Color4(20, 20, 20, 255),
+ };
+
+ public TestSkin(bool hasCustomColours)
+ : base(new SkinInfo(), null, null, string.Empty)
+ {
+ if (hasCustomColours)
+ Configuration.AddComboColours(Colours);
+ }
+
+ public event Action SourceChanged
+ {
+ add { }
+ remove { }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
index eff4d919b0..4af4d5f966 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
@@ -9,7 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
@@ -33,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CircularDistanceSnapGrid)
};
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneOsuDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
}
[SetUp]
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
index 64e7632b1b..412effe176 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
+ SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
return base.CreatePlayer(ruleset);
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
index 863d0eda09..d692be89b2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
index a9d5c03517..defd3a6f22 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs
@@ -22,6 +22,7 @@ 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;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -362,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableSlider(slider);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnNewResult += onNewResult;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
index 2a9c1d167b..d0ee1bddb5 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSliderHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
index 5f75cbabec..94df239267 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs
@@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double time_during_slide_2 = 3000;
private const double time_during_slide_3 = 3500;
private const double time_during_slide_4 = 3800;
+ private const double time_slider_end = 4000;
private List judgementResults;
private bool allJudgedFired;
@@ -284,16 +285,60 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("Tracking acquired", assertMidSliderJudgements);
}
+ ///
+ /// Scenario:
+ /// - Press a key on the slider head
+ /// - While holding the key, move cursor close to the edge of tracking area
+ /// - Keep the cursor on the edge of tracking area until the slider ends
+ /// Expected Result:
+ /// A passing test case will have the slider track the cursor throughout the whole test.
+ ///
+ [Test]
+ public void TestTrackingAreaEdge()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.19f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 250 },
+ new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
+ });
+
+ AddAssert("Tracking kept", assertGreatJudge);
+ }
+
+ ///
+ /// Scenario:
+ /// - Press a key on the slider head
+ /// - While holding the key, move cursor just outside the tracking area
+ /// - Keep the cursor just outside the tracking area until the slider ends
+ /// Expected Result:
+ /// A passing test case will have the slider drop the tracking on frame 2.
+ ///
+ [Test]
+ public void TestTrackingAreaOutsideEdge()
+ {
+ performTest(new List
+ {
+ new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start },
+ new OsuReplayFrame { Position = new Vector2(0, OsuHitObject.OBJECT_RADIUS * 1.21f), Actions = { OsuAction.LeftButton }, Time = time_slider_start + 250 },
+ new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.201f), Actions = { OsuAction.LeftButton }, Time = time_slider_end },
+ });
+
+ AddAssert("Tracking dropped", assertMidSliderJudgementFail);
+ }
+
private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great;
- private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
+ private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss;
- private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great;
+ private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great;
- private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss;
+ private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss;
private ScoreAccessibleReplayPlayer currentPlayer;
+ private const float slider_path_length = 25;
+
private void performTest(List frames)
{
AddStep("load player", () =>
@@ -309,8 +354,8 @@ namespace osu.Game.Rulesets.Osu.Tests
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
- new Vector2(25, 0),
- }, 25),
+ new Vector2(slider_path_length, 0),
+ }, slider_path_length),
}
},
BeatmapInfo =
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
index dde2aa53e0..5dd2bd18a8 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
@@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep($"move mouse to control point {index}", () =>
{
- Vector2 position = slider.Position + slider.Path.ControlPoints[index];
+ Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
});
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index 3ed3f3e981..f53b64c729 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Depth = depthIndex++
};
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
index a0ab1908d6..dd863deed2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs
@@ -14,9 +14,10 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
- public TestSceneSpinnerHidden()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Mods.Value = new[] { new OsuModHidden() };
- }
+ SelectedMods.Value = new[] { new OsuModHidden() };
+ });
}
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index d0ce0c33c2..5cf571d961 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -4,7 +4,7 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
@@ -70,6 +70,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
}
+ [Test]
+ public void TestSpinPerMinuteOnRewind()
+ {
+ double estimatedSpm = 0;
+
+ addSeekStep(2500);
+ AddStep("retrieve spm", () => estimatedSpm = drawableSpinner.SpmCounter.SpinsPerMinute);
+
+ addSeekStep(5000);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+
+ addSeekStep(2500);
+ AddAssert("spm still valid", () => Precision.AlmostEquals(drawableSpinner.SpmCounter.SpinsPerMinute, estimatedSpm, 1.0));
+ }
+
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
@@ -84,10 +99,10 @@ namespace osu.Game.Rulesets.Osu.Tests
new Spinner
{
Position = new Vector2(256, 192),
- EndTime = 5000,
+ EndTime = 6000,
},
// placeholder object to avoid hitting the results screen
- new HitObject
+ new HitCircle
{
StartTime = 99999,
}
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index fddf176fd0..217707b180 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
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
index 2296030f81..147d74c929 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs
@@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types;
-using System;
+using System.Linq;
using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
public class OsuBeatmapConverter : BeatmapConverter
{
- public OsuBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public OsuBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
}
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasPosition) };
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap)
{
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
index bb47c7e464..407f5f540e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs
@@ -30,12 +30,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
protected override bool OnClick(ClickEvent e)
{
- EndPlacement();
+ EndPlacement(true);
return true;
}
public override void UpdatePosition(Vector2 screenSpacePosition)
{
+ BeginPlacement();
HitObject.Position = ToLocalSpace(screenSpacePosition);
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
index a864257274..b0e13808a5 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs
@@ -7,10 +7,10 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
- public abstract class OsuSelectionBlueprint : SelectionBlueprint
+ public abstract class OsuSelectionBlueprint : OverlaySelectionBlueprint
where T : OsuHitObject
{
- protected T HitObject => (T)DrawableObject.HitObject;
+ protected new T HitObject => (T)DrawableObject.HitObject;
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
new file mode 100644
index 0000000000..0fc441fec6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointConnectionPiece.cs
@@ -0,0 +1,75 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Lines;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
+{
+ ///
+ /// A visualisation of the line between two s.
+ ///
+ public class PathControlPointConnectionPiece : CompositeDrawable
+ {
+ public PathControlPoint ControlPoint;
+
+ private readonly Path path;
+ private readonly Slider slider;
+
+ private IBindable sliderPosition;
+ private IBindable pathVersion;
+
+ public PathControlPointConnectionPiece(Slider slider, PathControlPoint controlPoint)
+ {
+ this.slider = slider;
+ ControlPoint = controlPoint;
+
+ Origin = Anchor.Centre;
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = path = new SmoothPath
+ {
+ Anchor = Anchor.Centre,
+ PathRadius = 1
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ sliderPosition = slider.PositionBindable.GetBoundCopy();
+ sliderPosition.BindValueChanged(_ => updateConnectingPath());
+
+ pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion.BindValueChanged(_ => updateConnectingPath());
+
+ updateConnectingPath();
+ }
+
+ ///
+ /// Updates the path connecting this control point to the next one.
+ ///
+ private void updateConnectingPath()
+ {
+ Position = slider.StackedPosition + ControlPoint.Position.Value;
+
+ path.ClearVertices();
+
+ int index = slider.Path.ControlPoints.IndexOf(ControlPoint) + 1;
+
+ if (index == 0 || index == slider.Path.ControlPoints.Count)
+ return;
+
+ path.AddVertex(Vector2.Zero);
+ path.AddVertex(slider.Path.ControlPoints[index].Position.Value - ControlPoint.Position.Value);
+
+ path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
index 0ccf020300..af4da5e853 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs
@@ -6,11 +6,11 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
using osuTK.Graphics;
@@ -18,16 +18,18 @@ using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
+ ///
+ /// A visualisation of a single in a .
+ ///
public class PathControlPointPiece : BlueprintPiece
{
- public Action RequestSelection;
- public Action ControlPointsChanged;
+ public Action RequestSelection;
public readonly BindableBool IsSelected = new BindableBool();
- public readonly int Index;
+
+ public readonly PathControlPoint ControlPoint;
private readonly Slider slider;
- private readonly Path path;
private readonly Container marker;
private readonly Drawable markerRing;
@@ -37,21 +39,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
[Resolved]
private OsuColour colours { get; set; }
- public PathControlPointPiece(Slider slider, int index)
+ private IBindable sliderPosition;
+ private IBindable controlPointPosition;
+
+ public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
{
this.slider = slider;
- Index = index;
+ ControlPoint = controlPoint;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
- path = new SmoothPath
- {
- Anchor = Anchor.Centre,
- PathRadius = 1
- },
marker = new Container
{
Anchor = Anchor.Centre,
@@ -86,48 +86,35 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
};
}
- protected override void Update()
+ protected override void LoadComplete()
{
- base.Update();
+ base.LoadComplete();
- Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
+ sliderPosition = slider.PositionBindable.GetBoundCopy();
+ sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
+
+ controlPointPosition = ControlPoint.Position.GetBoundCopy();
+ controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
+
+ IsSelected.BindValueChanged(_ => updateMarkerDisplay());
updateMarkerDisplay();
- updateConnectingPath();
- }
-
- ///
- /// Updates the state of the circular control point marker.
- ///
- private void updateMarkerDisplay()
- {
- markerRing.Alpha = IsSelected.Value ? 1 : 0;
-
- Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
- if (IsHovered || IsSelected.Value)
- colour = Color4.White;
- marker.Colour = colour;
- }
-
- ///
- /// Updates the path connecting this control point to the previous one.
- ///
- private void updateConnectingPath()
- {
- path.ClearVertices();
-
- if (Index != slider.Path.ControlPoints.Length - 1)
- {
- path.AddVertex(Vector2.Zero);
- path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
- }
-
- path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
}
// The connecting path is excluded from positional input
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
+ protected override bool OnHover(HoverEvent e)
+ {
+ updateMarkerDisplay();
+ return false;
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ updateMarkerDisplay();
+ }
+
protected override bool OnMouseDown(MouseDownEvent e)
{
if (RequestSelection == null)
@@ -136,29 +123,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
switch (e.Button)
{
case MouseButton.Left:
- RequestSelection.Invoke(Index, e);
+ RequestSelection.Invoke(this, e);
return true;
case MouseButton.Right:
if (!IsSelected.Value)
- RequestSelection.Invoke(Index, e);
+ RequestSelection.Invoke(this, e);
return false; // Allow context menu to show
}
return false;
}
- protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
-
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
protected override bool OnDragStart(DragStartEvent e) => e.Button == MouseButton.Left;
- protected override bool OnDrag(DragEvent e)
+ protected override void OnDrag(DragEvent e)
{
- var newControlPoints = slider.Path.ControlPoints.ToArray();
-
- if (Index == 0)
+ if (ControlPoint == slider.Path.ControlPoints[0])
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
@@ -168,29 +151,26 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.StartTime = snappedTime;
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
- for (int i = 1; i < newControlPoints.Length; i++)
- newControlPoints[i] -= movementDelta;
+ for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
+ slider.Path.ControlPoints[i].Position.Value -= movementDelta;
}
else
- newControlPoints[Index] += e.Delta;
-
- if (isSegmentSeparatorWithNext)
- newControlPoints[Index + 1] = newControlPoints[Index];
-
- if (isSegmentSeparatorWithPrevious)
- newControlPoints[Index - 1] = newControlPoints[Index];
-
- ControlPointsChanged?.Invoke(newControlPoints);
-
- return true;
+ ControlPoint.Position.Value += e.Delta;
}
- protected override bool OnDragEnd(DragEndEvent e) => true;
+ ///
+ /// Updates the state of the circular control point marker.
+ ///
+ private void updateMarkerDisplay()
+ {
+ Position = slider.StackedPosition + ControlPoint.Position.Value;
- private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
+ markerRing.Alpha = IsSelected.Value ? 1 : 0;
- 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];
+ Color4 colour = ControlPoint.Type.Value != null ? colours.Red : colours.Yellow;
+ if (IsHovered || IsSelected.Value)
+ colour = Color4.White;
+ marker.Colour = colour;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
index cdca48490e..e293eba9d7 100644
--- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
-using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -14,25 +14,28 @@ using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Edit.Compose;
-using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu
{
- public Action ControlPointsChanged;
-
internal readonly Container Pieces;
+
+ private readonly Container connections;
+
private readonly Slider slider;
+
private readonly bool allowSelection;
private InputManager inputManager;
- [Resolved(CanBeNull = true)]
- private IPlacementHandler placementHandler { get; set; }
+ private IBindableList controlPoints;
+
+ public Action> RemoveControlPointsRequested;
public PathControlPointVisualiser(Slider slider, bool allowSelection)
{
@@ -41,7 +44,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
RelativeSizeAxes = Axes.Both;
- InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both };
+ InternalChildren = new Drawable[]
+ {
+ connections = new Container { RelativeSizeAxes = Axes.Both },
+ Pieces = new Container { RelativeSizeAxes = Axes.Both }
+ };
}
protected override void LoadComplete()
@@ -49,33 +56,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
base.LoadComplete();
inputManager = GetContainingInputManager();
+
+ controlPoints = slider.Path.ControlPoints.GetBoundCopy();
+ controlPoints.ItemsAdded += addControlPoints;
+ controlPoints.ItemsRemoved += removeControlPoints;
+
+ addControlPoints(controlPoints);
}
- protected override void Update()
+ private void addControlPoints(IEnumerable controlPoints)
{
- base.Update();
-
- while (slider.Path.ControlPoints.Length > Pieces.Count)
+ foreach (var point in controlPoints)
{
- var piece = new PathControlPointPiece(slider, Pieces.Count)
+ Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
{
- ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
- };
+ if (allowSelection)
+ d.RequestSelection = selectPiece;
+ }));
- if (allowSelection)
- piece.RequestSelection = selectPiece;
-
- Pieces.Add(piece);
+ connections.Add(new PathControlPointConnectionPiece(slider, point));
}
+ }
- while (slider.Path.ControlPoints.Length < Pieces.Count)
- Pieces.Remove(Pieces[Pieces.Count - 1]);
+ private void removeControlPoints(IEnumerable controlPoints)
+ {
+ foreach (var point in controlPoints)
+ {
+ Pieces.RemoveAll(p => p.ControlPoint == point);
+ connections.RemoveAll(c => c.ControlPoint == point);
+ }
}
protected override bool OnClick(ClickEvent e)
{
foreach (var piece in Pieces)
+ {
piece.IsSelected.Value = false;
+ }
+
return false;
}
@@ -90,53 +108,35 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return false;
}
- public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
+ public void OnReleased(PlatformAction action)
+ {
+ }
- private void selectPiece(int index, MouseButtonEvent e)
+ private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
- Pieces[index].IsSelected.Toggle();
+ piece.IsSelected.Toggle();
else
{
- foreach (var piece in Pieces)
- piece.IsSelected.Value = piece.Index == index;
+ foreach (var p in Pieces)
+ p.IsSelected.Value = p == piece;
}
}
private bool deleteSelected()
{
- var newControlPoints = new List();
-
- foreach (var piece in Pieces)
- {
- if (!piece.IsSelected.Value)
- newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
- }
+ List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList();
// Ensure that there are any points to be deleted
- if (newControlPoints.Count == slider.Path.ControlPoints.Length)
+ if (toRemove.Count == 0)
return false;
- // If there are 0 remaining control points, treat the slider as being deleted
- if (newControlPoints.Count == 0)
- {
- placementHandler?.Delete(slider);
- return true;
- }
-
- // Make control points relative
- Vector2 first = newControlPoints[0];
- for (int i = 0; i < newControlPoints.Count; i++)
- newControlPoints[i] = newControlPoints[i] - first;
-
- // The slider's position defines the position of the first control point, and all further control points are relative to that point
- slider.Position += first;
+ RemoveControlPointsRequested?.Invoke(toRemove);
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
foreach (var piece in Pieces)
piece.IsSelected.Value = false;
- ControlPointsChanged?.Invoke(newControlPoints.ToArray());
return true;
}
@@ -147,16 +147,63 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (!Pieces.Any(p => p.IsHovered))
return null;
- int selectedPoints = Pieces.Count(p => p.IsSelected.Value);
+ var selectedPieces = Pieces.Where(p => p.IsSelected.Value).ToList();
+ int count = selectedPieces.Count;
- if (selectedPoints == 0)
+ if (count == 0)
return null;
+ List
private const double editor_hit_object_fade_out_extension = 500;
- public DrawableOsuEditRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods)
: base(ruleset, beatmap, mods)
{
}
@@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Edit
if (existing == null)
return;
+ hitObject.RemoveTransform(existing);
+
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
break;
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
new file mode 100644
index 0000000000..330f34b85c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs
@@ -0,0 +1,41 @@
+// 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.Rulesets.Edit;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
+using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Screens.Edit.Compose.Components;
+
+namespace osu.Game.Rulesets.Osu.Edit
+{
+ public class OsuBlueprintContainer : ComposeBlueprintContainer
+ {
+ public OsuBlueprintContainer(IEnumerable drawableHitObjects)
+ : base(drawableHitObjects)
+ {
+ }
+
+ protected override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
+
+ public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case DrawableHitCircle circle:
+ return new HitCircleSelectionBlueprint(circle);
+
+ case DrawableSlider slider:
+ return new SliderSelectionBlueprint(slider);
+
+ case DrawableSpinner spinner:
+ return new SpinnerSelectionBlueprint(spinner);
+ }
+
+ return base.CreateBlueprintFor(hitObject);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
index 9b00204d51..ff3be97427 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using JetBrains.Annotations;
+using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
@@ -8,8 +10,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
- public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
- : base(hitObject, nextHitObject, hitObject.StackedEndPosition)
+ public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null)
+ : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime)
{
Masking = true;
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 812afaaa24..cdf78a5902 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -9,12 +9,7 @@ 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;
-using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Compose.Components;
@@ -27,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
- protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
=> new DrawableOsuEditRuleset(ruleset, beatmap, mods);
protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[]
@@ -37,27 +32,13 @@ namespace osu.Game.Rulesets.Osu.Edit
new SpinnerCompositionTool()
};
- public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler();
-
- public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
- {
- switch (hitObject)
- {
- case DrawableHitCircle circle:
- return new HitCircleSelectionBlueprint(circle);
-
- case DrawableSlider slider:
- return new SliderSelectionBlueprint(slider);
-
- case DrawableSpinner spinner:
- return new SpinnerSelectionBlueprint(spinner);
- }
-
- return base.CreateBlueprintFor(hitObject);
- }
+ protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects);
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects)
{
+ if (BlueprintContainer.CurrentTool is SpinnerCompositionTool)
+ return null;
+
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
@@ -91,10 +72,30 @@ namespace osu.Game.Rulesets.Osu.Edit
if (sourceIndex == -1)
return null;
- OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
- OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
+ HitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
- return new OsuDistanceSnapGrid(sourceObject, targetObject);
+ int targetIndex = sourceIndex + targetOffset;
+ HitObject targetObject = null;
+
+ // Keep advancing the target object while its start time falls before the end time of the source object
+ while (true)
+ {
+ if (targetIndex >= EditorBeatmap.HitObjects.Count)
+ break;
+
+ if (EditorBeatmap.HitObjects[targetIndex].StartTime >= sourceObject.GetEndTime())
+ {
+ targetObject = EditorBeatmap.HitObjects[targetIndex];
+ break;
+ }
+
+ targetIndex++;
+ }
+
+ if (sourceObject is Spinner)
+ return null;
+
+ return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
index 7a5b98864c..bf30fbc351 100644
--- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs
@@ -27,22 +27,5 @@ namespace osu.Game.Rulesets.Osu.Judgements
return 300;
}
}
-
- protected override double HealthIncreaseFor(HitResult result)
- {
- switch (result)
- {
- case HitResult.Miss:
- return -0.02;
-
- case HitResult.Meh:
- case HitResult.Good:
- case HitResult.Great:
- return 0.01;
-
- default:
- return 0;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
deleted file mode 100644
index 5104d9494b..0000000000
--- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs
+++ /dev/null
@@ -1,14 +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.Rulesets.Scoring;
-
-namespace osu.Game.Rulesets.Osu.Judgements
-{
- public class OsuSliderTailJudgement : OsuJudgement
- {
- public override bool AffectsCombo => false;
-
- protected override int NumericResultFor(HitResult result) => 0;
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 65d7acc911..fe46876050 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Autopilot";
public override string Acronym => "AP";
- public override IconUsage Icon => OsuIcon.ModAutopilot;
+ public override IconUsage? Icon => OsuIcon.ModAutopilot;
public override ModType Type => ModType.Automation;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 63110b2797..937473e824 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -18,13 +18,13 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor
+ public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToHealthProcessor
{
public override string Name => "Blinds";
public override string Description => "Play with blinds on your screen.";
public override string Acronym => "BL";
- public override IconUsage Icon => FontAwesome.Solid.Adjust;
+ public override IconUsage? Icon => FontAwesome.Solid.Adjust;
public override ModType Type => ModType.DifficultyIncrease;
public override bool Ranked => false;
@@ -37,9 +37,9 @@ namespace osu.Game.Rulesets.Osu.Mods
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
}
- public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
+ public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
- scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
+ healthProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
}
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs
new file mode 100644
index 0000000000..5d9a524577
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.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;
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModCinema : ModCinema
+ {
+ public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
+
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } },
+ Replay = new OsuAutoGenerator(beatmap).Generate()
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
index adca95cf8a..73cb483ef0 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs
@@ -5,13 +5,13 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModDeflate : OsuModeObjectScaleTween
+ public class OsuModDeflate : OsuModObjectScaleTween
{
public override string Name => "Deflate";
public override string Acronym => "DF";
- public override IconUsage Icon => FontAwesome.Solid.CompressArrowsAlt;
+ public override IconUsage? Icon => FontAwesome.Solid.CompressArrowsAlt;
public override string Description => "Hit them at the right size!";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
new file mode 100644
index 0000000000..75de6896a3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Osu.Mods
+{
+ public class OsuModDifficultyAdjust : ModDifficultyAdjust
+ {
+ [SettingSource("Circle Size", "Override a beatmap's set CS.", FIRST_SETTING_ORDER - 1)]
+ public BindableNumber CircleSize { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ [SettingSource("Approach Rate", "Override a beatmap's set AR.", LAST_SETTING_ORDER + 1)]
+ public BindableNumber ApproachRate { get; } = new BindableFloat
+ {
+ Precision = 0.1f,
+ MinValue = 1,
+ MaxValue = 10,
+ Default = 5,
+ Value = 5,
+ };
+
+ protected override void TransferSettings(BeatmapDifficulty difficulty)
+ {
+ base.TransferSettings(difficulty);
+
+ TransferSetting(CircleSize, difficulty.CircleSize);
+ TransferSetting(ApproachRate, difficulty.ApproachRate);
+ }
+
+ protected override void ApplySettings(BeatmapDifficulty difficulty)
+ {
+ base.ApplySettings(difficulty);
+
+ difficulty.CircleSize = CircleSize.Value;
+ difficulty.ApproachRate = ApproachRate.Value;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
index 778c2f7d43..ac20407ed2 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs
@@ -8,7 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
index 3c81203ad7..f08d4e8f5e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs
@@ -5,13 +5,13 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods
{
- internal class OsuModGrow : OsuModeObjectScaleTween
+ internal class OsuModGrow : OsuModObjectScaleTween
{
public override string Name => "Grow";
public override string Acronym => "GR";
- public override IconUsage Icon => FontAwesome.Solid.ArrowsAltV;
+ public override IconUsage? Icon => FontAwesome.Solid.ArrowsAltV;
public override string Description => "Hit them at the right size!";
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index 3d566362ae..bc5f79331f 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -28,11 +28,8 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
- var newControlPoints = new Vector2[slider.Path.ControlPoints.Length];
- for (int i = 0; i < slider.Path.ControlPoints.Length; i++)
- newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y);
-
- slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance);
+ foreach (var point in slider.Path.ControlPoints)
+ point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
index 5668c17792..7780e23a26 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs
@@ -2,10 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModNightcore : ModNightcore
+ public class OsuModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.12;
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
similarity index 96%
rename from osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
rename to osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 923278f484..42ddddc4dd 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModeObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
///
/// Adjusts the size of hit objects during their fade in animation.
///
- public abstract class OsuModeObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
+ public abstract class OsuModObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
{
public override ModType Type => ModType.Fun;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 649b01c132..6286c80d7c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using static osu.Game.Input.Handlers.ReplayInputHandler;
@@ -19,69 +20,18 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
- public void Update(Playfield playfield)
- {
- bool requiresHold = false;
- bool requiresHit = false;
+ ///
+ /// How early before a hitobject's start time to trigger a hit.
+ ///
+ private const float relax_leniency = 3;
- const float relax_leniency = 3;
-
- foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
- {
- if (!(drawable is DrawableOsuHitObject osuHit))
- continue;
-
- double time = osuHit.Clock.CurrentTime;
- double relativetime = time - osuHit.HitObject.StartTime;
-
- if (time < osuHit.HitObject.StartTime - relax_leniency) continue;
-
- if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit)
- continue;
-
- if (osuHit is DrawableHitCircle && osuHit.IsHovered)
- {
- Debug.Assert(osuHit.HitObject.HitWindows != null);
- requiresHit |= osuHit.HitObject.HitWindows.CanBeHit(relativetime);
- }
-
- requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner;
- }
-
- if (requiresHit)
- {
- addAction(false);
- addAction(true);
- }
-
- addAction(requiresHold);
- }
-
- private bool wasHit;
+ private bool isDownState;
private bool wasLeft;
private OsuInputManager osuInputManager;
- private void addAction(bool hitting)
- {
- if (wasHit == hitting)
- return;
-
- wasHit = hitting;
-
- var state = new ReplayState
- {
- PressedActions = new List()
- };
-
- if (hitting)
- {
- state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
- wasLeft = !wasLeft;
- }
-
- state.Apply(osuInputManager.CurrentState, osuInputManager);
- }
+ private ReplayState state;
+ private double lastStateChangeTime;
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
@@ -89,5 +39,85 @@ namespace osu.Game.Rulesets.Osu.Mods
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
osuInputManager.AllowUserPresses = false;
}
+
+ public void Update(Playfield playfield)
+ {
+ bool requiresHold = false;
+ bool requiresHit = false;
+
+ double time = playfield.Clock.CurrentTime;
+
+ foreach (var h in playfield.HitObjectContainer.AliveObjects.OfType())
+ {
+ // we are not yet close enough to the object.
+ if (time < h.HitObject.StartTime - relax_leniency)
+ break;
+
+ // already hit or beyond the hittable end time.
+ if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime))
+ continue;
+
+ switch (h)
+ {
+ case DrawableHitCircle circle:
+ handleHitCircle(circle);
+ break;
+
+ case DrawableSlider slider:
+ // Handles cases like "2B" beatmaps, where sliders may be overlapping and simply holding is not enough.
+ if (!slider.HeadCircle.IsHit)
+ handleHitCircle(slider.HeadCircle);
+
+ requiresHold |= slider.Ball.IsHovered || h.IsHovered;
+ break;
+
+ case DrawableSpinner _:
+ requiresHold = true;
+ break;
+ }
+ }
+
+ if (requiresHit)
+ {
+ changeState(false);
+ changeState(true);
+ }
+
+ if (requiresHold)
+ changeState(true);
+ else if (isDownState && time - lastStateChangeTime > AutoGenerator.KEY_UP_DELAY)
+ changeState(false);
+
+ void handleHitCircle(DrawableHitCircle circle)
+ {
+ if (!circle.IsHovered)
+ return;
+
+ Debug.Assert(circle.HitObject.HitWindows != null);
+ requiresHit |= circle.HitObject.HitWindows.CanBeHit(time - circle.HitObject.StartTime);
+ }
+
+ void changeState(bool down)
+ {
+ if (isDownState == down)
+ return;
+
+ isDownState = down;
+ lastStateChangeTime = time;
+
+ state = new ReplayState
+ {
+ PressedActions = new List()
+ };
+
+ if (down)
+ {
+ state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
+ wasLeft = !wasLeft;
+ }
+
+ state?.Apply(osuInputManager.CurrentState, osuInputManager);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index e786ec86f9..940c888f3a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -20,13 +20,13 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spin In";
public override string Acronym => "SI";
- public override IconUsage Icon => FontAwesome.Solid.Undo;
+ public override IconUsage? Icon => FontAwesome.Solid.Undo;
public override ModType Type => ModType.Fun;
public override string Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
- public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 1cdcddbd33..9d5d300a9e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Spun Out";
public override string Acronym => "SO";
- public override IconUsage Icon => OsuIcon.ModSpunout;
+ public override IconUsage? Icon => OsuIcon.ModSpunout;
public override ModType Type => ModType.DifficultyReduction;
public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index 8360e2692e..2464308347 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Target";
public override string Acronym => "TP";
public override ModType Type => ModType.Conversion;
- public override IconUsage Icon => OsuIcon.ModTarget;
+ public override IconUsage? Icon => OsuIcon.ModTarget;
public override string Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 1;
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index 7e20feba02..774f9cf58b 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -6,11 +6,11 @@ using System.Linq;
using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -18,12 +18,11 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Traceable";
public override string Acronym => "TC";
- public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost;
public override ModType Type => ModType.Fun;
public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
+ public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
private Bindable increaseFirstObjectVisibility = new Bindable();
public void ReadFromConfig(OsuConfigManager config)
@@ -54,13 +53,8 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
case DrawableSlider slider:
- slider.AccentColour.BindValueChanged(_ =>
- {
- //will trigger on skin change.
- slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
- slider.Body.BorderColour = slider.AccentColour.Value;
- }, true);
-
+ slider.Body.OnSkinChanged += () => applySliderState(slider);
+ applySliderState(slider);
break;
case DrawableSpinner spinner:
@@ -69,5 +63,11 @@ namespace osu.Game.Rulesets.Osu.Mods
break;
}
}
+
+ private void applySliderState(DrawableSlider slider)
+ {
+ ((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
+ ((PlaySliderBody)slider.Body.Drawable).BorderColour = slider.AccentColour.Value;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
index a9475af638..41daef1f38 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Transform";
public override string Acronym => "TR";
- public override IconUsage Icon => FontAwesome.Solid.ArrowsAlt;
+ public override IconUsage? Icon => FontAwesome.Solid.ArrowsAlt;
public override ModType Type => ModType.Fun;
public override string Description => "Everything rotates. EVERYTHING.";
public override double ScoreMultiplier => 1;
@@ -27,26 +28,40 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableHitObjects(IEnumerable drawables)
{
foreach (var drawable in drawables)
+ drawable.ApplyCustomUpdateState += applyTransform;
+ }
+
+ private void applyTransform(DrawableHitObject drawable, ArmedState state)
+ {
+ switch (drawable)
{
- var hitObject = (OsuHitObject)drawable.HitObject;
+ case DrawableSliderHead _:
+ case DrawableSliderTail _:
+ case DrawableSliderTick _:
+ case DrawableRepeatPoint _:
+ return;
- float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
+ default:
+ var hitObject = (OsuHitObject)drawable.HitObject;
- Vector2 originalPosition = drawable.Position;
- Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance;
+ float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2;
- //the - 1 and + 1 prevents the hit objects to appear in the wrong position.
- double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
- double moveDuration = hitObject.TimePreempt + 1;
+ Vector2 originalPosition = drawable.Position;
+ Vector2 appearOffset = new Vector2(MathF.Cos(theta), MathF.Sin(theta)) * appearDistance;
- using (drawable.BeginAbsoluteSequence(appearTime, true))
- {
- drawable
- .MoveToOffset(appearOffset)
- .MoveTo(originalPosition, moveDuration, Easing.InOutSine);
- }
+ //the - 1 and + 1 prevents the hit objects to appear in the wrong position.
+ double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
+ double moveDuration = hitObject.TimePreempt + 1;
- theta += (float)hitObject.TimeFadeIn / 1000;
+ using (drawable.BeginAbsoluteSequence(appearTime, true))
+ {
+ drawable
+ .MoveToOffset(appearOffset)
+ .MoveTo(originalPosition, moveDuration, Easing.InOutSine);
+ }
+
+ theta += (float)hitObject.TimeFadeIn / 1000;
+ break;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
index 1664a37a66..cc2f4c3f70 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Name => "Wiggle";
public override string Acronym => "WG";
- public override IconUsage Icon => FontAwesome.Solid.Certificate;
+ public override IconUsage? Icon => FontAwesome.Solid.Certificate;
public override ModType Type => ModType.Fun;
public override string Description => "They just won't stay still...";
public override double ScoreMultiplier => 1;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
index 6c4fbbac17..921b23cb13 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs
@@ -20,10 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
private const int spacing = 32;
private const double preempt = 800;
+ public override bool RemoveWhenNotAlive => false;
+
///
/// The start time of .
///
- public readonly Bindable StartTime = new Bindable();
+ public readonly Bindable StartTime = new BindableDouble();
///
/// The which s will exit from.
@@ -79,27 +81,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
}
- private void scheduleRefresh() => Scheduler.AddOnce(refresh);
+ private void scheduleRefresh()
+ {
+ Scheduler.AddOnce(refresh);
+ }
private void refresh()
{
ClearInternal();
- if (End == null)
- return;
-
OsuHitObject osuStart = Start.HitObject;
- OsuHitObject osuEnd = End.HitObject;
+ double startTime = osuStart.GetEndTime();
- if (osuEnd.NewCombo)
- return;
+ LifetimeStart = startTime;
- if (osuStart is Spinner || osuEnd is Spinner)
+ OsuHitObject osuEnd = End?.HitObject;
+
+ if (osuEnd == null || osuEnd.NewCombo || osuStart is Spinner || osuEnd is Spinner)
+ {
+ // ensure we always set a lifetime for full LifetimeManagementContainer benefits
+ LifetimeEnd = LifetimeStart;
return;
+ }
Vector2 startPosition = osuStart.EndPosition;
Vector2 endPosition = osuEnd.Position;
- double startTime = osuStart.GetEndTime();
double endTime = osuEnd.StartTime;
Vector2 distanceVector = endPosition - startPosition;
@@ -107,6 +113,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
double duration = endTime - startTime;
+ double? firstTransformStartTime = null;
+ double finalTransformEndTime = startTime;
+
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
{
float fraction = (float)d / distance;
@@ -125,16 +134,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
Scale = new Vector2(1.5f * osuEnd.Scale),
});
+ if (firstTransformStartTime == null)
+ firstTransformStartTime = fadeInTime;
+
using (fp.BeginAbsoluteSequence(fadeInTime))
{
fp.FadeIn(osuEnd.TimeFadeIn);
fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out);
fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out);
fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn);
- }
- fp.Expire(true);
+ finalTransformEndTime = fadeOutTime + osuEnd.TimeFadeIn;
+ }
}
+
+ // todo: use Expire() on FollowPoints and take lifetime from them when https://github.com/ppy/osu-framework/issues/3300 is fixed.
+ LifetimeStart = firstTransformStartTime ?? startTime;
+ LifetimeEnd = finalTransformEndTime;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
index be192080f9..4d73e711bb 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
///
/// Visualises connections between s.
///
- public class FollowPointRenderer : CompositeDrawable
+ public class FollowPointRenderer : LifetimeManagementContainer
{
///
/// All the s contained by this .
@@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
/// The index of in .
private void addConnection(FollowPointConnection connection)
{
- AddInternal(connection);
-
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
@@ -74,6 +72,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
FollowPointConnection previousConnection = connections[index - 1];
previousConnection.End = connection.Start;
}
+
+ AddInternal(connection);
}
///
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 910475ddf5..58e7a3b0a8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
- private readonly IBindable scaleBindable = new Bindable();
+ private readonly IBindable scaleBindable = new BindableFloat();
public OsuAction? HitAction => HitArea.HitAction;
@@ -205,7 +205,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return false;
}
- public bool OnReleased(OsuAction action) => false;
+ public void OnReleased(OsuAction action)
+ {
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 71cb9a9691..8fdcd060e7 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -6,12 +6,11 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
using osuTK;
-using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private double animDuration;
- private readonly SkinnableDrawable scaleContainer;
+ private readonly Drawable scaleContainer;
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
: base(repeatPoint)
@@ -35,19 +34,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Blending = BlendingParameters.Additive;
Origin = Anchor.Centre;
- InternalChild = scaleContainer = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
- {
- RelativeSizeAxes = Axes.Both,
- Icon = FontAwesome.Solid.ChevronRight,
- Size = new Vector2(0.35f)
- }, confineMode: ConfineMode.NoScaling)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- };
+ InternalChild = scaleContainer = new ReverseArrowPiece();
}
- private readonly IBindable scaleBindable = new Bindable();
+ private readonly IBindable scaleBindable = new BindableFloat();
[BackgroundDependencyLoader]
private void load()
@@ -64,11 +54,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdateInitialTransforms()
{
- animDuration = Math.Min(150, repeatPoint.SpanDuration / 2);
+ animDuration = Math.Min(300, repeatPoint.SpanDuration);
this.Animate(
d => d.FadeIn(animDuration),
- d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 4, Easing.OutElasticHalf)
+ d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf)
);
}
@@ -87,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
case ArmedState.Hit:
- this.FadeOut(animDuration, Easing.OutQuint)
+ this.FadeOut(animDuration, Easing.Out)
.ScaleTo(Scale * 1.5f, animDuration, Easing.Out);
break;
}
@@ -98,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public void UpdateSnakingPosition(Vector2 start, Vector2 end)
{
bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0;
- List curve = drawableSlider.Body.CurrentCurve;
+ List curve = ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
Position = isRepeatAtEnd ? end : start;
@@ -120,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- float aimRotation = MathHelper.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
+ float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X));
while (Math.Abs(aimRotation - Rotation) > 180)
aimRotation += aimRotation < Rotation ? 360 : -360;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 69189758a6..7403649184 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -11,7 +11,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics;
@@ -24,8 +23,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child;
- public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
+ public readonly SkinnableDrawable Body;
+
+ private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
private readonly Container headContainer;
private readonly Container tailContainer;
@@ -36,11 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable positionBindable = new Bindable();
private readonly IBindable stackHeightBindable = new Bindable();
- private readonly IBindable scaleBindable = new Bindable();
- private readonly IBindable pathBindable = new Bindable();
-
- [Resolved(CanBeNull = true)]
- private OsuRulesetConfigManager config { get; set; }
+ private readonly IBindable scaleBindable = new BindableFloat();
public DrawableSlider(Slider s)
: base(s)
@@ -51,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
- Body = new SnakingSliderBody(s),
+ Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling),
tickContainer = new Container { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
@@ -70,28 +67,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
- config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn);
- config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
-
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
- scaleBindable.BindValueChanged(scale =>
- {
- updatePathRadius();
- Ball.Scale = new Vector2(scale.NewValue);
- });
+ scaleBindable.BindValueChanged(scale => Ball.Scale = new Vector2(scale.NewValue));
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
- pathBindable.BindTo(slider.PathBindable);
-
- pathBindable.BindValueChanged(_ => Body.Refresh());
AccentColour.BindValueChanged(colour =>
{
- Body.AccentColour = colour.NewValue;
-
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
}, true);
@@ -169,16 +154,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = Math.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
Ball.UpdateProgress(completionProgress);
- Body.UpdateProgress(completionProgress);
+ sliderBody?.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
- if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
+ if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(sliderBody?.SnakedStart ?? 0), slider.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
- Size = Body.Size;
- OriginPosition = Body.PathOffset;
+ Size = sliderBody?.Size ?? Vector2.Zero;
+ OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
if (DrawSize != Vector2.Zero)
{
@@ -192,28 +177,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public override void OnKilled()
{
base.OnKilled();
- Body.RecyclePath();
+ sliderBody?.RecyclePath();
}
- private float sliderPathRadius;
-
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
base.ApplySkin(skin, allowFallback);
- Body.BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
- sliderPathRadius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
- updatePathRadius();
-
- Body.AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
- Body.BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
-
bool allowBallTint = skin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
}
- private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < slider.EndTime)
@@ -264,6 +238,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
- public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
+
+ private class DefaultSliderBody : PlaySliderBody
+ {
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
index a10c66d1df..c5609b01e0 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -4,7 +4,6 @@
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -13,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public class DrawableSliderHead : DrawableHitCircle
{
private readonly IBindable positionBindable = new Bindable();
- private readonly IBindable pathBindable = new Bindable();
+ private readonly IBindable pathVersion = new Bindable();
private readonly Slider slider;
@@ -27,10 +26,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private void load()
{
positionBindable.BindTo(HitObject.PositionBindable);
- pathBindable.BindTo(slider.PathBindable);
+ pathVersion.BindTo(slider.Path.Version);
positionBindable.BindValueChanged(_ => updatePosition());
- pathBindable.BindValueChanged(_ => updatePosition(), true);
+ pathVersion.BindValueChanged(_ => updatePosition(), true);
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index 42bf5e4d21..21a3a0d236 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -3,7 +3,6 @@
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osuTK;
@@ -21,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public bool Tracking { get; set; }
private readonly IBindable positionBindable = new Bindable();
- private readonly IBindable pathBindable = new Bindable();
+ private readonly IBindable pathVersion = new Bindable();
public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle)
: base(hitCircle)
@@ -36,10 +35,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true;
positionBindable.BindTo(hitCircle.PositionBindable);
- pathBindable.BindTo(slider.PathBindable);
+ pathVersion.BindTo(slider.Path.Version);
positionBindable.BindValueChanged(_ => updatePosition());
- pathBindable.BindValueChanged(_ => updatePosition(), true);
+ pathVersion.BindValueChanged(_ => updatePosition(), true);
// TODO: This has no drawable content. Support for skins should be added.
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 9d4d9958a1..60b5c335d6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
};
}
- private readonly IBindable scaleBindable = new Bindable();
+ private readonly IBindable scaleBindable = new BindableFloat();
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 1261d3d19a..de11ab6419 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public readonly SpinnerDisc Disc;
public readonly SpinnerTicks Ticks;
- private readonly SpinnerSpmCounter spmCounter;
+ public readonly SpinnerSpmCounter SpmCounter;
private readonly Container mainContainer;
@@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
}
},
- spmCounter = new SpinnerSpmCounter
+ SpmCounter = new SpinnerSpmCounter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void Update()
{
Disc.Tracking = OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false;
- if (!spmCounter.IsPresent && Disc.Tracking)
- spmCounter.FadeIn(HitObject.TimeFadeIn);
+ if (!SpmCounter.IsPresent && Disc.Tracking)
+ SpmCounter.FadeIn(HitObject.TimeFadeIn);
base.Update();
}
@@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
circle.Rotation = Disc.Rotation;
Ticks.Rotation = Disc.Rotation;
- spmCounter.SetRotation(Disc.RotationAbsolute);
+ SpmCounter.SetRotation(Disc.RotationAbsolute);
float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight;
Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs
new file mode 100644
index 0000000000..c31d6beb01
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DrawableSliderPath.cs
@@ -0,0 +1,70 @@
+// 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.Lines;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public abstract class DrawableSliderPath : SmoothPath
+ {
+ protected const float BORDER_PORTION = 0.128f;
+ protected const float GRADIENT_PORTION = 1 - BORDER_PORTION;
+
+ private const float border_max_size = 8f;
+ private const float border_min_size = 0f;
+
+ private Color4 borderColour = Color4.White;
+
+ public Color4 BorderColour
+ {
+ get => borderColour;
+ set
+ {
+ if (borderColour == value)
+ return;
+
+ borderColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ private Color4 accentColour = Color4.White;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+
+ accentColour = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ private float borderSize = 1;
+
+ public float BorderSize
+ {
+ get => borderSize;
+ set
+ {
+ if (borderSize == value)
+ return;
+
+ if (value < border_min_size || value > border_max_size)
+ return;
+
+ borderSize = value;
+
+ InvalidateTexture();
+ }
+ }
+
+ protected float CalculatedBorderPortion => BorderSize * BORDER_PORTION;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs
new file mode 100644
index 0000000000..cedf2f6e09
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/PlaySliderBody.cs
@@ -0,0 +1,52 @@
+// 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.Drawables;
+using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public abstract class PlaySliderBody : SnakingSliderBody
+ {
+ private IBindable scaleBindable;
+ private IBindable pathVersion;
+ private IBindable accentColour;
+
+ [Resolved]
+ private DrawableHitObject drawableObject { get; set; }
+
+ [Resolved(CanBeNull = true)]
+ private OsuRulesetConfigManager config { get; set; }
+
+ private Slider slider;
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin)
+ {
+ slider = (Slider)drawableObject.HitObject;
+
+ scaleBindable = slider.ScaleBindable.GetBoundCopy();
+ scaleBindable.BindValueChanged(scale => PathRadius = OsuHitObject.OBJECT_RADIUS * scale.NewValue, true);
+
+ pathVersion = slider.Path.Version.GetBoundCopy();
+ pathVersion.BindValueChanged(_ => Refresh());
+
+ accentColour = drawableObject.AccentColour.GetBoundCopy();
+ accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true);
+
+ config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
+ config?.BindWith(OsuRulesetSetting.SnakingOutSliders, SnakingOut);
+
+ BorderSize = skin.GetConfig(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1;
+ BorderColour = skin.GetConfig(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
+ }
+
+ private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour)
+ => AccentColour = skin.GetConfig(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour;
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs
new file mode 100644
index 0000000000..35a27bb0a6
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs
@@ -0,0 +1,43 @@
+// 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.Audio.Track;
+using osu.Framework.Graphics;
+using osuTK;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public class ReverseArrowPiece : BeatSyncedContainer
+ {
+ public ReverseArrowPiece()
+ {
+ Divisor = 2;
+ MinimumBeatLength = 200;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Blending = BlendingParameters.Additive;
+
+ Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
+
+ Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.ReverseArrow), _ => new SpriteIcon
+ {
+ RelativeSizeAxes = Axes.Both,
+ Icon = FontAwesome.Solid.ChevronRight,
+ Size = new Vector2(0.35f)
+ })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) =>
+ Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index ef7b077480..c871089acd 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public Func GetInitialHitAction;
private readonly Slider slider;
- public readonly Drawable FollowCircle;
+ private readonly Drawable followCircle;
private readonly DrawableSlider drawableSlider;
public SliderBall(Slider slider, DrawableSlider drawableSlider = null)
@@ -38,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new[]
{
- FollowCircle = new FollowCircleContainer
+ followCircle = new FollowCircleContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -95,8 +96,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
tracking = value;
- FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint);
- FollowCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
+ followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint);
+ followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint);
}
}
@@ -149,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
// in valid time range
Time.Current >= slider.StartTime && Time.Current < slider.EndTime &&
// in valid position range
- lastScreenSpaceMousePosition.HasValue && FollowCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
+ lastScreenSpaceMousePosition.HasValue && followCircle.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) &&
// valid action
(actions?.Any(isValidTrackingAction) ?? false);
}
@@ -214,9 +215,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public class DefaultSliderBall : CompositeDrawable
{
+ private Box box;
+
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject, ISkinSource skin)
{
+ var slider = (DrawableSlider)drawableObject;
+
RelativeSizeAxes = Axes.Both;
float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
@@ -231,14 +236,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
BorderThickness = 10,
BorderColour = Color4.White,
Alpha = 1,
- Child = new Box
+ Child = box = new Box
{
+ Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
- Alpha = 0.4f,
+ AlwaysPresent = true,
+ Alpha = 0
}
};
+
+ slider.Tracking.BindValueChanged(trackingChanged, true);
}
+
+ private void trackingChanged(ValueChangedEvent tracking) =>
+ box.FadeTo(tracking.NewValue ? 0.6f : 0.05f, 200, Easing.OutQuint);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 24a437c20e..8758a4a066 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Lines;
using osuTK;
@@ -12,9 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public abstract class SliderBody : CompositeDrawable
{
- public const float DEFAULT_BORDER_SIZE = 1;
-
- private SliderPath path;
+ private DrawableSliderPath path;
protected Path Path => path;
@@ -80,19 +79,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
///
- /// Initialises a new , releasing all resources retained by the old one.
+ /// Initialises a new , releasing all resources retained by the old one.
///
public virtual void RecyclePath()
{
- InternalChild = path = new SliderPath
+ InternalChild = path = CreateSliderPath().With(p =>
{
- Position = path?.Position ?? Vector2.Zero,
- PathRadius = path?.PathRadius ?? 10,
- AccentColour = path?.AccentColour ?? Color4.White,
- BorderColour = path?.BorderColour ?? Color4.White,
- BorderSize = path?.BorderSize ?? DEFAULT_BORDER_SIZE,
- Vertices = path?.Vertices ?? Array.Empty()
- };
+ p.Position = path?.Position ?? Vector2.Zero;
+ p.PathRadius = path?.PathRadius ?? 10;
+ p.AccentColour = path?.AccentColour ?? Color4.White;
+ p.BorderColour = path?.BorderColour ?? Color4.White;
+ p.BorderSize = path?.BorderSize ?? 1;
+ p.Vertices = path?.Vertices ?? Array.Empty();
+ });
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos);
@@ -103,77 +102,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
/// The vertices
protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices;
- private class SliderPath : SmoothPath
+ protected virtual DrawableSliderPath CreateSliderPath() => new DefaultDrawableSliderPath();
+
+ private class DefaultDrawableSliderPath : DrawableSliderPath
{
- private const float border_max_size = 8f;
- private const float border_min_size = 0f;
-
- private const float border_portion = 0.128f;
- private const float gradient_portion = 1 - border_portion;
-
private const float opacity_at_centre = 0.3f;
private const float opacity_at_edge = 0.8f;
- private Color4 borderColour = Color4.White;
-
- public Color4 BorderColour
- {
- get => borderColour;
- set
- {
- if (borderColour == value)
- return;
-
- borderColour = value;
-
- InvalidateTexture();
- }
- }
-
- private Color4 accentColour = Color4.White;
-
- public Color4 AccentColour
- {
- get => accentColour;
- set
- {
- if (accentColour == value)
- return;
-
- accentColour = value;
-
- InvalidateTexture();
- }
- }
-
- private float borderSize = DEFAULT_BORDER_SIZE;
-
- public float BorderSize
- {
- get => borderSize;
- set
- {
- if (borderSize == value)
- return;
-
- if (value < border_min_size || value > border_max_size)
- return;
-
- borderSize = value;
-
- InvalidateTexture();
- }
- }
-
- private float calculatedBorderPortion => BorderSize * border_portion;
-
protected override Color4 ColourAt(float position)
{
- if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion)
+ if (CalculatedBorderPortion != 0f && position <= CalculatedBorderPortion)
return BorderColour;
- position -= calculatedBorderPortion;
- return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A);
+ position -= CalculatedBorderPortion;
+ return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / GRADIENT_PORTION) * AccentColour.A);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
index f2150280b3..e24fa865ad 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
@@ -50,16 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
///
private Vector2 snakedPathOffset;
- private readonly Slider slider;
-
- public SnakingSliderBody(Slider slider)
- {
- this.slider = slider;
- }
+ private Slider slider;
[BackgroundDependencyLoader]
- private void load()
+ private void load(DrawableHitObject drawableObject)
{
+ slider = (Slider)drawableObject.HitObject;
+
Refresh();
}
@@ -127,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private void setRange(double p0, double p1)
{
if (p0 > p1)
- MathHelper.Swap(ref p0, ref p1);
+ (p0, p1) = (p1, p0);
if (SnakedStart == p0 && SnakedEnd == p1) return;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
index c45e98cc76..e3dd2b1b4f 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs
@@ -8,6 +8,7 @@ using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
+using osu.Framework.Utils;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
base.Update();
- var thisAngle = -(float)MathHelper.RadiansToDegrees(Math.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
+ var thisAngle = -MathUtils.RadiansToDegrees(MathF.Atan2(mousePosition.X - DrawSize.X / 2, mousePosition.Y - DrawSize.Y / 2));
bool validAndTracking = tracking && spinner.StartTime <= Time.Current && spinner.EndTime > Time.Current;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
index b1d90c49f6..80ab03c45c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -62,6 +63,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void SetRotation(float currentRotation)
{
+ // Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
+ if (Precision.AlmostEquals(0, Time.Elapsed))
+ return;
+
// If we've gone back in time, it's fine to work with a fresh set of records for now
if (records.Count > 0 && Time.Current < records.Last().Time)
records.Clear();
@@ -71,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
var record = records.Peek();
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
record = records.Dequeue();
+
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 0ba712a83f..15af141c99 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double Radius => OBJECT_RADIUS * Scale;
- public readonly Bindable ScaleBindable = new Bindable(1);
+ public readonly Bindable ScaleBindable = new BindableFloat(1);
public float Scale
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index c6f5a075e0..77f8ec6cc8 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
-using osu.Framework.Bindables;
using osu.Framework.Caching;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -19,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class Slider : OsuHitObject, IHasCurve
{
- public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ public double EndTime
+ {
+ get => StartTime + this.SpanCount() * Path.Distance / Velocity;
+ set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
+ }
+
public double Duration => EndTime - StartTime;
private readonly Cached endPositionCache = new Cached();
@@ -28,17 +32,21 @@ namespace osu.Game.Rulesets.Osu.Objects
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
- public readonly Bindable PathBindable = new Bindable();
+ private readonly SliderPath path = new SliderPath();
public SliderPath Path
{
- get => PathBindable.Value;
+ get => path;
set
{
- PathBindable.Value = value;
- endPositionCache.Invalidate();
+ path.ControlPoints.Clear();
+ path.ExpectedDistance.Value = null;
- updateNestedPositions();
+ if (value != null)
+ {
+ path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
+ path.ExpectedDistance.Value = value.ExpectedDistance.Value;
+ }
}
}
@@ -50,8 +58,6 @@ namespace osu.Game.Rulesets.Osu.Objects
set
{
base.Position = value;
- endPositionCache.Invalidate();
-
updateNestedPositions();
}
}
@@ -80,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects
set
{
repeatCount = value;
- endPositionCache.Invalidate();
+ updateNestedPositions();
}
}
@@ -110,8 +116,8 @@ namespace osu.Game.Rulesets.Osu.Objects
public Slider()
{
- SamplesBindable.ItemsAdded += _ => updateNestedSamples();
- SamplesBindable.ItemsRemoved += _ => updateNestedSamples();
+ SamplesBindable.CollectionChanged += (_, __) => updateNestedSamples();
+ Path.Version.ValueChanged += _ => updateNestedPositions();
}
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@@ -189,6 +195,8 @@ namespace osu.Game.Rulesets.Osu.Objects
private void updateNestedPositions()
{
+ endPositionCache.Invalidate();
+
if (HeadCircle != null)
HeadCircle.Position = Position;
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
index 14c3369967..127c36fcc0 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
@@ -15,15 +14,15 @@ namespace osu.Game.Rulesets.Osu.Objects
///
public class SliderTailCircle : SliderCircle
{
- private readonly IBindable pathBindable = new Bindable();
+ private readonly IBindable pathVersion = new Bindable();
public SliderTailCircle(Slider slider)
{
- pathBindable.BindTo(slider.PathBindable);
- pathBindable.BindValueChanged(_ => Position = slider.EndPosition);
+ pathVersion.BindTo(slider.Path.Version);
+ pathVersion.BindValueChanged(_ => Position = slider.EndPosition);
}
- public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 2441a1449d..0b8d03d118 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -13,8 +13,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class Spinner : OsuHitObject, IHasEndTime
{
- public double EndTime { get; set; }
- public double Duration => EndTime - StartTime;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
+
+ public double Duration { get; set; }
///
/// Number of spins required to finish the spinner without miss.
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index fa69cec78d..148869f5e8 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -23,16 +23,23 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Difficulty;
+using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Skinning;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
+using System;
namespace osu.Game.Rulesets.Osu
{
- public class OsuRuleset : Ruleset
+ public class OsuRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
+
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
public const string SHORT_NAME = "osu";
@@ -60,7 +67,9 @@ namespace osu.Game.Rulesets.Osu
if (mods.HasFlag(LegacyMods.Autopilot))
yield return new OsuModAutopilot();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new OsuModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new OsuModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -121,12 +130,13 @@ namespace osu.Game.Rulesets.Osu
return new Mod[]
{
new OsuModTarget(),
+ new OsuModDifficultyAdjust(),
};
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new OsuModAutoplay(), new ModCinema()),
+ new MultiMod(new OsuModAutoplay(), new OsuModCinema()),
new OsuModRelax(),
new OsuModAutopilot(),
};
@@ -149,7 +159,7 @@ namespace osu.Game.Rulesets.Osu
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -165,19 +175,16 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Clicking circles";
+
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
- public override int? LegacyID => 0;
+ public int LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
-
- public OsuRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
index 8dd48eace0..4ea4220faf 100644
--- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
+++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs
@@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu
ReverseArrow,
HitCircleText,
SliderFollowCircle,
- SliderBall
+ SliderBall,
+ SliderBody,
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index bd59e8a03f..4cb2cd6539 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osuTK;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
@@ -156,9 +156,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner)
{
- calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
+ calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
- Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
+ Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
if (spinCentreOffset.Length > SPIN_RADIUS)
{
@@ -230,7 +230,7 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{
- OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
+ OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[^1];
// Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
@@ -363,7 +363,7 @@ namespace osu.Game.Rulesets.Osu.Replays
}
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
- if (Frames[Frames.Count - 1].Time <= endFrame.Time)
+ if (Frames[^1].Time <= endFrame.Time)
AddFrameToReplay(endFrame);
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
index c6ac1dd346..b42e9ac187 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs
@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Input.StateChanges;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
using osuTK;
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index affe18a30d..1de7d488f3 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -1,56 +1,15 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Scoring
{
- internal class OsuScoreProcessor : ScoreProcessor
+ internal class OsuScoreProcessor : ScoreProcessor
{
- public OsuScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- private float hpDrainRate;
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- {
- switch (result.Type)
- {
- case HitResult.Great:
- return 10.2 - hpDrainRate;
-
- case HitResult.Good:
- return 8 - hpDrainRate;
-
- case HitResult.Meh:
- return 4 - hpDrainRate;
-
- // case HitResult.SliderTick:
- // return Math.Max(7 - hpDrainRate, 0) * 0.01;
-
- case HitResult.Miss:
- return hpDrainRate;
-
- default:
- return 0;
- }
- }
-
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows();
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
index 470ba3acae..e96bd29ad5 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursor.cs
@@ -3,14 +3,16 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
+using osu.Game.Rulesets.Osu.UI.Cursor;
using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning
{
- public class LegacyCursor : CompositeDrawable
+ public class LegacyCursor : OsuCursorSprite
{
+ private bool spin;
+
public LegacyCursor()
{
Size = new Vector2(50);
@@ -22,21 +24,29 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
- InternalChildren = new Drawable[]
+ spin = skin.GetConfig(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
+
+ InternalChildren = new[]
{
+ ExpandTarget = new NonPlayfieldSprite
+ {
+ Texture = skin.GetTexture("cursor"),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
new NonPlayfieldSprite
{
Texture = skin.GetTexture("cursormiddle"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- new NonPlayfieldSprite
- {
- Texture = skin.GetTexture("cursor"),
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- }
};
}
+
+ protected override void LoadComplete()
+ {
+ if (spin)
+ ExpandTarget.Spin(10000, RotationDirection.Clockwise);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
new file mode 100644
index 0000000000..21df49d80b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
@@ -0,0 +1,56 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Skinning
+{
+ public class LegacySliderBody : PlaySliderBody
+ {
+ protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
+
+ private class LegacyDrawableSliderPath : DrawableSliderPath
+ {
+ private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
+
+ public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
+
+ protected override Color4 ColourAt(float position)
+ {
+ float realBorderPortion = shadow_portion + CalculatedBorderPortion;
+ float realGradientPortion = 1 - realBorderPortion;
+
+ if (position <= shadow_portion)
+ return new Color4(0f, 0f, 0f, 0.25f * position / shadow_portion);
+
+ if (position <= realBorderPortion)
+ return BorderColour;
+
+ position -= realBorderPortion;
+
+ Color4 outerColour = AccentColour.Darken(0.1f);
+ Color4 innerColour = lighten(AccentColour, 0.5f);
+
+ return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
+ }
+
+ ///
+ /// Lightens a colour in a way more friendly to dark or strong colours.
+ ///
+ private static Color4 lighten(Color4 color, float amount)
+ {
+ amount *= 0.5f;
+ return new Color4(
+ Math.Min(1, color.R * (1 + 0.5f * amount) + 1 * amount),
+ Math.Min(1, color.G * (1 + 0.5f * amount) + 1 * amount),
+ Math.Min(1, color.B * (1 + 0.5f * amount) + 1 * amount),
+ color.A);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
index 3e41dc08d7..475e643755 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
/// Their hittable area is 128px, but the actual circle portion is 118px.
/// We must account for some gameplay elements such as slider bodies, where this padding is not present.
///
- private const float legacy_circle_radius = 64 - 5;
+ public const float LEGACY_CIRCLE_RADIUS = 64 - 5;
public OsuLegacySkinTransformer(ISkinSource source)
{
@@ -47,13 +47,20 @@ namespace osu.Game.Rulesets.Osu.Skinning
switch (osuComponent.Component)
{
case OsuSkinComponents.FollowPoint:
- return this.GetAnimation(component.LookupName, true, false);
+ return this.GetAnimation(component.LookupName, true, false, true);
case OsuSkinComponents.SliderFollowCircle:
- return this.GetAnimation("sliderfollowcircle", true, true);
+ var followCircle = this.GetAnimation("sliderfollowcircle", true, true, true);
+ if (followCircle != null)
+ // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x
+ followCircle.Scale *= 0.5f;
+ return followCircle;
case OsuSkinComponents.SliderBall:
- var sliderBallContent = this.GetAnimation("sliderb", true, true, "");
+ var sliderBallContent = this.GetAnimation("sliderb", true, true, animationSeparator: "");
+
+ // todo: slider ball has a custom frame delay based on velocity
+ // Math.Max((150 / Velocity) * GameBase.SIXTY_FRAME_TIME, GameBase.SIXTY_FRAME_TIME);
if (sliderBallContent != null)
{
@@ -70,6 +77,12 @@ namespace osu.Game.Rulesets.Osu.Skinning
return null;
+ case OsuSkinComponents.SliderBody:
+ if (hasHitCircle.Value)
+ return new LegacySliderBody();
+
+ return null;
+
case OsuSkinComponents.HitCircle:
if (hasHitCircle.Value)
return new LegacyMainCirclePiece();
@@ -121,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
case OsuSkinConfiguration.SliderPathRadius:
if (hasHitCircle.Value)
- return SkinUtils.As(new BindableFloat(legacy_circle_radius));
+ return SkinUtils.As(new BindableFloat(LEGACY_CIRCLE_RADIUS));
break;
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 85a7f5b0cd..48ac3f965e 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -12,5 +12,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
SliderPathRadius,
AllowSliderBallTint,
CursorExpand,
+ CursorRotate
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 80291c002e..4e86662ec6 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void addPart(Vector2 screenSpacePosition)
{
parts[currentIndex].Position = screenSpacePosition;
- parts[currentIndex].Time = time;
+ parts[currentIndex].Time = time + 1;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % max_sprites;
@@ -201,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
- private readonly TrailBatch vertexBatch = new TrailBatch(max_sprites, 1);
+ private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
public TrailDrawNode(CursorTrail source)
: base(source)
@@ -227,23 +227,52 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
- for (int i = 0; i < parts.Length; ++i)
+ texture.TextureGL.Bind();
+
+ RectangleF textureRect = texture.GetTextureRect();
+
+ foreach (var part in parts)
{
- if (parts[i].InvalidationID == -1)
+ if (part.InvalidationID == -1)
continue;
- vertexBatch.DrawTime = parts[i].Time;
+ if (time - part.Time >= 1)
+ continue;
- Vector2 pos = parts[i].Position;
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomLeft,
+ Colour = DrawColourInfo.Colour.BottomLeft.Linear,
+ Time = part.Time
+ });
- DrawQuad(
- texture,
- new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y),
- DrawColourInfo.Colour,
- null,
- vertexBatch.AddAction);
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
+ TexturePosition = textureRect.BottomRight,
+ Colour = DrawColourInfo.Colour.BottomRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopRight,
+ Colour = DrawColourInfo.Colour.TopRight.Linear,
+ Time = part.Time
+ });
+
+ vertexBatch.Add(new TexturedTrailVertex
+ {
+ Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
+ TexturePosition = textureRect.TopLeft,
+ Colour = DrawColourInfo.Colour.TopLeft.Linear,
+ Time = part.Time
+ });
}
+ vertexBatch.Draw();
shader.Unbind();
}
@@ -253,25 +282,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Dispose();
}
-
- // Todo: This shouldn't exist, but is currently used to reduce allocations by caching variable-capturing closures.
- private class TrailBatch : QuadBatch
- {
- public new readonly Action AddAction;
- public float DrawTime;
-
- public TrailBatch(int size, int maxBuffers)
- : base(size, maxBuffers)
- {
- AddAction = v => Add(new TexturedTrailVertex
- {
- Position = v.Position,
- TexturePosition = v.TexturePosition,
- Time = DrawTime + 1,
- Colour = v.Colour,
- });
- }
- }
}
[StructLayout(LayoutKind.Sequential)]
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
index 0aa8661fd3..4f3d07f208 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs
@@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private bool cursorExpand;
- private Container expandTarget;
+ private SkinnableDrawable cursorSprite;
+
+ private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite;
public OsuCursor()
{
@@ -37,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[BackgroundDependencyLoader]
private void load()
{
- InternalChild = expandTarget = new Container
+ InternalChild = new Container
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
- Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
+ Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
- private class DefaultCursor : CompositeDrawable
+ private class DefaultCursor : OsuCursorSprite
{
public DefaultCursor()
{
@@ -71,10 +73,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
- new CircularContainer
+ ExpandTarget = new CircularContainer
{
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = size / 6,
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 6433ced624..28600ef55b 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osuTK;
@@ -29,10 +30,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Drawable cursorTrail;
- public Bindable CursorScale;
+ public Bindable CursorScale = new BindableFloat(1);
+
private Bindable userCursorScale;
private Bindable autoCursorScale;
- private readonly IBindable beatmap = new Bindable();
public OsuCursorContainer()
{
@@ -43,37 +44,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
};
}
+ [Resolved(canBeNull: true)]
+ private GameplayBeatmap beatmap { get; set; }
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
[BackgroundDependencyLoader(true)]
- private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig, IBindable beatmap)
+ private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig)
{
rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
-
- this.beatmap.BindTo(beatmap);
- this.beatmap.ValueChanged += _ => calculateScale();
-
- userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
- userCursorScale.ValueChanged += _ => calculateScale();
-
- autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
- autoCursorScale.ValueChanged += _ => calculateScale();
-
- CursorScale = new Bindable();
- CursorScale.ValueChanged += e => ActiveCursor.Scale = cursorTrail.Scale = new Vector2(e.NewValue);
-
- calculateScale();
- }
-
- private void calculateScale()
- {
- float scale = userCursorScale.Value;
-
- if (autoCursorScale.Value && beatmap.Value != null)
- {
- // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= 1f - 0.7f * (1f + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
- }
-
- CursorScale.Value = scale;
}
protected override void LoadComplete()
@@ -81,6 +61,46 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
base.LoadComplete();
showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true);
+
+ userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ userCursorScale.ValueChanged += _ => calculateScale();
+
+ autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
+ autoCursorScale.ValueChanged += _ => calculateScale();
+
+ CursorScale.ValueChanged += e =>
+ {
+ var newScale = new Vector2(e.NewValue);
+
+ ActiveCursor.Scale = newScale;
+ cursorTrail.Scale = newScale;
+ };
+
+ calculateScale();
+ }
+
+ ///
+ /// Get the scale applicable to the ActiveCursor based on a beatmap's circle size.
+ ///
+ public static float GetScaleForCircleSize(float circleSize) =>
+ 1f - 0.7f * (1f + circleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY;
+
+ private void calculateScale()
+ {
+ float scale = userCursorScale.Value;
+
+ if (autoCursorScale.Value && beatmap != null)
+ {
+ // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
+ scale *= GetScaleForCircleSize(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+ }
+
+ CursorScale.Value = scale;
+
+ var newScale = new Vector2(scale);
+
+ ActiveCursor.ScaleTo(newScale, 400, Easing.OutQuint);
+ cursorTrail.Scale = newScale;
}
private int downCount;
@@ -107,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
return false;
}
- public bool OnReleased(OsuAction action)
+ public void OnReleased(OsuAction action)
{
switch (action)
{
@@ -120,8 +140,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
updateExpandedState();
break;
}
-
- return false;
}
public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input.
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.cs
new file mode 100644
index 0000000000..573c408a78
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorSprite.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.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+
+namespace osu.Game.Rulesets.Osu.UI.Cursor
+{
+ public abstract class OsuCursorSprite : CompositeDrawable
+ {
+ ///
+ /// The an optional piece of the cursor to expand when in a clicked state.
+ /// If null, the whole cursor will be affected by expansion.
+ ///
+ public Drawable ExpandTarget { get; protected set; }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
index 49aea52902..a37ef8d9a0 100644
--- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs
@@ -14,8 +14,6 @@ using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
-using osu.Game.Rulesets.Osu.Scoring;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osuTK;
@@ -26,15 +24,13 @@ namespace osu.Game.Rulesets.Osu.UI
{
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
- public DrawableOsuRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // always show the gameplay cursor
- public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this);
-
protected override Playfield CreatePlayfield() => new OsuPlayfield();
protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
index 3b18e41f30..abba444c73 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI
{
Add(localCursorContainer = new OsuCursorContainer());
- localCursorScale = new Bindable();
+ localCursorScale = new BindableFloat();
localCursorScale.BindTo(localCursorContainer.CursorScale);
localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true);
}
@@ -107,7 +107,9 @@ namespace osu.Game.Rulesets.Osu.UI
return false;
}
- public bool OnReleased(OsuAction action) => false;
+ public void OnReleased(OsuAction action)
+ {
+ }
public void Appear() => Schedule(() =>
{
diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
index 330cb42901..73faf16d9f 100644
--- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
+++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Rulesets.Taiko.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json
index 7d929e6bbf..5b02ecfc91 100644
--- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json
+++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
+ "${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
+ "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 28f5d4d301..f23fd6d3f9 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -4,7 +4,7 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Tests.Beatmaps;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
index f27e329e8e..ccacc50de1 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs
@@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
@@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
public void TestZeroTickTimeOffsets()
{
AddUntilStep("gameplay finished", () => Player.ScoreProcessor.HasCompleted);
- AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.Judgement is TaikoSwellTickJudgement).All(r => r.TimeOffset == 0));
+ AddAssert("all tick offsets are 0", () => Player.Results.Where(r => r.HitObject is SwellTick).All(r => r.TimeOffset == 0));
}
protected override bool Autoplay => true;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
index 8522a42739..c01eef5252 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs
@@ -7,11 +7,10 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
@@ -91,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 768,
- Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) }
+ Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
});
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
index d0db193738..140433a523 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
- Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
return new ScoreAccessiblePlayer();
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index b5bd384e05..f6054a5d6f 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,14 +2,14 @@
-
+
WinExe
- netcoreapp3.0
+ netcoreapp3.1
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
index aaf113f216..c31b07344d 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
@@ -26,10 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Audio
var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
- // todo: this is ugly
- centre.Namespace = "taiko";
- rim.Namespace = "taiko";
-
mappings[s.Time] = new DrumSample
{
Centre = addSound(centre),
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 10cc861b7e..cc9d6e4470 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -39,14 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset;
- protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(HitObject) };
-
- public TaikoBeatmapConverter(IBeatmap beatmap)
- : base(beatmap)
+ public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
{
- isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo);
+ isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
}
+ public override bool CanConvert() => true;
+
protected override Beatmap ConvertBeatmap(IBeatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
new file mode 100644
index 0000000000..71aa007d3b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs
@@ -0,0 +1,21 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Rulesets.Taiko.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModCinema : ModCinema
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap) => new Score
+ {
+ ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } },
+ Replay = new TaikoAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
new file mode 100644
index 0000000000..56a73ad7df
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Mods
+{
+ public class TaikoModDifficultyAdjust : ModDifficultyAdjust
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
index e45081b6d6..5377eb1072 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs
@@ -2,10 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
- public class TaikoModNightcore : ModNightcore
+ public class TaikoModNightcore : ModNightcore
{
public override double ScoreMultiplier => 1.12;
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
index 2afbbc737c..6306195704 100644
--- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.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 osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Objects
@@ -8,5 +9,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
public class BarLine : TaikoHitObject, IBarLine
{
public bool Major { get; set; }
+
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index 1a5a797f28..e9caabbcc8 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -54,5 +54,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Alpha = 0.75f
});
}
+
+ protected override void UpdateStateTransforms(ArmedState state) => this.FadeOut(150);
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 338fd9e20f..5806c90115 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -4,7 +4,7 @@
using System;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 4b25ff0ecc..85dfc8d5e0 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -77,11 +77,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return result;
}
- public override bool OnReleased(TaikoAction action)
+ public override void OnReleased(TaikoAction action)
{
if (action == HitAction)
HitAction = null;
- return base.OnReleased(action);
+
+ base.OnReleased(action);
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 0db6498c12..5f892dd2fa 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -77,7 +77,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
public abstract bool OnPressed(TaikoAction action);
- public virtual bool OnReleased(TaikoAction action) => false;
+
+ public virtual void OnReleased(TaikoAction action)
+ {
+ }
public override double LifetimeStart
{
@@ -105,19 +108,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
- public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
- where TaikoHitType : TaikoHitObject
+ public abstract class DrawableTaikoHitObject : DrawableTaikoHitObject
+ where TTaikoHit : TaikoHitObject
{
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
- public new TaikoHitType HitObject;
+ public new TTaikoHit HitObject;
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece;
private readonly Container strongHitContainer;
- protected DrawableTaikoHitObject(TaikoHitType hitObject)
+ protected DrawableTaikoHitObject(TTaikoHit hitObject)
: base(hitObject)
{
HitObject = hitObject;
@@ -166,8 +169,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum
protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
- protected override string SampleNamespace => "taiko";
-
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
///
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index 8956ca9c19..aacd78f176 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
///
private const float base_distance = 100;
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
index e60984596d..2f06066a16 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs
@@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class Swell : TaikoHitObject, IHasEndTime
{
- public double EndTime => StartTime + Duration;
+ public double EndTime
+ {
+ get => StartTime + Duration;
+ set => Duration = value - StartTime;
+ }
public double Duration { get; set; }
diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
index 91f4d3dbe7..bdc0478195 100644
--- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs
@@ -3,13 +3,12 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects
{
public class SwellTick : TaikoHitObject
{
- public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
+ public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
index 6f4fbd0651..c41727557b 100644
--- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs
@@ -3,7 +3,6 @@
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Scoring;
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.CreateNestedHitObjects();
if (IsStrong)
- AddNested(new StrongHitObject { StartTime = (this as IHasEndTime)?.EndTime ?? StartTime });
+ AddNested(new StrongHitObject { StartTime = this.GetEndTime() });
}
public override Judgement CreateJudgement() => new TaikoJudgement();
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index e61953aeb8..48eb33976e 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Replays;
-using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Beatmaps;
@@ -39,9 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
TaikoHitObject h = Beatmap.HitObjects[i];
-
- IHasEndTime endTimeData = h as IHasEndTime;
- double endTime = endTimeData?.EndTime ?? h.StartTime;
+ double endTime = h.GetEndTime();
switch (h)
{
@@ -124,41 +121,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
-
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
-
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
- if (i < Beatmap.HitObjects.Count - 1)
- {
- double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
- if (waitTime > endTime)
- Frames.Add(new TaikoReplayFrame(waitTime));
- }
-
hitButton = !hitButton;
}
return Replay;
}
-
- protected override HitObject GetNextObject(int currentIndex)
- {
- Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
-
- for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
- {
- var currentObj = Beatmap.HitObjects[i];
-
- if (currentObj.GetType() == desiredType ||
- // Un-press all keys before a DrumRoll or Swell
- currentObj is DrumRoll || currentObj is Swell)
- {
- return Beatmap.HitObjects[i];
- }
- }
-
- return null;
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav
new file mode 100755
index 0000000000..9ea2be5855
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav
new file mode 100755
index 0000000000..af270ae12a
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav
new file mode 100755
index 0000000000..3d8024c6ae
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav
new file mode 100755
index 0000000000..16d254cc87
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav
new file mode 100755
index 0000000000..b4cfa26265
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav
new file mode 100755
index 0000000000..97804a5a61
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav
new file mode 100755
index 0000000000..67f02877a8
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav
new file mode 100755
index 0000000000..10206cd228
Binary files /dev/null and b/osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav differ
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
new file mode 100644
index 0000000000..edb089dbac
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
@@ -0,0 +1,49 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko.Objects;
+
+namespace osu.Game.Rulesets.Taiko.Scoring
+{
+ ///
+ /// A for the taiko ruleset.
+ /// Taiko fails if the player has not half-filled their health by the end of the map.
+ ///
+ public class TaikoHealthProcessor : AccumulatingHealthProcessor
+ {
+ ///
+ /// A value used for calculating .
+ ///
+ private const double object_count_factor = 3;
+
+ ///
+ /// HP multiplier for a successful .
+ ///
+ private double hpMultiplier;
+
+ ///
+ /// HP multiplier for a .
+ ///
+ private double hpMissMultiplier;
+
+ public TaikoHealthProcessor()
+ : base(0.5)
+ {
+ }
+
+ public override void ApplyBeatmap(IBeatmap beatmap)
+ {
+ base.ApplyBeatmap(beatmap);
+
+ hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
+ hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
+ }
+
+ protected override double GetHealthIncreaseFor(JudgementResult result)
+ => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier);
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
index 75a27ff639..003d40af56 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
@@ -1,60 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Game.Beatmaps;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Objects;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko.Scoring
{
- internal class TaikoScoreProcessor : ScoreProcessor
+ internal class TaikoScoreProcessor : ScoreProcessor
{
- ///
- /// A value used for calculating .
- ///
- private const double object_count_factor = 3;
-
- ///
- /// Taiko fails at the end of the map if the player has not half-filled their HP bar.
- ///
- protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
-
- ///
- /// HP multiplier for a successful .
- ///
- private double hpMultiplier;
-
- ///
- /// HP multiplier for a .
- ///
- private double hpMissMultiplier;
-
- public TaikoScoreProcessor(DrawableRuleset drawableRuleset)
- : base(drawableRuleset)
- {
- }
-
- protected override void ApplyBeatmap(Beatmap beatmap)
- {
- base.ApplyBeatmap(beatmap);
-
- hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
-
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
- }
-
- protected override double HealthAdjustmentFactorFor(JudgementResult result)
- => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
-
- protected override void Reset(bool storeResults)
- {
- base.Reset(storeResults);
-
- Health.Value = 0;
- }
-
public override HitWindows CreateHitWindows() => new TaikoHitWindows();
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
new file mode 100644
index 0000000000..381cd14cd4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -0,0 +1,55 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Taiko.Skinning
+{
+ public class TaikoLegacySkinTransformer : ISkin
+ {
+ private readonly ISkinSource source;
+
+ public TaikoLegacySkinTransformer(ISkinSource source)
+ {
+ this.source = source;
+ }
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component);
+
+ public Texture GetTexture(string componentName) => source.GetTexture(componentName);
+
+ public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
+
+ public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup);
+
+ private class LegacyTaikoSampleInfo : ISampleInfo
+ {
+ private readonly ISampleInfo source;
+
+ public LegacyTaikoSampleInfo(ISampleInfo source)
+ {
+ this.source = source;
+ }
+
+ public IEnumerable LookupNames
+ {
+ get
+ {
+ foreach (var name in source.LookupNames)
+ yield return $"taiko-{name}";
+
+ foreach (var name in source.LookupNames)
+ yield return name;
+ }
+ }
+
+ public int Volume => source.Volume;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index b2655f592c..fc79e59864 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -15,16 +15,28 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Difficulty;
+using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
+using System;
+using osu.Game.Rulesets.Taiko.Skinning;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko
{
- public class TaikoRuleset : Ruleset
+ public class TaikoRuleset : Ruleset, ILegacyRuleset
{
- public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods);
- public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
+
+ public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();
+
+ public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
+
+ public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source);
public const string SHORT_NAME = "taiko";
@@ -50,7 +62,9 @@ namespace osu.Game.Rulesets.Taiko
else if (mods.HasFlag(LegacyMods.SuddenDeath))
yield return new TaikoModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Autoplay))
+ if (mods.HasFlag(LegacyMods.Cinema))
+ yield return new TaikoModCinema();
+ else if (mods.HasFlag(LegacyMods.Autoplay))
yield return new TaikoModAutoplay();
if (mods.HasFlag(LegacyMods.Easy))
@@ -97,10 +111,16 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(),
};
+ case ModType.Conversion:
+ return new Mod[]
+ {
+ new TaikoModDifficultyAdjust(),
+ };
+
case ModType.Automation:
return new Mod[]
{
- new MultiMod(new TaikoModAutoplay(), new ModCinema()),
+ new MultiMod(new TaikoModAutoplay(), new TaikoModCinema()),
new TaikoModRelax(),
};
@@ -111,7 +131,7 @@ namespace osu.Game.Rulesets.Taiko
};
default:
- return new Mod[] { };
+ return Array.Empty();
}
}
@@ -119,19 +139,16 @@ namespace osu.Game.Rulesets.Taiko
public override string ShortName => SHORT_NAME;
+ public override string PlayingVerb => "Bashing drums";
+
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
- public override int? LegacyID => 1;
+ public int LegacyID => 1;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
-
- public TaikoRuleset(RulesetInfo rulesetInfo = null)
- : base(rulesetInfo)
- {
- }
}
}
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
index fc109bf6a6..0c7495aa52 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
@@ -5,10 +5,8 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
-using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Framework.Input;
@@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override bool UserScrollSpeedAdjustment => false;
- public DrawableTaikoRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList mods)
+ public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Left;
@@ -40,8 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI
new BarLineGenerator(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar.Major ? new DrawableBarLineMajor(bar) : new DrawableBarLine(bar)));
}
- public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
-
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer();
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 5234ae1f69..d26ccfe867 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -187,7 +187,9 @@ namespace osu.Game.Rulesets.Taiko.UI
return false;
}
- public bool OnReleased(TaikoAction action) => false;
+ public void OnReleased(TaikoAction action)
+ {
+ }
}
}
}
diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs
index d96a3e27a4..9533b90131 100644
--- a/osu.Game.Tests.iOS/Application.cs
+++ b/osu.Game.Tests.iOS/Application.cs
@@ -5,7 +5,7 @@ using UIKit;
namespace osu.Game.Tests.iOS
{
- public class Application
+ public static class Application
{
public static void Main(string[] args)
{
diff --git a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
index 98e630abd2..12d729d09f 100644
--- a/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/EditorBeatmapTest.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestHitObjectAddEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h;
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps
public void HitObjectRemoveEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h;
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestInitialHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddedHitObjectStartTimeChangeEvent()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestAddHitObjectInMiddle()
{
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestResortWhenStartTimeChanged()
{
var hitCircle = new HitCircle { StartTime = 1000 };
- var editorBeatmap = new EditorBeatmap(new OsuBeatmap
+ var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{
HitObjects =
{
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 26e70f19e4..33f484a9aa 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
+using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@@ -313,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new OsuBeatmapConverter(beatmap).Convert();
+ var converted = new OsuBeatmapConverter(beatmap, new OsuRuleset()).Convert();
new OsuBeatmapProcessor(converted).PreProcess();
new OsuBeatmapProcessor(converted).PostProcess();
@@ -336,7 +337,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decoder.Decode(stream);
- var converted = new CatchBeatmapConverter(beatmap).Convert();
+ var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert();
new CatchBeatmapProcessor(converted).PreProcess();
new CatchBeatmapProcessor(converted).PostProcess();
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
new file mode 100644
index 0000000000..f2b3a16f68
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.IO;
+using osu.Game.IO.Serialization;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Beatmaps.Formats
+{
+ [TestFixture]
+ public class LegacyBeatmapEncoderTest
+ {
+ private const string normal = "Soleily - Renatus (Gamu) [Insane].osu";
+
+ private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
+
+ [TestCaseSource(nameof(allBeatmaps))]
+ public void TestDecodeEncodedBeatmap(string name)
+ {
+ var decoded = decode(normal, out var encoded);
+
+ Assert.That(decoded.HitObjects.Count, Is.EqualTo(encoded.HitObjects.Count));
+ Assert.That(encoded.Serialize(), Is.EqualTo(decoded.Serialize()));
+ }
+
+ private Beatmap decode(string filename, out Beatmap encoded)
+ {
+ using (var stream = TestResources.OpenResource(filename))
+ using (var sr = new LineBufferedReader(stream))
+ {
+ var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
+
+ using (var ms = new MemoryStream())
+ using (var sw = new StreamWriter(ms))
+ using (var sr2 = new LineBufferedReader(ms))
+ {
+ new LegacyBeatmapEncoder(legacyDecoded).Encode(sw);
+ sw.Flush();
+
+ ms.Position = 0;
+
+ encoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr2);
+ return legacyDecoded;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 66d53d7e7b..96ff6b81e3 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
- Assert.AreEqual("SB/black.jpg", sprite.Path);
+ Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
var animation = background.Elements.OfType().First();
Assert.NotNull(animation);
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 4766411cbd..c1bd73ef05 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
@@ -13,7 +14,9 @@ using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Resources;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
@@ -552,6 +555,83 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [Test]
+ public async Task TestUpdateBeatmapInfo()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ var temp = TestResources.GetTestBeatmapForImport();
+ await osu.Dependencies.Get().Import(temp);
+
+ // Update via the beatmap, not the beatmap info, to ensure correct linking
+ BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
+ Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
+ beatmapToUpdate.BeatmapInfo.Version = "updated";
+
+ manager.Update(setToUpdate);
+
+ BeatmapInfo updatedInfo = manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID);
+ Assert.That(updatedInfo.Version, Is.EqualTo("updated"));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public async Task TestUpdateBeatmapFile()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile)))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ var temp = TestResources.GetTestBeatmapForImport();
+ await osu.Dependencies.Get().Import(temp);
+
+ BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
+ Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap;
+ BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename));
+
+ using (var stream = new MemoryStream())
+ {
+ using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
+ {
+ beatmapToUpdate.HitObjects.Clear();
+ beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 });
+
+ new LegacyBeatmapEncoder(beatmapToUpdate).Encode(writer);
+ }
+
+ stream.Seek(0, SeekOrigin.Begin);
+
+ manager.UpdateFile(setToUpdate, fileToUpdate, stream);
+ }
+
+ // Check that the old file reference has been removed
+ Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID));
+
+ // Check that the new file is referenced correctly by attempting a retrieval
+ Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap;
+ Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
+ Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
index fe3cc375ea..3cb5909ba9 100644
--- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -2,11 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
@@ -17,10 +21,35 @@ namespace osu.Game.Tests.Editor
{
private TestHitObjectComposer composer;
+ [Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
+ private readonly EditorBeatmap editorBeatmap;
+
+ protected override Container Content { get; }
+
+ public TestSceneHitObjectComposerDistanceSnapping()
+ {
+ base.Content.Add(new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap()),
+ Content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ }
+ },
+ });
+ }
+
[SetUp]
public void Setup() => Schedule(() =>
{
- Child = composer = new TestHitObjectComposer();
+ Children = new Drawable[]
+ {
+ composer = new TestHitObjectComposer()
+ };
BeatDivisor.Value = 1;
@@ -107,17 +136,19 @@ namespace osu.Game.Tests.Editor
[Test]
public void TestGetSnappedDurationFromDistance()
{
- assertSnappedDuration(50, 0);
+ assertSnappedDuration(0, 0);
+ assertSnappedDuration(50, 1000);
assertSnappedDuration(100, 1000);
- assertSnappedDuration(150, 1000);
+ assertSnappedDuration(150, 2000);
assertSnappedDuration(200, 2000);
- assertSnappedDuration(250, 2000);
+ assertSnappedDuration(250, 3000);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
+ assertSnappedDuration(0, 0);
assertSnappedDuration(50, 0);
- assertSnappedDuration(100, 0);
- assertSnappedDuration(150, 0);
+ assertSnappedDuration(100, 1000);
+ assertSnappedDuration(150, 1000);
assertSnappedDuration(200, 1000);
assertSnappedDuration(250, 1000);
@@ -128,8 +159,8 @@ namespace osu.Game.Tests.Editor
});
assertSnappedDuration(50, 0);
- assertSnappedDuration(100, 0);
- assertSnappedDuration(150, 0);
+ assertSnappedDuration(100, 500);
+ assertSnappedDuration(150, 500);
assertSnappedDuration(200, 500);
assertSnappedDuration(250, 500);
assertSnappedDuration(400, 1000);
@@ -138,17 +169,17 @@ namespace osu.Game.Tests.Editor
[Test]
public void GetSnappedDistanceFromDistance()
{
- assertSnappedDistance(50, 0);
+ assertSnappedDistance(50, 100);
assertSnappedDistance(100, 100);
- assertSnappedDistance(150, 100);
+ assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200);
- assertSnappedDistance(250, 200);
+ assertSnappedDistance(250, 300);
AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
assertSnappedDistance(50, 0);
- assertSnappedDistance(100, 0);
- assertSnappedDistance(150, 0);
+ assertSnappedDistance(100, 200);
+ assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
@@ -159,8 +190,8 @@ namespace osu.Game.Tests.Editor
});
assertSnappedDistance(50, 0);
- assertSnappedDistance(100, 0);
- assertSnappedDistance(150, 0);
+ assertSnappedDistance(100, 200);
+ assertSnappedDistance(150, 200);
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
assertSnappedDistance(400, 400);
@@ -183,7 +214,7 @@ namespace osu.Game.Tests.Editor
private class TestHitObjectComposer : OsuHitObjectComposer
{
- public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
+ public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
public TestHitObjectComposer()
: base(new OsuRuleset())
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
new file mode 100644
index 0000000000..885abb61b5
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.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 NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneDrainingHealthProcessor : OsuTestScene
+ {
+ private Bindable breakTime;
+ private HealthProcessor processor;
+ private ManualClock clock;
+
+ [Test]
+ public void TestInitialHealthStartsAtOne()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedBeforeGameplayStart()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+
+ setTime(100);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedAfterGameplayEnd()
+ {
+ createProcessor(createBeatmap(1000, 2000));
+ setTime(2001); // After the hitobjects
+ setHealth(1); // Reset the current health for assertions to take place
+
+ setTime(2100);
+ assertHealthEqualTo(1);
+ setTime(3000);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthNotDrainedDuringBreak()
+ {
+ createProcessor(createBeatmap(0, 2000));
+ setBreak(true);
+
+ setTime(700);
+ assertHealthEqualTo(1);
+ setTime(900);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthDrainedDuringGameplay()
+ {
+ createProcessor(createBeatmap(0, 1000));
+
+ setTime(500);
+ assertHealthNotEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedAfterRewind()
+ {
+ createProcessor(createBeatmap(0, 1000));
+ setTime(500);
+
+ setTime(0);
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthGainedOnHit()
+ {
+ Beatmap beatmap = createBeatmap(0, 1000);
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ assertHealthNotEqualTo(1);
+
+ AddStep("apply hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+ assertHealthEqualTo(1);
+ }
+
+ [Test]
+ public void TestHealthRemovedOnRevert()
+ {
+ var beatmap = createBeatmap(0, 1000);
+ JudgementResult result = null;
+
+ createProcessor(beatmap);
+ setTime(10); // Decrease health slightly
+ AddStep("apply hit result", () => processor.ApplyResult(result = new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
+
+ AddStep("revert hit result", () => processor.RevertResult(result));
+ assertHealthNotEqualTo(1);
+ }
+
+ private Beatmap createBeatmap(double startTime, double endTime)
+ {
+ var beatmap = new Beatmap
+ {
+ BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
+ };
+
+ for (double time = startTime; time <= endTime; time += 100)
+ beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
+
+ return beatmap;
+ }
+
+ private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
+ {
+ breakTime = new Bindable();
+
+ Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
+ {
+ d.RelativeSizeAxes = Axes.Both;
+ d.Clock = new FramedClock(clock = new ManualClock());
+ });
+
+ processor.IsBreakTime.BindTo(breakTime);
+ processor.ApplyBeatmap(beatmap);
+ });
+
+ private void setTime(double time) => AddStep($"set time = {time}", () => clock.CurrentTime = time);
+
+ private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
+
+ private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
+
+ private void assertHealthEqualTo(double value)
+ => AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private void assertHealthNotEqualTo(double value)
+ => AddAssert($"health != {value}", () => !Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
+
+ private class JudgeableHitObject : HitObject
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+ protected override HitWindows CreateHitWindows() => new HitWindows();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 6d7159a825..7a89642e11 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -11,8 +11,8 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Gameplay
}
}
- private class TestHitObjectWithCombo : HitObject, IHasComboInformation
+ private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
{
public bool NewCombo { get; } = false;
public int ComboOffset { get; } = 0;
@@ -126,11 +126,11 @@ namespace osu.Game.Tests.Gameplay
{
switch (lookup)
{
- case GlobalSkinConfiguration global:
+ case GlobalSkinColours global:
switch (global)
{
- case GlobalSkinConfiguration.ComboColours:
- return SkinUtils.As(new Bindable>(ComboColours));
+ case GlobalSkinColours.ComboColours:
+ return SkinUtils.As(new Bindable>(ComboColours));
}
break;
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs
new file mode 100644
index 0000000000..f2bfccb6de
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectContainer.cs
@@ -0,0 +1,75 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneHitObjectContainer : OsuTestScene
+ {
+ private HitObjectContainer container;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = container = new HitObjectContainer();
+ });
+
+ [Test]
+ public void TestLateHitObjectIsAddedEarlierInList()
+ {
+ DrawableHitObject hitObject = null;
+
+ AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
+
+ AddStep("add late hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 })));
+
+ AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 0);
+ }
+
+ [Test]
+ public void TestEarlyHitObjectIsAddedLaterInList()
+ {
+ DrawableHitObject hitObject = null;
+
+ AddStep("setup", () => container.Add(new TestDrawableHitObject(new HitObject { StartTime = 500 })));
+
+ AddStep("add early hitobject", () => container.Add(hitObject = new TestDrawableHitObject(new HitObject())));
+
+ AddAssert("hitobject index is 0", () => container.IndexOf(hitObject) == 1);
+ }
+
+ [Test]
+ public void TestHitObjectsResortedAfterStartTimeChange()
+ {
+ DrawableHitObject firstObject = null;
+ DrawableHitObject secondObject = null;
+
+ AddStep("setup", () =>
+ {
+ container.Add(firstObject = new TestDrawableHitObject(new HitObject()));
+ container.Add(secondObject = new TestDrawableHitObject(new HitObject { StartTime = 1000 }));
+ });
+
+ AddStep("move first object after second", () => firstObject.HitObject.StartTime = 2000);
+
+ AddAssert("first object index is 1", () => container.IndexOf(firstObject) == 0);
+ AddAssert("second object index is 0", () => container.IndexOf(secondObject) == 1);
+ }
+
+ private class TestDrawableHitObject : DrawableHitObject
+ {
+ public TestDrawableHitObject([NotNull] HitObject hitObject)
+ : base(hitObject)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
new file mode 100644
index 0000000000..84506739ab
--- /dev/null
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -0,0 +1,76 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.IO.Stores;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Gameplay
+{
+ [HeadlessTest]
+ public class TestSceneStoryboardSamples : OsuTestScene
+ {
+ [Test]
+ public void TestRetrieveTopLevelSample()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ [Test]
+ public void TestRetrieveSampleInSubFolder()
+ {
+ ISkin skin = null;
+ SampleChannel channel = null;
+
+ AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
+ AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
+
+ AddAssert("sample is non-null", () => channel != null);
+ }
+
+ private class TestSkin : LegacySkin
+ {
+ public TestSkin(string resourceName, AudioManager audioManager)
+ : base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
+ {
+ }
+ }
+
+ private class TestResourceStore : IResourceStore
+ {
+ private readonly string resourceName;
+
+ public TestResourceStore(string resourceName)
+ {
+ this.resourceName = resourceName;
+ }
+
+ public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null;
+
+ public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null;
+
+ public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null;
+
+ public IEnumerable GetAvailableResources() => new[] { resourceName };
+
+ public void Dispose()
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
index a51b90851c..2782e902fe 100644
--- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
+++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new DifficultyControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant
@@ -60,18 +60,18 @@ namespace osu.Game.Tests.NonVisual
{
var cpi = new ControlPointInfo();
- cpi.Add(0, new SampleControlPoint()); // is redundant
+ cpi.Add(0, new SampleControlPoint()); // is *not* redundant, special exception for first sample point
cpi.Add(1000, new SampleControlPoint()); // is redundant
- Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
- Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
-
- cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
-
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
+
+ cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant
+
+ Assert.That(cpi.Groups.Count, Is.EqualTo(2));
+ Assert.That(cpi.SamplePoints.Count, Is.EqualTo(2));
+ Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
}
[Test]
@@ -83,7 +83,7 @@ namespace osu.Game.Tests.NonVisual
cpi.Add(1000, new EffectControlPoint()); // is redundant
Assert.That(cpi.Groups.Count, Is.EqualTo(0));
- Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0));
+ Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
index 1c78b63499..d5ac38008e 100644
--- a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
+++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.NonVisual
{
Assert.AreEqual(0, stack.Count);
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[0];
});
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.NonVisual
// e.g. indices 3, 4, 5, 6 (out of range)
for (int i = stack.Count; i < stack.Count + capacity; i++)
{
- Assert.Throws(() =>
+ Assert.Throws(() =>
{
int unused = stack[i];
});
diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs
new file mode 100644
index 0000000000..d9318aa822
--- /dev/null
+++ b/osu.Game.Tests/Online/TestAPIModSerialization.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.Collections.Generic;
+using Newtonsoft.Json;
+using NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Online.API;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Tests.Online
+{
+ [TestFixture]
+ public class TestAPIModSerialization
+ {
+ [Test]
+ public void TestAcronymIsPreserved()
+ {
+ var apiMod = new APIMod(new TestMod());
+
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod));
+
+ Assert.That(deserialized.Acronym, Is.EqualTo(apiMod.Acronym));
+ }
+
+ [Test]
+ public void TestRawSettingIsPreserved()
+ {
+ var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
+
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod));
+
+ Assert.That(deserialized.Settings, Contains.Key("test_setting").With.ContainValue(2.0));
+ }
+
+ [Test]
+ public void TestConvertedModHasCorrectSetting()
+ {
+ var apiMod = new APIMod(new TestMod { TestSetting = { Value = 2 } });
+
+ var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod));
+ var converted = (TestMod)deserialized.ToMod(new TestRuleset());
+
+ Assert.That(converted.TestSetting.Value, Is.EqualTo(2));
+ }
+
+ private class TestRuleset : Ruleset
+ {
+ public override IEnumerable GetModsFor(ModType type) => new[] { new TestMod() };
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException();
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException();
+
+ public override string Description { get; } = string.Empty;
+ public override string ShortName { get; } = string.Empty;
+ }
+
+ private class TestMod : Mod
+ {
+ public override string Name => "Test Mod";
+ public override string Acronym => "TM";
+ public override double ScoreMultiplier => 1;
+
+ [SettingSource("Test")]
+ public BindableNumber TestSetting { get; } = new BindableDouble
+ {
+ MinValue = 0,
+ MaxValue = 10,
+ Default = 5,
+ Precision = 0.01,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs
new file mode 100644
index 0000000000..0ae0186770
--- /dev/null
+++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs
@@ -0,0 +1,51 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Overlays.Notifications;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Online
+{
+ [HeadlessTest]
+ public class TestSceneBeatmapManager : OsuTestScene
+ {
+ private BeatmapManager beatmaps;
+ private ProgressNotification recentNotification;
+
+ private static readonly BeatmapSetInfo test_model = new BeatmapSetInfo { OnlineBeatmapSetID = 1 };
+
+ [BackgroundDependencyLoader]
+ private void load(BeatmapManager beatmaps)
+ {
+ this.beatmaps = beatmaps;
+
+ beatmaps.PostNotification = n => recentNotification = n as ProgressNotification;
+ }
+
+ [Test]
+ public void TestCancelDownloadFromRequest()
+ {
+ AddStep("download beatmap", () => beatmaps.Download(test_model));
+
+ AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_model).Cancel());
+
+ AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
+ AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
+ }
+
+ [Test]
+ public void TestCancelDownloadFromNotification()
+ {
+ AddStep("download beatmap", () => beatmaps.Download(test_model));
+
+ AddStep("cancel download from notification", () => recentNotification.Close());
+
+ AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_model) == null);
+ AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz
new file mode 100644
index 0000000000..987dbea6db
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz
new file mode 100644
index 0000000000..8a92423d35
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus_virtual.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/ogg-beatmap.osz b/osu.Game.Tests/Resources/Archives/ogg-beatmap.osz
new file mode 100644
index 0000000000..f264a8dda2
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/ogg-beatmap.osz differ
diff --git a/osu.Game.Tests/Resources/Archives/ogg-skin.osk b/osu.Game.Tests/Resources/Archives/ogg-skin.osk
new file mode 100644
index 0000000000..d7379446aa
Binary files /dev/null and b/osu.Game.Tests/Resources/Archives/ogg-skin.osk differ
diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs
index 66084a3204..8b892fbb2f 100644
--- a/osu.Game.Tests/Resources/TestResources.cs
+++ b/osu.Game.Tests/Resources/TestResources.cs
@@ -9,9 +9,11 @@ namespace osu.Game.Tests.Resources
{
public static class TestResources
{
- public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
+ public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
- public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
+ public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
+
+ public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
diff --git a/osu.Game.Tests/Resources/test-sample.mp3 b/osu.Game.Tests/Resources/test-sample.mp3
new file mode 100644
index 0000000000..f7c344f39a
Binary files /dev/null and b/osu.Game.Tests/Resources/test-sample.mp3 differ
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 89b5db9e1b..a139c3a8c2 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
+using osu.Game.IO.Archives;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -154,7 +156,30 @@ namespace osu.Game.Tests.Scores.IO
}
}
- private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score)
+ [Test]
+ public async Task TestOnlineScoreIsAvailableLocally()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
+ {
+ try
+ {
+ var osu = await loadOsu(host);
+
+ await loadIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
+
+ var scoreManager = osu.Dependencies.Get();
+
+ // Note: A new score reference is used here since the import process mutates the original object to set an ID
+ Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score, ArchiveReader archive = null)
{
var beatmapManager = osu.Dependencies.Get();
@@ -165,7 +190,7 @@ namespace osu.Game.Tests.Scores.IO
score.Ruleset = new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
- await scoreManager.Import(score);
+ await scoreManager.Import(score, archive);
return scoreManager.GetAllUsableScores().FirstOrDefault();
}
@@ -196,5 +221,23 @@ namespace osu.Game.Tests.Scores.IO
Assert.IsTrue(task.Wait(timeout), failureMessage);
}
+
+ private class TestArchiveReader : ArchiveReader
+ {
+ public TestArchiveReader()
+ : base("test_archive")
+ {
+ }
+
+ public override Stream GetStream(string name) => new MemoryStream();
+
+ public override void Dispose()
+ {
+ }
+
+ public override IEnumerable Filenames => new[] { "test_file.osr" };
+
+ public override Stream GetUnderlyingStream() => new MemoryStream();
+ }
}
}
diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
index d7f709dc03..a6e8622b6f 100644
--- a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs
@@ -18,12 +18,21 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000));
- Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000));
- Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000));
- Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000));
+ Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 0, 10000, 1));
+ Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 0, 5000, 1));
+ Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 0, 5000, 1));
+ Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 0, 10000, 1));
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(8900, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000
+ Assert.AreEqual(13500, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000
+ Assert.AreEqual(19000, algorithm.GetDisplayStartTime(25000, 100, 5000, 500)); // 25000 - (1 + 100 / 500) * 5000
}
[Test]
diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
index 106aa88be3..1429d22c1a 100644
--- a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs
@@ -27,11 +27,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
}
[Test]
- public void TestDisplayStartTime()
+ public void TestPointDisplayStartTime()
{
- Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant
- Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5)
- Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5)
+ Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 0, 1000, 1)); // Like constant
+ Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 0, 1000, 1)); // 10500 - (1000 * 0.5)
+ Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 0, 1000, 1)); // 23000 - (1000 / 0.5)
+ }
+
+ [Test]
+ public void TestObjectDisplayStartTime()
+ {
+ Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000 / 1
+ Assert.AreEqual(9450, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000 / 2
+ Assert.AreEqual(14250, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000 / 2
+ Assert.AreEqual(16500, algorithm.GetDisplayStartTime(18000, 250, 2000, 500)); // 18000 - (1 + 250 / 500) * 2000 / 2
+ Assert.AreEqual(17800, algorithm.GetDisplayStartTime(20000, 50, 1000, 500)); // 20000 - (1 + 50 / 500) * 1000 / 0.5
+ Assert.AreEqual(19800, algorithm.GetDisplayStartTime(22000, 50, 1000, 500)); // 22000 - (1 + 50 / 500) * 1000 / 0.5
}
[Test]
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index f68d49dd3e..cef38bbbb8 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture]
public class LegacySkinDecoderTest
{
- [TestCase(true)]
- [TestCase(false)]
- public void TestDecodeSkinColours(bool hasColours)
+ [Test]
+ public void TestDecodeSkinColours()
{
var decoder = new LegacySkinDecoder();
- using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
+ using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
-
- List expectedColors;
-
- if (hasColours)
+ var expectedColors = new List
{
- expectedColors = new List
- {
- new Color4(142, 199, 255, 255),
- new Color4(255, 128, 128, 255),
- new Color4(128, 255, 255, 255),
- new Color4(100, 100, 100, 100),
- };
- }
- else
- expectedColors = new DefaultSkin().Configuration.ComboColours;
+ new Color4(142, 199, 255, 255),
+ new Color4(255, 128, 128, 255),
+ new Color4(128, 255, 255, 255),
+ new Color4(100, 100, 100, 100),
+ };
Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++)
@@ -45,6 +36,37 @@ namespace osu.Game.Tests.Skins
}
}
+ [Test]
+ public void TestDecodeEmptySkinColours()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var comboColors = decoder.Decode(stream).ComboColours;
+ var expectedColors = SkinConfiguration.DefaultComboColours;
+
+ Assert.AreEqual(expectedColors.Count, comboColors.Count);
+ for (int i = 0; i < expectedColors.Count; i++)
+ Assert.AreEqual(expectedColors[i], comboColors[i]);
+ }
+ }
+
+ [Test]
+ public void TestDecodeEmptySkinColoursNoFallback()
+ {
+ var decoder = new LegacySkinDecoder();
+
+ using (var resStream = TestResources.OpenResource("skin-empty.ini"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var skinConfiguration = decoder.Decode(stream);
+ skinConfiguration.AllowDefaultComboColoursFallback = false;
+ Assert.IsNull(skinConfiguration.ComboColours);
+ }
+ }
+
[Test]
public void TestDecodeGeneral()
{
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
new file mode 100644
index 0000000000..4d3b73fb32
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.IO.Archives;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Skins
+{
+ [HeadlessTest]
+ public class TestSceneBeatmapSkinResources : OsuTestScene
+ {
+ [Resolved]
+ private BeatmapManager beatmaps { get; set; }
+
+ private WorkingBeatmap beatmap;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
+ beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]);
+ }
+
+ [Test]
+ public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null);
+
+ [Test]
+ public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual));
+ }
+}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index 8b9c648442..35313ee858 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
@@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene
{
- private LegacySkin source1;
- private LegacySkin source2;
+ private SkinSource source1;
+ private SkinSource source2;
private SkinRequester requester;
[SetUp]
@@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestGlobalLookup()
{
- AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
+ AddAssert("Check combo colours", () => requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.Count > 0);
}
[Test]
@@ -116,6 +117,28 @@ namespace osu.Game.Tests.Skins
});
}
+ [Test]
+ public void TestEmptyComboColours()
+ {
+ AddAssert("Check retrieved combo colours is skin default colours", () =>
+ requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(SkinConfiguration.DefaultComboColours) ?? false);
+ }
+
+ [Test]
+ public void TestEmptyComboColoursNoFallback()
+ {
+ AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
+ new Color4(100, 150, 200, 255),
+ new Color4(55, 110, 166, 255),
+ new Color4(75, 125, 175, 255)
+ ));
+
+ AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
+
+ AddAssert("Check retrieved combo colours from source1", () =>
+ requester.GetConfig>(GlobalSkinColours.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
+ }
+
[Test]
public void TestLegacyVersionLookup()
{
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
new file mode 100644
index 0000000000..107a96292f
--- /dev/null
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Audio;
+using osu.Game.IO.Archives;
+using osu.Game.Skinning;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Tests.Skins
+{
+ [HeadlessTest]
+ public class TestSceneSkinResources : OsuTestScene
+ {
+ [Resolved]
+ private SkinManager skins { get; set; }
+
+ private ISkin skin;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result;
+ skin = skins.GetSkin(imported);
+ }
+
+ [Test]
+ public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null);
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
new file mode 100644
index 0000000000..6d014ca1ca
--- /dev/null
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -0,0 +1,427 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.States;
+using osu.Framework.Platform;
+using osu.Framework.Screens;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Scoring;
+using osu.Game.Screens;
+using osu.Game.Screens.Backgrounds;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.PlayerSettings;
+using osu.Game.Screens.Select;
+using osu.Game.Tests.Resources;
+using osu.Game.Users;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Background
+{
+ [TestFixture]
+ public class TestSceneUserDimBackgrounds : ManualInputManagerTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(ScreenWithBeatmapBackground),
+ typeof(PlayerLoader),
+ typeof(Player),
+ typeof(UserDimContainer),
+ typeof(OsuScreen)
+ };
+
+ private DummySongSelect songSelect;
+ private TestPlayerLoader playerLoader;
+ private TestPlayer player;
+ private BeatmapManager manager;
+ private RulesetStore rulesets;
+
+ [BackgroundDependencyLoader]
+ private void load(GameHost host, AudioManager audio)
+ {
+ Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
+ Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
+ Dependencies.Cache(new OsuConfigManager(LocalStorage));
+
+ manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
+
+ Beatmap.SetDefault();
+ }
+
+ [SetUp]
+ public virtual void SetUp() => Schedule(() =>
+ {
+ var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
+ Child = stack;
+
+ stack.Push(songSelect = new DummySongSelect());
+ });
+
+ ///
+ /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
+ ///
+ [Test]
+ public void PlayerLoaderSettingsHoverTest()
+ {
+ setupUserSettings();
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ AddStep("Trigger background preview", () =>
+ {
+ InputManager.MoveMouseTo(playerLoader.ScreenPos);
+ InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
+ });
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ ///
+ /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
+ /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
+ /// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
+ ///
+ [Test]
+ public void PlayerLoaderTransitionTest()
+ {
+ performFullSetup();
+ AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
+ AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
+ ///
+ [Test]
+ public void StoryboardBackgroundVisibilityTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Enable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ waitForDim();
+ AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
+ AddStep("Disable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = false;
+ player.StoryboardEnabled.Value = false;
+ });
+ waitForDim();
+ AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
+ }
+
+ ///
+ /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
+ ///
+ [Test]
+ public void StoryboardTransitionTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
+ }
+
+ ///
+ /// Ensure is properly accepting user-defined visual changes for a background.
+ ///
+ [Test]
+ public void DisableUserDimBackgroundTest()
+ {
+ performFullSetup();
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
+ AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Ensure is properly accepting user-defined visual changes for a storyboard.
+ ///
+ [Test]
+ public void DisableUserDimStoryboardTest()
+ {
+ performFullSetup();
+ createFakeStoryboard();
+ AddStep("Enable Storyboard", () =>
+ {
+ player.ReplacesBackground.Value = true;
+ player.StoryboardEnabled.Value = true;
+ });
+ AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
+ AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
+ waitForDim();
+ AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
+ AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
+ waitForDim();
+ AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
+ }
+
+ ///
+ /// Check if the visual settings container retains dim and blur when pausing
+ ///
+ [Test]
+ public void PauseTest()
+ {
+ performFullSetup(true);
+ AddStep("Pause", () => player.Pause());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Unpause", () => player.Resume());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ }
+
+ ///
+ /// Check if the visual settings container removes user dim when suspending for
+ ///
+ [Test]
+ public void TransitionTest()
+ {
+ performFullSetup();
+ FadeAccessibleResults results = null;
+ AddStep("Transition to Results", () => player.Push(results =
+ new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
+ AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
+ waitForDim();
+ AddAssert("Screen is undimmed, original background retained", () =>
+ songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if background gets undimmed and unblurred when leaving for
+ ///
+ [Test]
+ public void TransitionOutTest()
+ {
+ performFullSetup();
+ AddStep("Exit to song select", () => player.Exit());
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
+ }
+
+ ///
+ /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
+ ///
+ [Test]
+ public void ResumeFromPlayerTest()
+ {
+ performFullSetup();
+ AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
+ AddStep("Resume PlayerLoader", () => player.Restart());
+ waitForDim();
+ AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ waitForDim();
+ AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ }
+
+ private void waitForDim() => AddWaitStep("Wait for dim", 5);
+
+ private void createFakeStoryboard() => AddStep("Create storyboard", () =>
+ {
+ player.StoryboardEnabled.Value = false;
+ player.ReplacesBackground.Value = false;
+ player.DimmableStoryboard.Add(new OsuSpriteText
+ {
+ Size = new Vector2(500, 50),
+ Alpha = 1,
+ Colour = Color4.White,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "THIS IS A STORYBOARD",
+ Font = new FontUsage(size: 50)
+ });
+ });
+
+ private void performFullSetup(bool allowPause = false)
+ {
+ setupUserSettings();
+
+ AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
+
+ AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
+ AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
+ AddUntilStep("Wait for player to load", () => player.IsLoaded);
+ }
+
+ private void setupUserSettings()
+ {
+ AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
+ AddStep("Set default user settings", () =>
+ {
+ SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
+ songSelect.DimLevel.Value = 0.7f;
+ songSelect.BlurLevel.Value = 0.4f;
+ });
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+ rulesets?.Dispose();
+ }
+
+ private class DummySongSelect : PlaySongSelect
+ {
+ protected override BackgroundScreen CreateBackground()
+ {
+ FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
+ DimEnabled.BindTo(background.EnableUserDim);
+ return background;
+ }
+
+ public readonly Bindable DimEnabled = new Bindable();
+ public readonly Bindable DimLevel = new BindableDouble();
+ public readonly Bindable BlurLevel = new BindableDouble();
+
+ public new BeatmapCarousel Carousel => base.Carousel;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.DimLevel, DimLevel);
+ config.BindWith(OsuSetting.BlurLevel, BlurLevel);
+ }
+
+ public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1f - ((FadeAccessibleBackground)Background).CurrentDim);
+
+ public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
+
+ public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
+
+ public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
+
+ public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
+
+ public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ ///
+ /// Make sure every time a screen gets pushed, the background doesn't get replaced
+ ///
+ /// Whether or not the original background (The one created in DummySongSelect) is still the current background
+ public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
+ }
+
+ private class FadeAccessibleResults : SoloResults
+ {
+ public FadeAccessibleResults(ScoreInfo score)
+ : base(score)
+ {
+ }
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+ }
+
+ private class TestPlayer : Visual.TestPlayer
+ {
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+
+ public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
+
+ // Whether or not the player should be allowed to load.
+ public bool BlockLoad;
+
+ public Bindable StoryboardEnabled;
+ public readonly Bindable ReplacesBackground = new Bindable();
+ public readonly Bindable IsPaused = new Bindable();
+
+ public TestPlayer(bool allowPause = true)
+ : base(allowPause)
+ {
+ }
+
+ public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, CancellationToken token)
+ {
+ while (BlockLoad && !token.IsCancellationRequested)
+ Thread.Sleep(1);
+
+ StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
+ ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
+ DrawableRuleset.IsPaused.BindTo(IsPaused);
+ }
+ }
+
+ private class TestPlayerLoader : PlayerLoader
+ {
+ public VisualSettings VisualSettingsPos => VisualSettings;
+ public BackgroundScreen ScreenPos => Background;
+
+ public TestPlayerLoader(Player player)
+ : base(() => player)
+ {
+ }
+
+ public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
+
+ public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
+
+ protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
+ }
+
+ private class FadeAccessibleBackground : BackgroundScreenBeatmap
+ {
+ protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
+
+ public Color4 CurrentColour => dimmable.CurrentColour;
+
+ public float CurrentAlpha => dimmable.CurrentAlpha;
+
+ public float CurrentDim => dimmable.DimLevel;
+
+ public Vector2 CurrentBlur => Background.BlurSigma;
+
+ private TestDimmableBackground dimmable;
+
+ public FadeAccessibleBackground(WorkingBeatmap beatmap)
+ : base(beatmap)
+ {
+ }
+ }
+
+ private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
+ {
+ public Color4 CurrentColour => Content.Colour;
+ public float CurrentAlpha => Content.Alpha;
+
+ public new float DimLevel => base.DimLevel;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
index 8f71584b4d..fede99f450 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimContainer.cs
@@ -1,423 +1,112 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Input.Events;
-using osu.Framework.Input.States;
-using osu.Framework.Platform;
-using osu.Framework.Screens;
-using osu.Game.Beatmaps;
+using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Rulesets;
-using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Scoring;
-using osu.Game.Screens;
-using osu.Game.Screens.Backgrounds;
-using osu.Game.Screens.Play;
-using osu.Game.Screens.Play.PlayerSettings;
-using osu.Game.Screens.Select;
-using osu.Game.Tests.Resources;
-using osu.Game.Users;
-using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background
{
- [TestFixture]
- public class TestSceneUserDimContainer : ManualInputManagerTestScene
+ public class TestSceneUserDimContainer : OsuTestScene
{
- public override IReadOnlyList RequiredTypes => new[]
- {
- typeof(ScreenWithBeatmapBackground),
- typeof(PlayerLoader),
- typeof(Player),
- typeof(UserDimContainer),
- typeof(OsuScreen)
- };
+ private TestUserDimContainer userDimContainer;
- private DummySongSelect songSelect;
- private TestPlayerLoader playerLoader;
- private TestPlayer player;
- private BeatmapManager manager;
- private RulesetStore rulesets;
+ private readonly BindableBool isBreakTime = new BindableBool();
+
+ private Bindable lightenDuringBreaks = new Bindable();
[BackgroundDependencyLoader]
- private void load(GameHost host, AudioManager audio)
+ private void load(OsuConfigManager config)
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
- Dependencies.Cache(new OsuConfigManager(LocalStorage));
-
- manager.Import(TestResources.GetTestBeatmapForImport()).Wait();
-
- Beatmap.SetDefault();
+ lightenDuringBreaks = config.GetBindable(OsuSetting.LightenDuringBreaks);
}
[SetUp]
- public virtual void SetUp() => Schedule(() =>
+ public void SetUp() => Schedule(() =>
{
- Child = new OsuScreenStack(songSelect = new DummySongSelect())
+ Child = userDimContainer = new TestUserDimContainer
{
- RelativeSizeAxes = Axes.Both
+ RelativeSizeAxes = Axes.Both,
+ Child = new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ },
};
+
+ userDimContainer.IsBreakTime.BindTo(isBreakTime);
+ isBreakTime.Value = false;
+
+ lightenDuringBreaks.Value = false;
});
- ///
- /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel.
- ///
+ private const float test_user_dim = 0.6f;
+ private const float test_user_dim_lightened = test_user_dim - UserDimContainer.BREAK_LIGHTEN_AMOUNT;
+
+ [TestCase(test_user_dim, test_user_dim_lightened)]
+ [TestCase(0.2f, 0.0f)]
+ [TestCase(0.0f, 0.0f)]
+ public void TestBreakLightening(float userDim, float expectedBreakDim)
+ {
+ AddStep($"set dim level {userDim}", () => userDimContainer.UserDimLevel.Value = userDim);
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(expectedBreakDim));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(userDim));
+ }
+
[Test]
- public void PlayerLoaderSettingsHoverTest()
+ public void TestEnableSettingDuringBreak()
{
- setupUserSettings();
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true })));
- AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false);
- AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
- AddStep("Trigger background preview", () =>
- {
- InputManager.MoveMouseTo(playerLoader.ScreenPos);
- InputManager.MoveMouseTo(playerLoader.VisualSettingsPos);
- });
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
}
- ///
- /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings:
- /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader.
- /// We need to check that in this scenario, the dim and blur is still properly applied after entering player.
- ///
[Test]
- public void PlayerLoaderTransitionTest()
+ public void TestDisableSettingDuringBreak()
{
- performFullSetup();
- AddStep("Trigger hover event", () => playerLoader.TriggerOnHover());
- AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+ AddStep("set lighten during break", () => lightenDuringBreaks.Value = true);
+
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddUntilStep("has lightened", () => userDimContainer.DimEqual(test_user_dim_lightened));
+ AddStep("clear lighten during break", () => lightenDuringBreaks.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddUntilStep("not lightened", () => userDimContainer.DimEqual(test_user_dim));
}
- ///
- /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard.
- ///
[Test]
- public void StoryboardBackgroundVisibilityTest()
+ public void TestIgnoreUserSettings()
{
- performFullSetup();
- createFakeStoryboard();
- AddStep("Enable Storyboard", () =>
- {
- player.ReplacesBackground.Value = true;
- player.StoryboardEnabled.Value = true;
- });
- waitForDim();
- AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
- AddStep("Disable Storyboard", () =>
- {
- player.ReplacesBackground.Value = false;
- player.StoryboardEnabled.Value = false;
- });
- waitForDim();
- AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible);
+ AddStep("set dim level 0.6", () => userDimContainer.UserDimLevel.Value = test_user_dim);
+ AddUntilStep("dim reached", () => userDimContainer.DimEqual(test_user_dim));
+
+ AddStep("ignore settings", () => userDimContainer.IgnoreUserSettings.Value = true);
+ AddUntilStep("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("set break", () => isBreakTime.Value = true);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
+ AddStep("clear break", () => isBreakTime.Value = false);
+ AddAssert("no dim", () => userDimContainer.DimEqual(0));
}
- ///
- /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background.
- ///
- [Test]
- public void StoryboardTransitionTest()
+ private class TestUserDimContainer : UserDimContainer
{
- performFullSetup();
- createFakeStoryboard();
- AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Background is visible", () => songSelect.IsBackgroundVisible());
- }
+ public bool DimEqual(float expectedDimLevel) => Content.Colour == OsuColour.Gray(1f - expectedDimLevel);
- ///
- /// Ensure is properly accepting user-defined visual changes for a background.
- ///
- [Test]
- public void DisableUserDimBackgroundTest()
- {
- performFullSetup();
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
- AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- }
-
- ///
- /// Ensure is properly accepting user-defined visual changes for a storyboard.
- ///
- [Test]
- public void DisableUserDimStoryboardTest()
- {
- performFullSetup();
- createFakeStoryboard();
- AddStep("Enable Storyboard", () =>
- {
- player.ReplacesBackground.Value = true;
- player.StoryboardEnabled.Value = true;
- });
- AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
- AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
- waitForDim();
- AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
- AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
- waitForDim();
- AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
- }
-
- ///
- /// Check if the visual settings container retains dim and blur when pausing
- ///
- [Test]
- public void PauseTest()
- {
- performFullSetup(true);
- AddStep("Pause", () => player.Pause());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Unpause", () => player.Resume());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- }
-
- ///
- /// Check if the visual settings container removes user dim when suspending for
- ///
- [Test]
- public void TransitionTest()
- {
- performFullSetup();
- FadeAccessibleResults results = null;
- AddStep("Transition to Results", () => player.Push(results =
- new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } })));
- AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
- waitForDim();
- AddAssert("Screen is undimmed, original background retained", () =>
- songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect());
- }
-
- ///
- /// Check if background gets undimmed and unblurred when leaving for
- ///
- [Test]
- public void TransitionOutTest()
- {
- performFullSetup();
- AddStep("Exit to song select", () => player.Exit());
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect());
- }
-
- ///
- /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim.
- ///
- [Test]
- public void ResumeFromPlayerTest()
- {
- performFullSetup();
- AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
- AddStep("Resume PlayerLoader", () => player.Restart());
- waitForDim();
- AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
- AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- waitForDim();
- AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect());
- }
-
- private void waitForDim() => AddWaitStep("Wait for dim", 5);
-
- private void createFakeStoryboard() => AddStep("Create storyboard", () =>
- {
- player.StoryboardEnabled.Value = false;
- player.ReplacesBackground.Value = false;
- player.DimmableStoryboard.Add(new OsuSpriteText
- {
- Size = new Vector2(500, 50),
- Alpha = 1,
- Colour = Color4.White,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Text = "THIS IS A STORYBOARD",
- Font = new FontUsage(size: 50)
- });
- });
-
- private void performFullSetup(bool allowPause = false)
- {
- setupUserSettings();
-
- AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause))));
-
- AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded);
- AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
- AddUntilStep("Wait for player to load", () => player.IsLoaded);
- }
-
- private void setupUserSettings()
- {
- AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
- AddStep("Set default user settings", () =>
- {
- Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
- songSelect.DimLevel.Value = 0.7f;
- songSelect.BlurLevel.Value = 0.4f;
- });
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- rulesets?.Dispose();
- }
-
- private class DummySongSelect : PlaySongSelect
- {
- protected override BackgroundScreen CreateBackground()
- {
- FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value);
- DimEnabled.BindTo(background.EnableUserDim);
- return background;
- }
-
- public readonly Bindable DimEnabled = new Bindable();
- public readonly Bindable DimLevel = new Bindable();
- public readonly Bindable BlurLevel = new Bindable();
-
- public new BeatmapCarousel Carousel => base.Carousel;
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
- {
- config.BindWith(OsuSetting.DimLevel, DimLevel);
- config.BindWith(OsuSetting.BlurLevel, BlurLevel);
- }
-
- public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value);
-
- public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White;
-
- public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR);
-
- public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0);
-
- public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0;
-
- public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1;
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
-
- ///
- /// Make sure every time a screen gets pushed, the background doesn't get replaced
- ///
- /// Whether or not the original background (The one created in DummySongSelect) is still the current background
- public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen();
- }
-
- private class FadeAccessibleResults : SoloResults
- {
- public FadeAccessibleResults(ScoreInfo score)
- : base(score)
- {
- }
-
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
- }
-
- private class TestPlayer : Visual.TestPlayer
- {
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
-
- public new DimmableStoryboard DimmableStoryboard => base.DimmableStoryboard;
-
- // Whether or not the player should be allowed to load.
- public bool BlockLoad;
-
- public Bindable StoryboardEnabled;
- public readonly Bindable ReplacesBackground = new Bindable();
- public readonly Bindable IsPaused = new Bindable();
-
- public TestPlayer(bool allowPause = true)
- : base(allowPause)
- {
- }
-
- public bool IsStoryboardVisible => DimmableStoryboard.ContentDisplayed;
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config, CancellationToken token)
- {
- while (BlockLoad && !token.IsCancellationRequested)
- Thread.Sleep(1);
-
- StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard);
- ReplacesBackground.BindTo(Background.StoryboardReplacesBackground);
- DrawableRuleset.IsPaused.BindTo(IsPaused);
- }
- }
-
- private class TestPlayerLoader : PlayerLoader
- {
- public VisualSettings VisualSettingsPos => VisualSettings;
- public BackgroundScreen ScreenPos => Background;
-
- public TestPlayerLoader(Player player)
- : base(() => player)
- {
- }
-
- public void TriggerOnHover() => OnHover(new HoverEvent(new InputState()));
-
- public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR);
-
- protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
- }
-
- private class FadeAccessibleBackground : BackgroundScreenBeatmap
- {
- protected override DimmableBackground CreateFadeContainer() => dimmable = new TestDimmableBackground { RelativeSizeAxes = Axes.Both };
-
- public Color4 CurrentColour => dimmable.CurrentColour;
-
- public float CurrentAlpha => dimmable.CurrentAlpha;
-
- public Vector2 CurrentBlur => Background.BlurSigma;
-
- private TestDimmableBackground dimmable;
-
- public FadeAccessibleBackground(WorkingBeatmap beatmap)
- : base(beatmap)
- {
- }
- }
-
- private class TestDimmableBackground : BackgroundScreenBeatmap.DimmableBackground
- {
- public Color4 CurrentColour => Content.Colour;
- public float CurrentAlpha => Content.Alpha;
+ public new Bindable UserDimLevel => base.UserDimLevel;
}
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
index 9f16e1d781..a8830824c0 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneComposeScreen.cs
@@ -3,7 +3,10 @@
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
@@ -11,10 +14,22 @@ namespace osu.Game.Tests.Visual.Editor
[TestFixture]
public class TestSceneComposeScreen : EditorClockTestScene
{
+ [Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
+ private readonly EditorBeatmap editorBeatmap =
+ new EditorBeatmap(new OsuBeatmap
+ {
+ BeatmapInfo =
+ {
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
+ });
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
+
Child = new ComposeScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
index e4c987923c..f49256a633 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs
@@ -7,9 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
-using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -22,15 +20,15 @@ namespace osu.Game.Tests.Visual.Editor
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
- [Cached(typeof(IEditorBeatmap))]
- private readonly EditorBeatmap editorBeatmap;
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
{
- editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
}
@@ -44,7 +42,7 @@ namespace osu.Game.Tests.Visual.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
- new TestDistanceSnapGrid(new HitObject(), grid_position)
+ new TestDistanceSnapGrid()
};
});
@@ -73,7 +71,7 @@ namespace osu.Game.Tests.Visual.Editor
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
- new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 })
+ new TestDistanceSnapGrid(100)
};
});
}
@@ -82,69 +80,69 @@ namespace osu.Game.Tests.Visual.Editor
{
public new float DistanceSpacing => base.DistanceSpacing;
- public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null)
- : base(hitObject, nextHitObject, centrePosition)
+ public TestDistanceSnapGrid(double? endTime = null)
+ : base(grid_position, 0, endTime)
{
}
- protected override void CreateContent(Vector2 centrePosition)
+ protected override void CreateContent()
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
- Position = centrePosition
+ Position = StartPosition
});
- int beatIndex = 0;
+ int indexFromPlacement = 0;
- for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
+ for (float s = StartPosition.X + DistanceSpacing; s <= DrawWidth && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
- Position = new Vector2(s, centrePosition.Y),
- Colour = GetColourForBeatIndex(beatIndex)
+ Position = new Vector2(s, StartPosition.Y),
+ Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
- beatIndex = 0;
+ indexFromPlacement = 0;
- for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
+ for (float s = StartPosition.X - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
- Position = new Vector2(s, centrePosition.Y),
- Colour = GetColourForBeatIndex(beatIndex)
+ Position = new Vector2(s, StartPosition.Y),
+ Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
- beatIndex = 0;
+ indexFromPlacement = 0;
- for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++)
+ for (float s = StartPosition.Y + DistanceSpacing; s <= DrawHeight && indexFromPlacement < MaxIntervals; s += DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
- Position = new Vector2(centrePosition.X, s),
- Colour = GetColourForBeatIndex(beatIndex)
+ Position = new Vector2(StartPosition.X, s),
+ Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
- beatIndex = 0;
+ indexFromPlacement = 0;
- for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++)
+ for (float s = StartPosition.Y - DistanceSpacing; s >= 0 && indexFromPlacement < MaxIntervals; s -= DistanceSpacing, indexFromPlacement++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
- Position = new Vector2(centrePosition.X, s),
- Colour = GetColourForBeatIndex(beatIndex)
+ Position = new Vector2(StartPosition.X, s),
+ Colour = GetColourForIndexFromPlacement(indexFromPlacement)
});
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
index b7c7028b52..e41c2427fb 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
@@ -59,9 +60,13 @@ namespace osu.Game.Tests.Visual.Editor
},
});
+ var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
+
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs(clock);
Dependencies.CacheAs(clock);
+ Dependencies.CacheAs(editorBeatmap);
+ Dependencies.CacheAs(editorBeatmap);
Child = new OsuHitObjectComposer(new OsuRuleset());
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs
new file mode 100644
index 0000000000..4d8f877575
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.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 System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
+
+namespace osu.Game.Tests.Visual.Editor
+{
+ [TestFixture]
+ public class TestSceneTimelineBlueprintContainer : TimelineTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(TimelineHitObjectBlueprint),
+ };
+
+ public override Drawable CreateTestComponent() => new TimelineBlueprintContainer();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ Clock.Seek(10000);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs
new file mode 100644
index 0000000000..43a3cd6122
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineTickDisplay.cs
@@ -0,0 +1,32 @@
+// 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;
+using osu.Game.Screens.Edit.Compose.Components;
+using osu.Game.Screens.Edit.Compose.Components.Timeline;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Editor
+{
+ [TestFixture]
+ public class TestSceneTimelineTickDisplay : TimelineTestScene
+ {
+ public override Drawable CreateTestComponent() => new TimelineTickDisplay();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ BeatDivisor.Value = 4;
+
+ Add(new BeatDivisorControl(BeatDivisor)
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Margin = new MarginPadding(30),
+ Size = new Vector2(90)
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
index 121853d8d0..ae09a7fa47 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs
@@ -5,7 +5,8 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editor
@@ -25,10 +26,18 @@ namespace osu.Game.Tests.Visual.Editor
typeof(RowAttribute)
};
+ [Cached(typeof(EditorBeatmap))]
+ private readonly EditorBeatmap editorBeatmap;
+
+ public TestSceneTimingScreen()
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ }
+
[BackgroundDependencyLoader]
private void load()
{
- Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new TimingScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
index da8702209c..fd248abbc9 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
+++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs
@@ -7,7 +7,7 @@ using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.MathUtils;
+using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
similarity index 79%
rename from osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
rename to osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
index 6e5b3b93e9..7081eb3af5 100644
--- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
@@ -13,14 +12,15 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
- [TestFixture]
- public class TestSceneEditorComposeTimeline : EditorClockTestScene
+ public abstract class TimelineTestScene : EditorClockTestScene
{
public override IReadOnlyList RequiredTypes => new[]
{
@@ -30,13 +30,23 @@ namespace osu.Game.Tests.Visual.Editor
typeof(CentreMarker)
};
+ protected TimelineArea TimelineArea { get; private set; }
+
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
Beatmap.Value = new WaveformTestBeatmap(audio);
- Children = new Drawable[]
+ var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+
+ var editorBeatmap = new EditorBeatmap(playable);
+
+ Dependencies.Cache(editorBeatmap);
+ Dependencies.CacheAs(editorBeatmap);
+
+ AddRange(new Drawable[]
{
+ editorBeatmap,
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@@ -48,22 +58,28 @@ namespace osu.Game.Tests.Visual.Editor
new AudioVisualiser(),
}
},
- new TimelineArea
+ TimelineArea = new TimelineArea
{
+ Child = CreateTestComponent(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
- Size = new Vector2(0.8f, 100)
+ Size = new Vector2(0.8f, 100),
}
- };
+ });
}
+ public abstract Drawable CreateTestComponent();
+
private class AudioVisualiser : CompositeDrawable
{
private readonly Drawable marker;
- private readonly IBindable