1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:52:53 +08:00

Merge branch 'master' into add-beatmap-ruleset-selector

This commit is contained in:
Dean Herbert 2019-10-31 16:42:32 +09:00 committed by GitHub
commit 5d47a2e557
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
268 changed files with 7056 additions and 2296 deletions

View File

@ -0,0 +1,8 @@
---
name: Mobile Report
about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!)
---
**PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them.
If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome.
Otherwise, please check back in the future when the focus of development shifts towards mobile!

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset"> <configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" /> <browser url="http://localhost:5000" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset"> <configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" /> <browser url="http://localhost:5000" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset"> <configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" /> <browser url="http://localhost:5000" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset"> <configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" /> <browser url="http://localhost:5000" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament"> <configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tournament" /> <option name="PROGRAM_PARAMETERS" value="--tournament" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />
</method> </method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!"> <configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />
</method> </method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!"> <configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" /> <option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" /> <option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" /> <option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" /> <option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2"> <method v="2">
<option name="Build" enabled="true" /> <option name="Build" enabled="true" />
</method> </method>

32
.vscode/launch.json vendored
View File

@ -6,13 +6,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)", "preLaunchTask": "Build osu! (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -23,13 +23,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll" "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)", "preLaunchTask": "Build osu! (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -40,13 +40,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)", "preLaunchTask": "Build tests (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -56,13 +56,13 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll" "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)", "preLaunchTask": "Build tests (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -73,14 +73,14 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
"--tournament" "--tournament"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)", "preLaunchTask": "Build osu! (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -91,14 +91,14 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll", "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
"--tournament" "--tournament"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)", "preLaunchTask": "Build osu! (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -109,14 +109,14 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll", "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"--tournament" "--tournament"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)", "preLaunchTask": "Build tournament tests (Debug)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"
@ -127,14 +127,14 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll", "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"--tournament" "--tournament"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)", "preLaunchTask": "Build tournament tests (Release)",
"linux": { "linux": {
"env": { "env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
} }
}, },
"console": "internalConsole" "console": "internalConsole"

2
.vscode/tasks.json vendored
View File

@ -95,7 +95,7 @@
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
}, },
{ {
"label": "Restore (netcoreapp2.2)", "label": "Restore (netcoreapp3.0)",
"type": "shell", "type": "shell",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [

View File

@ -18,7 +18,7 @@ GEM
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5) dotenv (2.7.5)
emoji_regex (1.0.1) emoji_regex (1.0.1)
excon (0.66.0) excon (0.67.0)
faraday (0.15.4) faraday (0.15.4)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6) faraday-cookie_jar (0.0.6)
@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1) faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0) faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7) fastimage (2.1.7)
fastlane (2.131.0) fastlane (2.133.0)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0) addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0) babosa (>= 1.0.2, < 2.0.0)
@ -37,9 +37,9 @@ GEM
dotenv (>= 2.1.1, < 3.0.0) dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0) emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0) excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.9) faraday (< 0.16.0)
faraday-cookie_jar (~> 0.0.6) faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.9) faraday_middleware (< 0.16.0)
fastimage (>= 2.1.0, < 3.0.0) fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0) gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0) google-api-client (>= 0.21.2, < 0.24.0)
@ -52,7 +52,7 @@ GEM
multipart-post (~> 2.0.0) multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0) plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0) public_suffix (~> 2.0.0)
rubyzip (>= 1.2.2, < 2.0.0) rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3) security (= 0.1.3)
simctl (~> 1.6.3) simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0) slack-notifier (>= 2.0.0, < 3.0.0)
@ -102,7 +102,7 @@ GEM
memoist (0.16.0) memoist (0.16.0)
mime-types (3.3) mime-types (3.3)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904) mime-types-data (3.2019.1009)
mini_magick (4.9.5) mini_magick (4.9.5)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
multi_json (1.13.1) multi_json (1.13.1)
@ -121,9 +121,9 @@ GEM
uber (< 0.2.0) uber (< 0.2.0)
retriable (3.1.2) retriable (3.1.2)
rouge (2.0.7) rouge (2.0.7)
rubyzip (1.2.4) rubyzip (1.3.0)
security (0.1.3) security (0.1.3)
signet (0.11.0) signet (0.12.0)
addressable (~> 2.3) addressable (~> 2.3)
faraday (~> 0.9) faraday (~> 0.9)
jwt (>= 1.5, < 3.0) jwt (>= 1.5, < 3.0)

View File

@ -18,7 +18,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements ## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. - A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding. - When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs. - When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). - When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
@ -78,7 +78,7 @@ On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build dir
For example, you can run osu! with the following command: For example, you can run osu! with the following command:
```shell ```shell
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop
``` ```
### Testing with resource/framework modifications ### Testing with resource/framework modifications

View File

@ -1,6 +1,6 @@
clone_depth: 1 clone_depth: 1
version: '{branch}-{build}' version: '{branch}-{build}'
image: Previous Visual Studio 2017 image: Visual Studio 2019 Preview
test: off test: off
build_script: build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1 - cmd: PowerShell -Version 2.0 .\build.ps1

View File

@ -1,6 +1,6 @@
clone_depth: 1 clone_depth: 1
version: '{build}' version: '{build}'
image: Previous Visual Studio 2017 image: Visual Studio 2019 Preview
test: off test: off
skip_non_tags: true skip_non_tags: true
build_script: build_script:

63
build.ps1 Normal file → Executable file
View File

@ -1,39 +1,5 @@
##########################################################################
# This is a customized Cake bootstrapper script for PowerShell.
##########################################################################
<#
.SYNOPSIS
This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION
This Powershell script restores NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide.
.PARAMETER Script
The build script to execute.
.PARAMETER Target
The build script target to run.
.PARAMETER Configuration
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER ShowDescription
Shows description about tasks.
.PARAMETER DryRun
Performs a dry run.
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
https://cakebuild.net
#>
[CmdletBinding()] [CmdletBinding()]
Param( Param(
[string]$Script = "build.cake",
[string]$Target, [string]$Target,
[string]$Configuration, [string]$Configuration,
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
@ -45,27 +11,8 @@ Param(
[string[]]$ScriptArgs [string[]]$ScriptArgs
) )
Write-Host "Preparing to run build script..."
# Determine the script root for resolving other paths.
if(!$PSScriptRoot) {
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
# Resolve the paths for resources used for debugging.
$BUILD_DIR = Join-Path $PSScriptRoot "build"
$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
# Install the required tools locally.
Write-Host "Restoring cake tools..."
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
# Find the Cake executable
$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
# Build Cake arguments # Build Cake arguments
$cakeArguments = @("$Script"); $cakeArguments = "";
if ($Target) { $cakeArguments += "-target=$Target" } if ($Target) { $cakeArguments += "-target=$Target" }
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" } if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" } if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
@ -74,9 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" }
if ($Experimental) { $cakeArguments += "-experimental" } if ($Experimental) { $cakeArguments += "-experimental" }
$cakeArguments += $ScriptArgs $cakeArguments += $ScriptArgs
# Start Cake dotnet tool install Cake.Tool --global --version 0.35.0
Write-Host "Running build script..." dotnet cake ./build/build.cake --bootstrap
Push-Location -Path $BUILD_DIR dotnet cake ./build/build.cake $cakeArguments
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
Pop-Location
exit $LASTEXITCODE exit $LASTEXITCODE

View File

@ -1,18 +1,5 @@
#!/usr/bin/env bash echo "Installing Cake.Tool..."
dotnet tool install Cake.Tool --global --version 0.35.0
##########################################################################
# This is a customized Cake bootstrapper script for Shell.
##########################################################################
echo "Preparing to run build script..."
cd build
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
TOOLS_DIR=$SCRIPT_DIR/tools
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
SCRIPT="build.cake"
CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
# Parse arguments. # Parse arguments.
CAKE_ARGUMENTS=() CAKE_ARGUMENTS=()
@ -25,14 +12,6 @@ for i in "$@"; do
shift shift
done done
# Install the required tools locally.
echo "Restoring cake tools..."
dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1
# Search for the CakeBuild binary.
CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll")
# Start Cake
echo "Running build script..." echo "Running build script..."
dotnet cake ./build/build.cake --bootstrap
dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}" dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"

View File

@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.21" #addin "nuget:?package=CodeFileSanity&version=0.0.33"
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" #addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1" #tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake" Version="0.34.1" />
<PackageReference Include="Cake.CoreCLR" Version="0.34.1" />
</ItemGroup>
</Project>

View File

@ -61,7 +61,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.930.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2019.1029.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -16,6 +16,11 @@ namespace osu.Android
protected override void OnCreate(Bundle savedInstanceState) protected override void OnCreate(Bundle savedInstanceState)
{ {
// The default current directory on android is '/'.
// On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
// In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
base.OnCreate(savedInstanceState); base.OnCreate(savedInstanceState);
Window.AddFlags(WindowManagerFlags.Fullscreen); Window.AddFlags(WindowManagerFlags.Fullscreen);

View File

@ -15,7 +15,7 @@ using osuTK.Graphics;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
public class VersionManager : OverlayContainer public class VersionManager : VisibilityContainer
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures, OsuGameBase game) private void load(OsuColour colours, TextureStore textures, OsuGameBase game)

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" /> <Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@ -7,7 +7,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)", "preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)", "preLaunchTask": "Build (Release)",

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void load() private void load()
{ {
var controlPointInfo = new ControlPointInfo(); var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint()); controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{ {

View File

@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
} }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
public enum FruitVisualRepresentation public enum FruitVisualRepresentation

View File

@ -2,35 +2,50 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower> public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
{ {
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
private readonly Container bananaContainer; private readonly Container bananaContainer;
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null) public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
: base(s) : base(s)
{ {
this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft; Origin = Anchor.BottomLeft;
X = 0; X = 0;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
foreach (var b in s.NestedHitObjects.Cast<Banana>())
AddNested(createDrawableRepresentation?.Invoke(b));
} }
protected override void AddNested(DrawableHitObject h) protected override void AddNestedHitObject(DrawableHitObject hitObject)
{ {
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; base.AddNestedHitObject(hitObject);
bananaContainer.Add(h); bananaContainer.Add(hitObject);
base.AddNested(h); }
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
bananaContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case Banana banana:
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
}
return base.CreateNestedHitObject(hitObject);
} }
} }
} }

View File

@ -2,38 +2,50 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream> public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
{ {
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
private readonly Container dropletContainer; private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null) public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
: base(s) : base(s)
{ {
this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Origin = Anchor.BottomLeft; Origin = Anchor.BottomLeft;
X = 0; X = 0;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
foreach (var o in s.NestedHitObjects.Cast<CatchHitObject>())
AddNested(createDrawableRepresentation?.Invoke(o));
} }
protected override void AddNested(DrawableHitObject h) protected override void AddNestedHitObject(DrawableHitObject hitObject)
{ {
var catchObject = (DrawableCatchHitObject)h; base.AddNestedHitObject(hitObject);
dropletContainer.Add(hitObject);
}
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
dropletContainer.Clear();
}
dropletContainer.Add(h); protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
base.AddNested(h); {
switch (hitObject)
{
case CatchHitObject catchObject:
return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
}
return base.CreateNestedHitObject(hitObject);
} }
} }
} }

View File

@ -7,7 +7,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)", "preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)", "preLaunchTask": "Build (Release)",

View File

@ -27,8 +27,13 @@ namespace osu.Game.Rulesets.Mania.Tests
[Cached(typeof(IReadOnlyList<Mod>))] [Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>(); private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
[Cached(typeof(IScrollingInfo))]
private IScrollingInfo scrollingInfo;
protected ManiaPlacementBlueprintTestScene() protected ManiaPlacementBlueprintTestScene()
{ {
scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
Add(column = new Column(0) Add(column = new Column(0)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -36,15 +41,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = Color4.OrangeRed, AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll Clock = new FramedClock(new StopwatchClock()), // No scroll
}); });
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
return dependencies;
} }
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = { Value = OsuColour.Gray(0.3f) } AccentColour = { Value = OsuColour.Gray(0.3f) }
} }
}; };
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
} }
protected override void Update() protected override void Update()
@ -51,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests
nested.Y = (float)(-finalPosition * content.DrawHeight); nested.Y = (float)(-finalPosition * content.DrawHeight);
} }
} }
protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
} }
} }

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -17,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{ {
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
{ {
private readonly DrawableNote drawableObject;
protected override Container<Drawable> Content => content ?? base.Content; protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content; private readonly Container content;
@ -27,6 +24,8 @@ namespace osu.Game.Rulesets.Mania.Tests
var note = new Note { Column = 0 }; var note = new Note { Column = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
DrawableNote drawableObject;
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down) base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -34,8 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Size = new Vector2(50, 20), Size = new Vector2(50, 20),
Child = drawableObject = new DrawableNote(note) Child = drawableObject = new DrawableNote(note)
}; };
}
protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject); AddBlueprint(new NoteSelectionBlueprint(drawableObject));
}
} }
} }

View File

@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />

View File

@ -3,7 +3,6 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osuTK; using osuTK;
@ -49,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private double originalStartTime; private double originalStartTime;
protected override bool OnMouseMove(MouseMoveEvent e) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
base.OnMouseMove(e); base.UpdatePosition(screenSpacePosition);
if (PlacementBegun) if (PlacementBegun)
{ {
var endTime = TimeAt(e.ScreenSpaceMousePosition); var endTime = TimeAt(screenSpacePosition);
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
HitObject.Duration = Math.Abs(endTime - originalStartTime); HitObject.Duration = Math.Abs(endTime - originalStartTime);
@ -65,10 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
headPiece.Width = tailPiece.Width = SnappedWidth; headPiece.Width = tailPiece.Width = SnappedWidth;
headPiece.X = tailPiece.X = SnappedMousePosition.X; headPiece.X = tailPiece.X = SnappedMousePosition.X;
originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
} }
return true;
} }
} }
} }

View File

@ -16,45 +16,51 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
{ {
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject; public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly BodyPiece body; [Resolved]
private OsuColour colours { get; set; }
public HoldNoteSelectionBlueprint(DrawableHoldNote hold) public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
: base(hold) : base(hold)
{ {
InternalChildren = new Drawable[]
{
new HoldNoteNoteSelectionBlueprint(hold.Head),
new HoldNoteNoteSelectionBlueprint(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
},
};
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, IScrollingInfo scrollingInfo) private void load(IScrollingInfo scrollingInfo)
{ {
body.BorderColour = colours.Yellow;
direction.BindTo(scrollingInfo.Direction); direction.BindTo(scrollingInfo.Direction);
} }
protected override void LoadComplete()
{
base.LoadComplete();
InternalChildren = new Drawable[]
{
new HoldNoteNoteSelectionBlueprint(DrawableObject.Head),
new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail),
new BodyPiece
{
AccentColour = Color4.Transparent,
BorderColour = colours.Yellow
},
};
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
// When scrolling upwards our origin is already at the top of the head note (which is the intended location), // When scrolling upwards our origin is already at the top of the head note (which is the intended location),
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note) // but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
if (direction.Value == ScrollingDirection.Down) if (direction.Value == ScrollingDirection.Down)
Y -= HitObject.Tail.DrawHeight; Y -= DrawableObject.Tail.DrawHeight;
} }
public override Quad SelectionQuad => ScreenSpaceDrawQuad; public override Quad SelectionQuad => ScreenSpaceDrawQuad;
@ -71,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.Update(); base.Update();
Anchor = HitObject.Anchor; Anchor = DrawableObject.Anchor;
Origin = HitObject.Origin; Origin = DrawableObject.Origin;
Position = HitObject.DrawPosition; Position = DrawableObject.DrawPosition;
} }
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input. // Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.

View File

@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column == null) if (Column == null)
return base.OnMouseDown(e); return base.OnMouseDown(e);
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = Column.Index; HitObject.Column = Column.Index;
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition));
BeginPlacement();
return true; return true;
} }
@ -62,19 +60,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return base.OnMouseUp(e); return base.OnMouseUp(e);
} }
protected override bool OnMouseMove(MouseMoveEvent e) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
if (!PlacementBegun) if (!PlacementBegun)
Column = ColumnAt(e.ScreenSpaceMousePosition); Column = ColumnAt(screenSpacePosition);
if (Column == null) return false; if (Column == null) return;
SnappedWidth = Column.DrawWidth; SnappedWidth = Column.DrawWidth;
// Snap to the column // Snap to the column
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y); SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
return true;
} }
protected double TimeAt(Vector2 screenSpacePosition) protected double TimeAt(Vector2 screenSpacePosition)
@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// If we're scrolling downwards, a position of 0 is actually further away from the hit target // If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space // so we need to flip the vertical coordinate in the hitobject container's space
var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y; var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down) if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
@ -103,16 +100,58 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
scrollingInfo.TimeRange.Value, scrollingInfo.TimeRange.Value,
Column.HitObjectContainer.DrawHeight); Column.HitObjectContainer.DrawHeight);
return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
pos = Column.HitObjectContainer.DrawHeight - pos;
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
} }
protected Column ColumnAt(Vector2 screenSpacePosition) protected Column ColumnAt(Vector2 screenSpacePosition)
=> composer.ColumnAt(applyPositionOffset(screenSpacePosition, false)); => composer.ColumnAt(screenSpacePosition);
private Vector2 applyPositionOffset(Vector2 position, bool reverse) /// <summary>
/// Converts a mouse position to a hitobject position.
/// </summary>
/// <remarks>
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
/// </remarks>
/// <param name="mousePosition">The mouse position.</param>
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
{ {
position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2; switch (scrollingInfo.Direction.Value)
return position; {
case ScrollingDirection.Up:
mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
break;
}
return mousePosition;
}
/// <summary>
/// Converts a hitobject position to a mouse position.
/// </summary>
/// <param name="hitObjectPosition">The hitobject position.</param>
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
{
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
break;
}
return hitObjectPosition;
} }
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public Vector2 ScreenSpaceDragPosition { get; private set; } public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; } public Vector2 DragPosition { get; private set; }
protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
protected IClock EditorClock { get; private set; } protected IClock EditorClock { get; private set; }
@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved] [Resolved]
private IManiaHitObjectComposer composer { get; set; } private IManiaHitObjectComposer composer { get; set; }
public ManiaSelectionBlueprint(DrawableHitObject hitObject) public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(hitObject) : base(drawableObject)
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
} }
@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.Update(); base.Update();
Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
} }
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return base.OnMouseDown(e); return base.OnMouseDown(e);
} }
@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
var result = base.OnDrag(e); var result = base.OnDrag(e);
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return result; return result;
} }
public override void Show() public override void Show()
{ {
HitObject.AlwaysAlive = true; DrawableObject.AlwaysAlive = true;
base.Show(); base.Show();
} }
public override void Hide() public override void Hide()
{ {
HitObject.AlwaysAlive = false; DrawableObject.AlwaysAlive = false;
base.Hide(); base.Hide();
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{ {
base.Update(); base.Update();
Size = HitObject.DrawSize; Size = DrawableObject.DrawSize;
} }
} }
} }

View File

@ -3,9 +3,7 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Input.Events;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -31,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Edit
editorClock = clock; editorClock = clock;
} }
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) public override void HandleMovement(MoveSelectionEvent moveEvent)
{ {
adjustOrigins((ManiaSelectionBlueprint)blueprint); var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
performDragMovement(dragEvent); int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
performColumnMovement(dragEvent);
base.HandleDrag(blueprint, dragEvent); adjustOrigins(maniaBlueprint);
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent);
base.HandleMovement(moveEvent);
} }
/// <summary> /// <summary>
@ -47,41 +48,44 @@ namespace osu.Game.Rulesets.Mania.Edit
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param> /// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
private void adjustOrigins(ManiaSelectionBlueprint reference) private void adjustOrigins(ManiaSelectionBlueprint reference)
{ {
var referenceParent = (HitObjectContainer)reference.HitObject.Parent; var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent;
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y; float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y;
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
// Flip the vertical coordinate space when scrolling downwards // Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down) if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = targetPosition - referenceParent.DrawHeight; targetPosition = targetPosition - referenceParent.DrawHeight;
float movementDelta = targetPosition - reference.HitObject.Position.Y; float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>()) foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
b.HitObject.Y += movementDelta; b.DrawableObject.Y += movementDelta;
} }
private void performDragMovement(DragEvent dragEvent) private void performDragMovement(MoveSelectionEvent moveEvent)
{ {
float delta = moveEvent.InstantDelta.Y;
// When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
// This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight;
foreach (var b in SelectedBlueprints) foreach (var b in SelectedBlueprints)
{ {
var hitObject = b.HitObject; var hitObject = b.DrawableObject;
var objectParent = (HitObjectContainer)hitObject.Parent; var objectParent = (HitObjectContainer)hitObject.Parent;
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame // StartTime could be used to adjust the position if only one movement event was received per frame.
// without the position having been updated by the parenting ScrollingHitObjectContainer // However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
hitObject.Y += dragEvent.Delta.Y; hitObject.Y += delta;
float targetPosition; float targetPosition = hitObject.Position.Y;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target // The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
// so we need to flip the vertical coordinate in the hitobject container's space
if (scrollingInfo.Direction.Value == ScrollingDirection.Down) if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -hitObject.Position.Y; targetPosition = -targetPosition;
else
targetPosition = hitObject.Position.Y;
objectParent.Remove(hitObject); objectParent.Remove(hitObject);
@ -94,14 +98,13 @@ namespace osu.Game.Rulesets.Mania.Edit
} }
} }
private void performColumnMovement(DragEvent dragEvent) private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{ {
var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition); var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition); if (currentColumn == null)
if (lastColumn == null || currentColumn == null)
return; return;
int columnDelta = currentColumn.Index - lastColumn.Index; int columnDelta = currentColumn.Index - lastColumn;
if (columnDelta == 0) if (columnDelta == 0)
return; return;

View File

@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
{ {
public abstract class ManiaSelectionBlueprint : SelectionBlueprint public abstract class ManiaSelectionBlueprint : SelectionBlueprint
{ {
protected ManiaSelectionBlueprint(DrawableHitObject hitObject) protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(hitObject) : base(drawableObject)
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
} }

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
@ -22,8 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
public override bool DisplayResult => false; public override bool DisplayResult => false;
public readonly DrawableNote Head; public DrawableNote Head => headContainer.Child;
public readonly DrawableNote Tail; public DrawableNote Tail => tailContainer.Child;
private readonly Container<DrawableHeadNote> headContainer;
private readonly Container<DrawableTailNote> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly BodyPiece bodyPiece; private readonly BodyPiece bodyPiece;
@ -40,50 +43,81 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject) public DrawableHoldNote(HoldNote hitObject)
: base(hitObject) : base(hitObject)
{ {
Container<DrawableHoldNoteTick> tickContainer;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
bodyPiece = new BodyPiece bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
{ tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
RelativeSizeAxes = Axes.X, headContainer = new Container<DrawableHeadNote> { RelativeSizeAxes = Axes.Both },
}, tailContainer = new Container<DrawableTailNote> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
})
},
Head = new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
Tail = new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
}); });
foreach (var tick in tickContainer)
AddNested(tick);
AddNested(Head);
AddNested(Tail);
AccentColour.BindValueChanged(colour => AccentColour.BindValueChanged(colour =>
{ {
bodyPiece.AccentColour = colour.NewValue; bodyPiece.AccentColour = colour.NewValue;
Head.AccentColour.Value = colour.NewValue;
Tail.AccentColour.Value = colour.NewValue;
tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue);
}, true); }, true);
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableHeadNote head:
headContainer.Child = head;
break;
case DrawableTailNote tail:
tailContainer.Child = tail;
break;
case DrawableHoldNoteTick tick:
tickContainer.Add(tick);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
headContainer.Clear();
tailContainer.Clear();
tickContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case TailNote _:
return new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = { BindTarget = AccentColour }
};
case Note _:
return new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = { BindTarget = AccentColour }
};
case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime,
AccentColour = { BindTarget = AccentColour }
};
}
return base.CreateNestedHitObject(hitObject);
}
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e) protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{ {
base.OnDirectionChanged(e); base.OnDirectionChanged(e);

View File

@ -101,6 +101,6 @@ namespace osu.Game.Rulesets.Mania.Objects
public override Judgement CreateJudgement() => new HoldNoteJudgement(); public override Judgement CreateJudgement() => new HoldNoteJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{ {
public override Judgement CreateJudgement() => new HoldNoteTickJudgement(); public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -7,7 +7,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)", "preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)", "preLaunchTask": "Build (Release)",

View File

@ -1,10 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -14,16 +15,58 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
{ {
private readonly DrawableHitCircle drawableObject; private HitCircle hitCircle;
private DrawableHitCircle drawableObject;
private TestBlueprint blueprint;
public TestSceneHitCircleSelectionBlueprint() [SetUp]
public void Setup() => Schedule(() =>
{ {
var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; Clear();
hitCircle = new HitCircle { Position = new Vector2(256, 192) };
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableHitCircle(hitCircle)); Add(drawableObject = new DrawableHitCircle(hitCircle));
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
} }
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject); [Test]
public void TestMoveHitObject()
{
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
[Test]
public void TestStackedHitObject()
{
AddStep("set stacking", () => hitCircle.StackHeight = 5);
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition);
}
private class TestBlueprint : HitCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestBlueprint(DrawableHitCircle drawableCircle)
: base(drawableCircle)
{
}
}
} }
} }

View File

@ -0,0 +1,176 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CircularDistanceSnapGrid)
};
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
private readonly TestOsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
}
[SetUp]
public void Setup() => Schedule(() =>
{
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.Clear();
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
}
[Test]
public void TestCursorInCentre()
{
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorBeforeMovementPoint()
{
AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorAfterMovementPoint()
{
AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f)));
assertSnappedDistance((float)beat_length * 2);
}
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
{
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position));
});
private class SnappingCursorContainer : CompositeDrawable
{
public Func<Vector2, Vector2> GetSnapPosition;
private readonly Drawable cursor;
public SnappingCursorContainer()
{
RelativeSizeAxes = Axes.Both;
InternalChild = cursor = new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(50),
Colour = Color4.Red
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
base.OnMouseMove(e);
updatePosition(e.ScreenSpaceMousePosition);
return true;
}
private void updatePosition(Vector2 screenSpacePosition)
{
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
}
}
private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject)
{
}
}
private class SnapProvider : IDistanceSnapProvider
{
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
public float DurationToDistance(double referenceTime, double duration) => 0;
public double DistanceToDuration(double referenceTime, float distance) => 0;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
}
}
}

View File

@ -111,6 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1))); AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
} }
[Test]
public void TestChangeStackHeight()
{
DrawableSlider slider = null;
AddStep("create slider", () =>
{
slider = (DrawableSlider)createSlider(repeats: 1);
Add(slider);
});
AddStep("change stack height", () => slider.HitObject.StackHeight = 10);
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
}
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
@ -293,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{ {
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });

View File

@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, 25), }, 25),
} }
}, },
ControlPointInfo =
{
DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
},
BeatmapInfo = BeatmapInfo =
{ {
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests
}, },
}); });
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
p.OnLoadComplete += _ => p.OnLoadComplete += _ =>

View File

@ -3,11 +3,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -29,11 +31,16 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(PathControlPointPiece) typeof(PathControlPointPiece)
}; };
private readonly DrawableSlider drawableObject; private Slider slider;
private DrawableSlider drawableObject;
private TestSliderBlueprint blueprint;
public TestSceneSliderSelectionBlueprint() [SetUp]
public void Setup() => Schedule(() =>
{ {
var slider = new Slider Clear();
slider = new Slider
{ {
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.Bezier, new[]
@ -47,8 +54,78 @@ namespace osu.Game.Rulesets.Osu.Tests
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableSlider(slider)); Add(drawableObject = new DrawableSlider(slider));
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
checkPositions();
} }
protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject); [Test]
public void TestMoveHitObject()
{
moveHitObject();
checkPositions();
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
moveHitObject();
checkPositions();
}
[Test]
public void TestStackedHitObject()
{
AddStep("set stacking", () => slider.StackHeight = 5);
checkPositions();
}
private void moveHitObject()
{
AddStep("move hitobject", () =>
{
slider.Position = new Vector2(300, 225);
});
}
private void checkPositions()
{
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
AddAssert("head positioned correctly",
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private class TestSliderBlueprint : SliderSelectionBlueprint
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
public TestSliderBlueprint(DrawableSlider slider)
: base(slider)
{
}
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
}
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider, position)
{
}
}
} }
} }

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -25,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SpinnerPiece) typeof(SpinnerPiece)
}; };
private readonly DrawableSpinner drawableSpinner;
public TestSceneSpinnerSelectionBlueprint() public TestSceneSpinnerSelectionBlueprint()
{ {
var spinner = new Spinner var spinner = new Spinner
@ -35,16 +32,19 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = -1000, StartTime = -1000,
EndTime = 2000 EndTime = 2000
}; };
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
DrawableSpinner drawableSpinner;
Add(new Container Add(new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f), Size = new Vector2(0.5f),
Child = drawableSpinner = new DrawableSpinner(spinner) Child = drawableSpinner = new DrawableSpinner(spinner)
}); });
}
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) }; AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
}
} }
} }

View File

@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -0,0 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a selection or placement blueprint which visualises an <see cref="OsuHitObject"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="BlueprintPiece{T}"/> visualises.</typeparam>
public abstract class BlueprintPiece<T> : CompositeDrawable
where T : OsuHitObject
{
/// <summary>
/// Updates this <see cref="BlueprintPiece{T}"/> using the properties of a <see cref="OsuHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="OsuHitObject"/> to reference properties from.</param>
public virtual void UpdateFrom(T hitObject)
{
Position = hitObject.StackedPosition;
}
}
}

View File

@ -10,18 +10,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
{ {
public class HitCirclePiece : HitObjectPiece public class HitCirclePiece : BlueprintPiece<HitCircle>
{ {
private readonly HitCircle hitCircle; public HitCirclePiece()
public HitCirclePiece(HitCircle hitCircle)
: base(hitCircle)
{ {
this.hitCircle = hitCircle;
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Scale = new Vector2(hitCircle.Scale);
CornerRadius = Size.X / 2; CornerRadius = Size.X / 2;
InternalChild = new RingPiece(); InternalChild = new RingPiece();
@ -31,12 +26,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Colour = colours.Yellow; Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
} }
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; public override void UpdateFrom(HitCircle hitObject)
{
base.UpdateFrom(hitObject);
Scale = new Vector2(hitObject.Scale);
}
} }
} }

View File

@ -13,31 +13,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{ {
public new HitCircle HitObject => (HitCircle)base.HitObject; public new HitCircle HitObject => (HitCircle)base.HitObject;
private readonly HitCirclePiece circlePiece;
public HitCirclePlacementBlueprint() public HitCirclePlacementBlueprint()
: base(new HitCircle()) : base(new HitCircle())
{ {
InternalChild = new HitCirclePiece(HitObject); InternalChild = circlePiece = new HitCirclePiece();
} }
protected override void LoadComplete() protected override void Update()
{ {
base.LoadComplete(); base.Update();
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame circlePiece.UpdateFrom(HitObject);
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
HitObject.StartTime = EditorClock.CurrentTime;
EndPlacement(); EndPlacement();
return true; return true;
} }
protected override bool OnMouseMove(MouseMoveEvent e) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
HitObject.Position = e.MousePosition; HitObject.Position = ToLocalSpace(screenSpacePosition);
return true;
} }
} }
} }

View File

@ -1,18 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{ {
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint public class HitCircleSelectionBlueprint : OsuSelectionBlueprint<HitCircle>
{ {
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle) protected new DrawableHitCircle DrawableObject => (DrawableHitCircle)base.DrawableObject;
: base(hitCircle)
protected readonly HitCirclePiece CirclePiece;
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
: base(drawableCircle)
{ {
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject); InternalChild = CirclePiece = new HitCirclePiece();
} }
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
} }
} }

View File

@ -1,36 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="OsuHitObject"/>.
/// </summary>
public abstract class HitObjectPiece : CompositeDrawable
{
protected readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
protected readonly IBindable<int> StackHeightBindable = new Bindable<int>();
protected readonly IBindable<float> ScaleBindable = new Bindable<float>();
private readonly OsuHitObject hitObject;
protected HitObjectPiece(OsuHitObject hitObject)
{
this.hitObject = hitObject;
}
[BackgroundDependencyLoader]
private void load()
{
PositionBindable.BindTo(hitObject.PositionBindable);
StackHeightBindable.BindTo(hitObject.StackHeightBindable);
ScaleBindable.BindTo(hitObject.ScaleBindable);
}
}
}

View File

@ -7,12 +7,13 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{ {
public class OsuSelectionBlueprint : SelectionBlueprint public abstract class OsuSelectionBlueprint<T> : SelectionBlueprint
where T : OsuHitObject
{ {
protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject; protected T HitObject => (T)DrawableObject.HitObject;
public OsuSelectionBlueprint(DrawableHitObject hitObject) protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
: base(hitObject) : base(drawableObject)
{ {
} }
} }

View File

@ -1,32 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="Slider"/>.
/// </summary>
public abstract class SliderPiece : HitObjectPiece
{
protected readonly IBindable<SliderPath> PathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
protected SliderPiece(Slider slider)
: base(slider)
{
this.slider = slider;
}
[BackgroundDependencyLoader]
private void load()
{
PathBindable.BindTo(slider.PathBindable);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -8,14 +9,15 @@ using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
public class PathControlPointPiece : CompositeDrawable public class PathControlPointPiece : BlueprintPiece<Slider>
{ {
public Action<Vector2[]> ControlPointsChanged;
private readonly Slider slider; private readonly Slider slider;
private readonly int index; private readonly int index;
@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (isSegmentSeparatorWithPrevious) if (isSegmentSeparatorWithPrevious)
newControlPoints[index - 1] = newControlPoints[index]; newControlPoints[index - 1] = newControlPoints[index];
slider.Path = new SliderPath(slider.Path.Type, newControlPoints); ControlPointsChanged?.Invoke(newControlPoints);
return true; return true;
} }

View File

@ -1,37 +1,35 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
public class PathControlPointVisualiser : SliderPiece public class PathControlPointVisualiser : CompositeDrawable
{ {
public Action<Vector2[]> ControlPointsChanged;
private readonly Slider slider; private readonly Slider slider;
private readonly Container<PathControlPointPiece> pieces; private readonly Container<PathControlPointPiece> pieces;
public PathControlPointVisualiser(Slider slider) public PathControlPointVisualiser(Slider slider)
: base(slider)
{ {
this.slider = slider; this.slider = slider;
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both }; InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
} }
[BackgroundDependencyLoader] protected override void Update()
private void load()
{ {
PathBindable.BindValueChanged(_ => updatePathControlPoints(), true); base.Update();
}
private void updatePathControlPoints()
{
while (slider.Path.ControlPoints.Length > pieces.Count) while (slider.Path.ControlPoints.Length > pieces.Count)
pieces.Add(new PathControlPointPiece(slider, pieces.Count)); pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) });
while (slider.Path.ControlPoints.Length < pieces.Count) while (slider.Path.ControlPoints.Length < pieces.Count)
pieces.Remove(pieces[pieces.Count - 1]); pieces.Remove(pieces[pieces.Count - 1]);
} }

View File

@ -11,19 +11,15 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
public class SliderBodyPiece : SliderPiece public class SliderBodyPiece : BlueprintPiece<Slider>
{ {
private readonly Slider slider;
private readonly ManualSliderBody body; private readonly ManualSliderBody body;
public SliderBodyPiece(Slider slider) public SliderBodyPiece()
: base(slider)
{ {
this.slider = slider;
InternalChild = body = new ManualSliderBody InternalChild = body = new ManualSliderBody
{ {
AccentColour = Color4.Transparent, AccentColour = Color4.Transparent
}; };
} }
@ -31,19 +27,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
body.BorderColour = colours.Yellow; body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true);
} }
private void updatePosition() => Position = slider.StackedPosition; public override void UpdateFrom(Slider hitObject)
protected override void Update()
{ {
base.Update(); base.UpdateFrom(hitObject);
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
var vertices = new List<Vector2>(); var vertices = new List<Vector2>();
slider.Path.GetPathToProgress(vertices, 0, 1); hitObject.Path.GetPathToProgress(vertices, 0, 1);
body.SetVertices(vertices); body.SetVertices(vertices);

View File

@ -1,47 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderCirclePiece : HitCirclePiece
{
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
private readonly SliderPosition position;
public SliderCirclePiece(Slider slider, SliderPosition position)
: base(slider.HeadCircle)
{
this.slider = slider;
this.position = position;
}
[BackgroundDependencyLoader]
private void load()
{
pathBindable.BindTo(slider.PathBindable);
pathBindable.BindValueChanged(_ => UpdatePosition(), true);
}
protected override void UpdatePosition()
{
switch (position)
{
case SliderPosition.Start:
Position = slider.StackedPosition + slider.Path.PositionAt(0);
break;
case SliderPosition.End:
Position = slider.StackedPosition + slider.Path.PositionAt(1);
break;
}
}
}
}

View File

@ -1,22 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
{ {
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) protected readonly HitCirclePiece CirclePiece;
: base(hitObject)
private readonly SliderPosition position;
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider)
{ {
InternalChild = new SliderCirclePiece(slider, position); this.position = position;
InternalChild = CirclePiece = new HitCirclePiece();
Select(); Select();
} }
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle);
}
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false; public override bool HandlePositionalInput => false;
} }

View File

@ -6,11 +6,13 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -21,11 +23,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
private readonly List<Segment> segments = new List<Segment>(); private readonly List<Segment> segments = new List<Segment>();
private Vector2 cursor; private Vector2 cursor;
private InputManager inputManager;
private PlacementState state; private PlacementState state;
[Resolved(CanBeNull = true)]
private HitObjectComposer composer { get; set; }
public SliderPlacementBlueprint() public SliderPlacementBlueprint()
: base(new Objects.Slider()) : base(new Objects.Slider())
{ {
@ -38,10 +48,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new SliderBodyPiece(HitObject), bodyPiece = new SliderBodyPiece(),
new SliderCirclePiece(HitObject, SliderPosition.Start), headCirclePiece = new HitCirclePiece(),
new SliderCirclePiece(HitObject, SliderPosition.End), tailCirclePiece = new HitCirclePiece(),
new PathControlPointVisualiser(HitObject), new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
}; };
setState(PlacementState.Initial); setState(PlacementState.Initial);
@ -50,25 +60,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
inputManager = GetContainingInputManager();
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
} }
protected override bool OnMouseMove(MouseMoveEvent e) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
switch (state) switch (state)
{ {
case PlacementState.Initial: case PlacementState.Initial:
HitObject.Position = e.MousePosition; HitObject.Position = ToLocalSpace(screenSpacePosition);
return true; break;
case PlacementState.Body: case PlacementState.Body:
cursor = e.MousePosition - HitObject.Position; // The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
return true; // is used instead since snapping control points doesn't make much sense
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
break;
} }
return false;
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
@ -109,8 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve() private void beginCurve()
{ {
BeginPlacement(); BeginPlacement();
HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body); setState(PlacementState.Body);
} }
@ -128,8 +134,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider() private void updateSlider()
{ {
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
} }
private void setState(PlacementState newState) private void setState(PlacementState newState)

View File

@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -9,9 +13,14 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
public class SliderSelectionBlueprint : OsuSelectionBlueprint public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
{ {
private readonly SliderCircleSelectionBlueprint headBlueprint; protected readonly SliderBodyPiece BodyPiece;
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
[Resolved(CanBeNull = true)]
private HitObjectComposer composer { get; set; }
public SliderSelectionBlueprint(DrawableSlider slider) public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider) : base(slider)
@ -20,13 +29,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new SliderBodyPiece(sliderObject), BodyPiece = new SliderBodyPiece(),
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
new PathControlPointVisualiser(sliderObject), new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
}; };
} }
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint; protected override void Update()
{
base.Update();
BodyPiece.UpdateFrom(HitObject);
}
private void onNewControlPoints(Vector2[] controlPoints)
{
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
}
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
} }
} }

View File

@ -12,17 +12,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{ {
public class SpinnerPiece : HitObjectPiece public class SpinnerPiece : BlueprintPiece<Spinner>
{ {
private readonly Spinner spinner;
private readonly CircularContainer circle; private readonly CircularContainer circle;
private readonly RingPiece ring; private readonly RingPiece ring;
public SpinnerPiece(Spinner spinner) public SpinnerPiece()
: base(spinner)
{ {
this.spinner = spinner;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -44,21 +40,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
Origin = Anchor.Centre Origin = Anchor.Centre
} }
}; };
ring.Scale = new Vector2(spinner.Scale);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Colour = colours.Yellow; Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
StackHeightBindable.BindValueChanged(_ => updatePosition());
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
} }
private void updatePosition() => Position = spinner.Position; public override void UpdateFrom(Spinner hitObject)
{
base.UpdateFrom(hitObject);
ring.Scale = new Vector2(hitObject.Scale);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
} }

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{ {
@ -21,7 +22,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
public SpinnerPlacementBlueprint() public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 }) : base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{ {
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f }; InternalChild = piece = new SpinnerPiece { Alpha = 0.5f };
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
@ -33,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
} }
else else
{ {
HitObject.StartTime = EditorClock.CurrentTime;
isPlacingEnd = true; isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint); piece.FadeTo(1f, 150, Easing.OutQuint);
@ -43,5 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return true; return true;
} }
public override void UpdatePosition(Vector2 screenSpacePosition)
{
}
} }
} }

View File

@ -8,14 +8,21 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{ {
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint public class SpinnerSelectionBlueprint : OsuSelectionBlueprint<Spinner>
{ {
private readonly SpinnerPiece piece; private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner) public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner) : base(spinner)
{ {
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject); InternalChild = piece = new SpinnerPiece();
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
} }
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
public OsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject, hitObject.StackedEndPosition)
{
Masking = true;
}
}
}

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
@ -52,5 +54,31 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateBlueprintFor(hitObject); return base.CreateBlueprintFor(hitObject);
} }
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
{
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
{
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
else
{
double minTime = objects.Min(h => h.StartTime);
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
}
} }
} }

View File

@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq; using System.Linq;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuSelectionHandler : SelectionHandler public class OsuSelectionHandler : SelectionHandler
{ {
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) public override void HandleMovement(MoveSelectionEvent moveEvent)
{ {
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>()) foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
{ {
@ -21,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Edit
continue; continue;
} }
h.Position += dragEvent.Delta; h.Position += moveEvent.InstantDelta;
} }
base.HandleDrag(blueprint, dragEvent); base.HandleMovement(moveEvent);
} }
} }
} }

View File

@ -24,14 +24,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly IBindable<int> stackHeightBindable = new Bindable<int>(); private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new Bindable<float>();
public OsuAction? HitAction => hitArea.HitAction; public OsuAction? HitAction => HitArea.HitAction;
public readonly HitReceptor HitArea;
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer; private readonly Container scaleContainer;
private readonly HitArea hitArea;
public SkinnableDrawable CirclePiece { get; }
public DrawableHitCircle(HitCircle h) public DrawableHitCircle(HitCircle h)
: base(h) : base(h)
{ {
@ -48,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
hitArea = new HitArea HitArea = new HitReceptor
{ {
Hit = () => Hit = () =>
{ {
@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, },
}; };
Size = hitArea.DrawSize; Size = HitArea.DrawSize;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -153,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true); Expire(true);
hitArea.HitAction = null; HitArea.HitAction = null;
break; break;
case ArmedState.Miss: case ArmedState.Miss:
@ -172,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => ApproachCircle; public Drawable ProxiedLayer => ApproachCircle;
private class HitArea : Drawable, IKeyBindingHandler<OsuAction> public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
{ {
// IsHovered is used // IsHovered is used
public override bool HandlePositionalInput => true; public override bool HandlePositionalInput => true;
@ -181,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public OsuAction? HitAction; public OsuAction? HitAction;
public HitArea() public HitReceptor()
{ {
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);

View File

@ -5,7 +5,6 @@ using osuTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -21,16 +20,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{ {
private readonly Slider slider; public DrawableSliderHead HeadCircle => headContainer.Child;
private readonly List<Drawable> components = new List<Drawable>(); public DrawableSliderTail TailCircle => tailContainer.Child;
public readonly DrawableHitCircle HeadCircle;
public readonly DrawableSliderTail TailCircle;
public readonly SnakingSliderBody Body; public readonly SnakingSliderBody Body;
public readonly SliderBall Ball; public readonly SliderBall Ball;
private readonly Container<DrawableSliderHead> headContainer;
private readonly Container<DrawableSliderTail> tailContainer;
private readonly Container<DrawableSliderTick> tickContainer;
private readonly Container<DrawableRepeatPoint> repeatContainer;
private readonly Slider slider;
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>(); private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>(); private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>(); private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
@ -44,14 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Position = s.StackedPosition; Position = s.StackedPosition;
Container<DrawableSliderTick> ticks;
Container<DrawableRepeatPoint> repeatPoints;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
Body = new SnakingSliderBody(s), Body = new SnakingSliderBody(s),
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both }, tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this) Ball = new SliderBall(s, this)
{ {
GetInitialHitAction = () => HeadCircle.HitAction, GetInitialHitAction = () => HeadCircle.HitAction,
@ -60,45 +61,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true, AlwaysPresent = true,
Alpha = 0 Alpha = 0
}, },
HeadCircle = new DrawableSliderHead(s, s.HeadCircle) headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
{ tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
OnShake = Shake
},
TailCircle = new DrawableSliderTail(s, s.TailCircle)
}; };
components.Add(Body);
components.Add(Ball);
AddNested(HeadCircle);
AddNested(TailCircle);
components.Add(TailCircle);
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{
var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position };
ticks.Add(drawableTick);
components.Add(drawableTick);
AddNested(drawableTick);
}
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position };
repeatPoints.Add(drawableRepeatPoint);
components.Add(drawableRepeatPoint);
AddNested(drawableRepeatPoint);
}
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
Body.FadeInFromZero(HitObject.TimeFadeIn);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -108,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale => scaleBindable.BindValueChanged(scale =>
{ {
updatePathRadius(); updatePathRadius();
@ -115,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}); });
positionBindable.BindTo(HitObject.PositionBindable); positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable); scaleBindable.BindTo(HitObject.ScaleBindable);
pathBindable.BindTo(slider.PathBindable); pathBindable.BindTo(slider.PathBindable);
@ -129,6 +96,67 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, true); }, true);
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableSliderHead head:
headContainer.Child = head;
break;
case DrawableSliderTail tail:
tailContainer.Child = tail;
break;
case DrawableSliderTick tick:
tickContainer.Add(tick);
break;
case DrawableRepeatPoint repeat:
repeatContainer.Add(repeat);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
headContainer.Clear();
tailContainer.Clear();
repeatContainer.Clear();
tickContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case SliderTailCircle tail:
return new DrawableSliderTail(slider, tail);
case HitCircle head:
return new DrawableSliderHead(slider, head) { OnShake = Shake };
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
case RepeatPoint repeat:
return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
}
return base.CreateNestedHitObject(hitObject);
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
Body.FadeInFromZero(HitObject.TimeFadeIn);
}
public readonly Bindable<bool> Tracking = new Bindable<bool>(); public readonly Bindable<bool> Tracking = new Bindable<bool>();
protected override void Update() protected override void Update()
@ -139,9 +167,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress); Ball.UpdateProgress(completionProgress);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); Body.UpdateProgress(completionProgress);
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
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 IRequireTracking t) t.Tracking = Ball.Tracking;
}
Size = Body.Size; Size = Body.Size;
OriginPosition = Body.PathOffset; OriginPosition = Body.PathOffset;
@ -187,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => ApplyResult(r =>
{ {
var judgementsCount = NestedHitObjects.Count(); var judgementsCount = NestedHitObjects.Count;
var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount; var hitFraction = (double)judgementsHit / judgementsCount;
@ -228,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
} }
public Drawable ProxiedLayer => HeadCircle.ApproachCircle; public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
} }

View File

@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => updatePosition()); positionBindable.BindValueChanged(_ => updatePosition());
pathBindable.BindValueChanged(_ => updatePosition(), true); pathBindable.BindValueChanged(_ => updatePosition(), true);
// TODO: This has no drawable content. Support for skins should be added.
} }
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -30,17 +29,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[] Children = new Drawable[]
{ {
new CircularContainer new Container
{ {
Masking = true, Masking = true,
Origin = Anchor.Centre,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 60, Radius = 60,
Colour = Color4.White.Opacity(0.5f), Colour = Color4.White.Opacity(0.5f),
}, },
Child = new Box()
}, },
number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText number = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{ {

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -14,8 +15,16 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{ {
/// <summary>
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
/// </summary>
public const float OBJECT_RADIUS = 64; public const float OBJECT_RADIUS = 64;
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
/// </summary>
internal const float BASE_SCORING_DISTANCE = 100;
public double TimePreempt = 600; public double TimePreempt = 600;
public double TimeFadeIn = 400; public double TimeFadeIn = 400;
@ -90,6 +99,15 @@ namespace osu.Game.Rulesets.Osu.Objects
set => LastInComboBindable.Value = value; set => LastInComboBindable.Value = value;
} }
protected OsuHitObject()
{
StackHeightBindable.BindValueChanged(height =>
{
foreach (var nested in NestedHitObjects.OfType<OsuHitObject>())
nested.StackHeight = height.NewValue;
});
}
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -30,6 +30,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement(); public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
public class Slider : OsuHitObject, IHasCurve public class Slider : OsuHitObject, IHasCurve
{ {
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second.
/// </summary>
private const float base_scoring_distance = 100;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
@ -54,13 +49,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
base.Position = value; base.Position = value;
endPositionCache.Invalidate();
if (HeadCircle != null) if (HeadCircle != null)
HeadCircle.Position = value; HeadCircle.Position = value;
if (TailCircle != null) if (TailCircle != null)
TailCircle.Position = EndPosition; TailCircle.Position = EndPosition;
endPositionCache.Invalidate();
} }
} }
@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
@ -168,6 +163,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
StartTime = e.Time, StartTime = e.Time,
Position = Position, Position = Position,
StackHeight = StackHeight,
Samples = getNodeSamples(0), Samples = getNodeSamples(0),
SampleControlPoint = SampleControlPoint, SampleControlPoint = SampleControlPoint,
}); });
@ -181,6 +177,7 @@ namespace osu.Game.Rulesets.Osu.Objects
{ {
StartTime = e.Time, StartTime = e.Time,
Position = EndPosition, Position = EndPosition,
StackHeight = StackHeight
}); });
break; break;
@ -205,6 +202,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement(); public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -25,6 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuSliderTailJudgement(); public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -32,6 +32,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement(); public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -33,6 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement(); public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -143,14 +143,14 @@
"Objects": [{ "Objects": [{
"StartTime": 34989, "StartTime": 34989,
"EndTime": 34989, "EndTime": 34989,
"X": 163, "X": 156.597382,
"Y": 138 "Y": 131.597382
}, },
{ {
"StartTime": 35018, "StartTime": 35018,
"EndTime": 35018, "EndTime": 35018,
"X": 188, "X": 181.597382,
"Y": 138 "Y": 131.597382
} }
] ]
}, },
@ -159,14 +159,14 @@
"Objects": [{ "Objects": [{
"StartTime": 35106, "StartTime": 35106,
"EndTime": 35106, "EndTime": 35106,
"X": 163, "X": 159.798691,
"Y": 138 "Y": 134.798691
}, },
{ {
"StartTime": 35135, "StartTime": 35135,
"EndTime": 35135, "EndTime": 35135,
"X": 188, "X": 184.798691,
"Y": 138 "Y": 134.798691
} }
] ]
}, },
@ -191,20 +191,20 @@
"Objects": [{ "Objects": [{
"StartTime": 35695, "StartTime": 35695,
"EndTime": 35695, "EndTime": 35695,
"X": 166, "X": 162.798691,
"Y": 76 "Y": 72.79869
}, },
{ {
"StartTime": 35871, "StartTime": 35871,
"EndTime": 35871, "EndTime": 35871,
"X": 240.99855, "X": 237.797241,
"Y": 75.53417 "Y": 72.33286
}, },
{ {
"StartTime": 36011, "StartTime": 36011,
"EndTime": 36011, "EndTime": 36011,
"X": 315.9971, "X": 312.795776,
"Y": 75.0683441 "Y": 71.8670349
} }
] ]
}, },
@ -235,20 +235,20 @@
"Objects": [{ "Objects": [{
"StartTime": 36518, "StartTime": 36518,
"EndTime": 36518, "EndTime": 36518,
"X": 166, "X": 169.201309,
"Y": 76 "Y": 79.20131
}, },
{ {
"StartTime": 36694, "StartTime": 36694,
"EndTime": 36694, "EndTime": 36694,
"X": 240.99855, "X": 244.19986,
"Y": 75.53417 "Y": 78.73548
}, },
{ {
"StartTime": 36834, "StartTime": 36834,
"EndTime": 36834, "EndTime": 36834,
"X": 315.9971, "X": 319.198425,
"Y": 75.0683441 "Y": 78.26965
} }
] ]
}, },
@ -257,20 +257,20 @@
"Objects": [{ "Objects": [{
"StartTime": 36929, "StartTime": 36929,
"EndTime": 36929, "EndTime": 36929,
"X": 315, "X": 324.603943,
"Y": 75 "Y": 84.6039352
}, },
{ {
"StartTime": 37105, "StartTime": 37105,
"EndTime": 37105, "EndTime": 37105,
"X": 240.001526, "X": 249.605469,
"Y": 75.47769 "Y": 85.08163
}, },
{ {
"StartTime": 37245, "StartTime": 37245,
"EndTime": 37245, "EndTime": 37245,
"X": 165.003052, "X": 174.607,
"Y": 75.95539 "Y": 85.5593262
} }
] ]
} }

View File

@ -2,14 +2,11 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
@ -23,12 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private bool cursorExpand; private bool cursorExpand;
private Bindable<float> cursorScale;
private Bindable<bool> autoCursorScale;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private Container expandTarget; private Container expandTarget;
private Drawable scaleTarget;
public OsuCursor() public OsuCursor()
{ {
@ -43,43 +35,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, IBindable<WorkingBeatmap> beatmap) private void load()
{ {
InternalChild = expandTarget = new Container InternalChild = expandTarget = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Child = scaleTarget = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
} }
}; };
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += _ => calculateScale();
cursorScale = config.GetBindable<float>(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
autoCursorScale.ValueChanged += _ => calculateScale();
calculateScale();
}
private void calculateScale()
{
float scale = cursorScale.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;
}
scaleTarget.Scale = new Vector2(scale);
} }
private const float pressed_scale = 1.2f; private const float pressed_scale = 1.2f;

View File

@ -8,6 +8,8 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -27,6 +29,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private readonly Drawable cursorTrail; private readonly Drawable cursorTrail;
public Bindable<float> CursorScale;
private Bindable<float> userCursorScale;
private Bindable<bool> autoCursorScale;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
public OsuCursorContainer() public OsuCursorContainer()
{ {
InternalChild = fadeContainer = new Container InternalChild = fadeContainer = new Container
@ -37,9 +44,41 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuRulesetConfigManager config) private void load(OsuConfigManager config, OsuRulesetConfigManager rulesetConfig, IBindable<WorkingBeatmap> beatmap)
{ {
config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); rulesetConfig?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += _ => calculateScale();
userCursorScale = config.GetBindable<float>(OsuSetting.GameplayCursorSize);
userCursorScale.ValueChanged += _ => calculateScale();
autoCursorScale = config.GetBindable<bool>(OsuSetting.AutoCursorSize);
autoCursorScale.ValueChanged += _ => calculateScale();
CursorScale = new Bindable<float>();
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()
{
base.LoadComplete();
showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true); showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true);
} }
@ -90,13 +129,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override void PopIn() protected override void PopIn()
{ {
fadeContainer.FadeTo(1, 300, Easing.OutQuint); fadeContainer.FadeTo(1, 300, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); ActiveCursor.ScaleTo(CursorScale.Value, 400, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); ActiveCursor.ScaleTo(CursorScale.Value * 0.8f, 450, Easing.OutQuint);
} }
private class DefaultCursorTrail : CursorTrail private class DefaultCursorTrail : CursorTrail

View File

@ -57,21 +57,15 @@ namespace osu.Game.Rulesets.Osu.UI
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
h.OnNewResult += onNewResult; h.OnNewResult += onNewResult;
h.OnLoadComplete += d =>
if (h is IDrawableHitObjectWithProxiedApproach c)
{ {
var original = c.ProxiedLayer; if (d is IDrawableHitObjectWithProxiedApproach c)
approachCircles.Add(c.ProxiedLayer.CreateProxy());
// Hitobjects only have lifetimes set on LoadComplete. For nested hitobjects (e.g. SliderHeads), this only happens when the parenting slider becomes visible. };
// This delegation is required to make sure that the approach circles for those not-yet-loaded objects aren't added prematurely.
original.OnLoadComplete += addApproachCircleProxy;
}
base.Add(h); base.Add(h);
} }
private void addApproachCircleProxy(Drawable d) => approachCircles.Add(d.CreateProxy());
public override void PostProcess() public override void PostProcess()
{ {
connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>(); connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();

View File

@ -1,15 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -18,9 +18,11 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
public class OsuResumeOverlay : ResumeOverlay public class OsuResumeOverlay : ResumeOverlay
{ {
private Container cursorScaleContainer;
private OsuClickToResumeCursor clickToResumeCursor; private OsuClickToResumeCursor clickToResumeCursor;
private GameplayCursorContainer localCursorContainer; private OsuCursorContainer localCursorContainer;
private Bindable<float> localCursorScale;
public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null;
@ -29,22 +31,35 @@ namespace osu.Game.Rulesets.Osu.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); Add(cursorScaleContainer = new Container
{
RelativePositionAxes = Axes.Both,
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
});
} }
public override void Show() public override void Show()
{ {
base.Show(); base.Show();
clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); GameplayCursor.ActiveCursor.Hide();
cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position);
clickToResumeCursor.Appear();
if (localCursorContainer == null) if (localCursorContainer == null)
{
Add(localCursorContainer = new OsuCursorContainer()); Add(localCursorContainer = new OsuCursorContainer());
localCursorScale = new Bindable<float>();
localCursorScale.BindTo(localCursorContainer.CursorScale);
localCursorScale.BindValueChanged(scale => cursorScaleContainer.Scale = new Vector2(scale.NewValue), true);
}
} }
public override void Hide() public override void Hide()
{ {
localCursorContainer?.Expire(); localCursorContainer?.Expire();
localCursorContainer = null; localCursorContainer = null;
GameplayCursor.ActiveCursor.Show();
base.Hide(); base.Hide();
} }
@ -82,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.UI
case OsuAction.RightButton: case OsuAction.RightButton:
if (!IsHovered) return false; if (!IsHovered) return false;
this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); this.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
ResumeRequested?.Invoke(); ResumeRequested?.Invoke();
return true; return true;
@ -93,11 +108,10 @@ namespace osu.Game.Rulesets.Osu.UI
public bool OnReleased(OsuAction action) => false; public bool OnReleased(OsuAction action) => false;
public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => public void Appear() => Schedule(() =>
{ {
updateColour(); updateColour();
this.MoveTo(activeCursorPosition); this.ScaleTo(4).Then().ScaleTo(1, 1000, Easing.OutQuint);
this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint);
}); });
private void updateColour() private void updateColour()

View File

@ -7,7 +7,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" "${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)", "preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch", "request": "launch",
"program": "dotnet", "program": "dotnet",
"args": [ "args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" "${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll"
], ],
"cwd": "${workspaceRoot}", "cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)", "preLaunchTask": "Build (Release)",

View File

@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
AddStep("Reset height", () => changePlayfieldSize(6)); AddStep("Reset height", () => changePlayfieldSize(6));
var controlPointInfo = new ControlPointInfo(); var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint()); controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{ {
@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
Hit hit = new Hit(); Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty()); hit.ApplyDefaults(cpi, new BeatmapDifficulty());
@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great;
var cpi = new ControlPointInfo(); var cpi = new ControlPointInfo();
cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
Hit hit = new Hit(); Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty()); hit.ApplyDefaults(cpi, new BeatmapDifficulty());
@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
private class TestStrongNestedHit : DrawableStrongNestedHit private class TestStrongNestedHit : DrawableStrongNestedHit
{ {
public TestStrongNestedHit(DrawableHitObject mainObject) public TestStrongNestedHit(DrawableHitObject mainObject)
: base(null, mainObject) : base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject)
{ {
} }

View File

@ -9,7 +9,7 @@
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework> <TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />

View File

@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio
{ {
this.controlPoints = controlPoints; this.controlPoints = controlPoints;
IEnumerable<SampleControlPoint> samplePoints; IEnumerable<SampleControlPoint> samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints;
if (controlPoints.SamplePoints.Count == 0)
// Get the default sample point
samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) };
else
samplePoints = controlPoints.SamplePoints;
foreach (var s in samplePoints) foreach (var s in samplePoints)
{ {

View File

@ -12,6 +12,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -28,30 +29,17 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary> /// </summary>
private int rollingHits; private int rollingHits;
private readonly Container<DrawableDrumRollTick> tickContainer;
private Color4 colourIdle;
private Color4 colourEngaged;
public DrawableDrumRoll(DrumRoll drumRoll) public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll) : base(drumRoll)
{ {
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
Container<DrawableDrumRollTick> tickContainer;
MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both }); MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick> { RelativeSizeAxes = Axes.Both });
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{
var newTick = new DrawableDrumRollTick(tick);
newTick.OnNewResult += onNewTickResult;
AddNested(newTick);
tickContainer.Add(newTick);
} }
}
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece();
public override bool OnPressed(TaikoAction action) => false;
private Color4 colourIdle;
private Color4 colourEngaged;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
@ -60,8 +48,51 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
colourEngaged = colours.YellowDarker; colourEngaged = colours.YellowDarker;
} }
private void onNewTickResult(DrawableHitObject obj, JudgementResult result) protected override void LoadComplete()
{ {
base.LoadComplete();
OnNewResult += onNewResult;
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableDrumRollTick tick:
tickContainer.Add(tick);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
tickContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case DrumRollTick tick:
return new DrawableDrumRollTick(tick);
}
return base.CreateNestedHitObject(hitObject);
}
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece();
public override bool OnPressed(TaikoAction action) => false;
private void onNewResult(DrawableHitObject obj, JudgementResult result)
{
if (!(obj is DrawableDrumRollTick))
return;
if (result.Type > HitResult.Miss) if (result.Type > HitResult.Miss)
rollingHits++; rollingHits++;
else else

View File

@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -14,6 +13,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -30,8 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary> /// </summary>
private const double ring_appear_offset = 100; private const double ring_appear_offset = 100;
private readonly List<DrawableSwellTick> ticks = new List<DrawableSwellTick>(); private readonly Container<DrawableSwellTick> ticks;
private readonly Container bodyContainer; private readonly Container bodyContainer;
private readonly CircularContainer targetRing; private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing; private readonly CircularContainer expandingRing;
@ -108,16 +107,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
} }
}); });
AddInternal(ticks = new Container<DrawableSwellTick> { RelativeSizeAxes = Axes.Both });
MainPiece.Add(symbol = new SwellSymbolPiece()); MainPiece.Add(symbol = new SwellSymbolPiece());
foreach (var tick in HitObject.NestedHitObjects.OfType<SwellTick>())
{
var vis = new DrawableSwellTick(tick);
ticks.Add(vis);
AddInternal(vis);
AddNested(vis);
}
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -136,11 +128,49 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Width *= Parent.RelativeChildSize.X; Width *= Parent.RelativeChildSize.X;
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableSwellTick tick:
ticks.Add(tick);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
ticks.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case SwellTick tick:
return new DrawableSwellTick(tick);
}
return base.CreateNestedHitObject(hitObject);
}
protected override void CheckForResult(bool userTriggered, double timeOffset) protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
if (userTriggered) if (userTriggered)
{ {
var nextTick = ticks.Find(j => !j.IsHit); DrawableSwellTick nextTick = null;
foreach (var t in ticks)
{
if (!t.IsHit)
{
nextTick = t;
break;
}
}
nextTick?.TriggerResult(HitResult.Great); nextTick?.TriggerResult(HitResult.Great);

View File

@ -11,6 +11,7 @@ using osu.Game.Audio;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
@ -109,11 +110,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
protected readonly Vector2 BaseSize; public new TaikoHitType HitObject;
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece; protected readonly TaikoPiece MainPiece;
public new TaikoHitType HitObject; private readonly Container<DrawableStrongNestedHit> strongHitContainer;
protected DrawableTaikoHitObject(TaikoHitType hitObject) protected DrawableTaikoHitObject(TaikoHitType hitObject)
: base(hitObject) : base(hitObject)
@ -129,15 +131,36 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Content.Add(MainPiece = CreateMainPiece()); Content.Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai; MainPiece.KiaiMode = HitObject.Kiai;
var strongObject = HitObject.NestedHitObjects.OfType<StrongHitObject>().FirstOrDefault(); AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
if (strongObject != null)
{
var strongHit = CreateStrongHit(strongObject);
AddNested(strongHit);
AddInternal(strongHit);
} }
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableStrongNestedHit strong:
strongHitContainer.Add(strong);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
strongHitContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case StrongHitObject strong:
return CreateStrongHit(strong);
}
return base.CreateNestedHitObject(hitObject);
} }
// Normal and clap samples are handled by the drum // Normal and clap samples are handled by the drum

View File

@ -88,6 +88,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -27,6 +27,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement(); public override Judgement CreateJudgement() => new TaikoDrumRollTickJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
public override Judgement CreateJudgement() => new TaikoStrongJudgement(); public override Judgement CreateJudgement() => new TaikoStrongJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -35,6 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoSwellJudgement(); public override Judgement CreateJudgement() => new TaikoSwellJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
{ {
public override Judgement CreateJudgement() => new TaikoSwellTickJudgement(); public override Judgement CreateJudgement() => new TaikoSwellTickJudgement();
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

View File

@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = beatmap.ControlPointInfo; var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count); Assert.AreEqual(4, controlPoints.TimingPoints.Count);
Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
Assert.AreEqual(42, controlPoints.SamplePoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count);
Assert.AreEqual(42, controlPoints.EffectPoints.Count); Assert.AreEqual(8, controlPoints.EffectPoints.Count);
var timingPoint = controlPoints.TimingPointAt(0); var timingPoint = controlPoints.TimingPointAt(0);
Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(956, timingPoint.Time);
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(48428); difficultyPoint = controlPoints.DifficultyPointAt(48428);
Assert.AreEqual(48428, difficultyPoint.Time); Assert.AreEqual(0, difficultyPoint.Time);
Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier);
difficultyPoint = controlPoints.DifficultyPointAt(116999); difficultyPoint = controlPoints.DifficultyPointAt(116999);
@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
effectPoint = controlPoints.EffectPointAt(119637); effectPoint = controlPoints.EffectPointAt(119637);
Assert.AreEqual(119637, effectPoint.Time); Assert.AreEqual(95901, effectPoint.Time);
Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine); Assert.IsFalse(effectPoint.OmitFirstBarLine);
} }
@ -262,6 +262,21 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
[Test]
public void TestTimingPointResetsSpeedMultiplier()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("timingpoint-speedmultiplier-reset.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPoints.DifficultyPointAt(0).SpeedMultiplier, Is.EqualTo(0.5).Within(0.1));
Assert.That(controlPoints.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1).Within(0.1));
}
}
[Test] [Test]
public void TestDecodeBeatmapColours() public void TestDecodeBeatmapColours()
{ {
@ -362,6 +377,23 @@ namespace osu.Game.Tests.Beatmaps.Formats
} }
} }
[Test]
public void TestDecodeControlPointDifficultyChange()
{
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("controlpoint-difficulty-multiplier.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var controlPointInfo = decoder.Decode(stream).ControlPointInfo;
Assert.That(controlPointInfo.DifficultyPointAt(5).SpeedMultiplier, Is.EqualTo(1));
Assert.That(controlPointInfo.DifficultyPointAt(1000).SpeedMultiplier, Is.EqualTo(10));
Assert.That(controlPointInfo.DifficultyPointAt(2000).SpeedMultiplier, Is.EqualTo(1.8518518518518519d));
Assert.That(controlPointInfo.DifficultyPointAt(3000).SpeedMultiplier, Is.EqualTo(0.5));
}
}
[Test] [Test]
public void TestDecodeControlPointCustomSampleBank() public void TestDecodeControlPointCustomSampleBank()
{ {
@ -591,5 +623,27 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.Throws<IOException>(() => Decoder.GetDecoder<Beatmap>(stream)); Assert.Throws<IOException>(() => Decoder.GetDecoder<Beatmap>(stream));
} }
} }
[Test]
public void TestAllowFallbackDecoderOverwrite()
{
Decoder<Beatmap> decoder = null;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
}
Assert.DoesNotThrow(LegacyDifficultyCalculatorBeatmapDecoder.Register);
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyDifficultyCalculatorBeatmapDecoder>(decoder);
}
}
} }
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed() public async Task TestImportWhenClosed()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
{ {
try try
{ {
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete() public async Task TestImportThenDelete()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
{ {
try try
{ {
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport() public async Task TestImportThenImport()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
{ {
try try
{ {
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport() public async Task TestImportCorruptThenImport()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
{ {
try try
{ {
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure() public async Task TestRollbackOnFailure()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
{ {
try try
{ {
@ -213,7 +213,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImportDifferentHash() public async Task TestImportThenImportDifferentHash()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash)))
{ {
try try
{ {
@ -244,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport() public async Task TestImportThenDeleteThenImport()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
{ {
try try
{ {
@ -272,7 +272,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
{ {
try try
{ {
@ -306,7 +306,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs() public async Task TestImportWithDuplicateBeatmapIDs()
{ {
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
{ {
try try
{ {
@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportWhenFileOpen() public async Task TestImportWhenFileOpen()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
{ {
try try
{ {
@ -414,7 +414,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public async Task TestImportNestedStructure() public async Task TestImportNestedStructure()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure")) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
{ {
try try
{ {
@ -456,9 +456,63 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null) [Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{ {
var temp = path ?? TestResources.GetTestBeatmapForImport(); using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
string dataFolder = Path.Combine(extractedFolder, "actual_data");
string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX");
string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted");
Directory.CreateDirectory(dataFolder);
Directory.CreateDirectory(resourceForkFolder);
using (var resourceForkFile = File.CreateText(resourceForkFilePath))
{
resourceForkFile.WriteLine("adding content so that it's not empty");
}
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(dataFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();

Some files were not shown because too many files have changed in this diff Show More