1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-18 03:27:20 +08:00

Merge branch 'master' into cursor-size-trail

This commit is contained in:
Dean Herbert 2019-10-14 16:35:37 +09:00 committed by GitHub
commit 64a6ca28e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1303 additions and 754 deletions
.idea/.idea.osu/.idea/runConfigurations
.vscode
Gemfile.lockREADME.mdappveyor.ymlappveyor_deploy.ymlbuild.ps1build.sh
build
osu.Android.props
osu.Desktop
osu.Game.Rulesets.Catch.Tests
osu.Game.Rulesets.Catch/Objects
osu.Game.Rulesets.Mania.Tests
osu.Game.Rulesets.Mania
osu.Game.Rulesets.Osu.Tests
osu.Game.Rulesets.Osu
osu.Game.Rulesets.Taiko.Tests
osu.Game.Rulesets.Taiko/Objects
osu.Game.Tests
osu.Game.Tournament.Tests
osu.Game
osu.iOS.props

@ -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" />

@ -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" />

@ -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" />

@ -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" />

@ -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>

@ -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>

@ -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

@ -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

@ -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": [

@ -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)

@ -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

@ -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

@ -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:

65
build.ps1 Normal file → Executable 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" exit $LASTEXITCODE
Pop-Location
exit $LASTEXITCODE

@ -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[@]}"

@ -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();

@ -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>

@ -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.1011.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -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>

@ -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)",

@ -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" />

@ -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

@ -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)",

@ -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" />

@ -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 HitObject => (DrawableManiaHitObject)base.HitObject;
protected IClock EditorClock { get; private set; } protected IClock EditorClock { get; private set; }

@ -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.HitObject.HitObject.Column;
performColumnMovement(dragEvent);
base.HandleDrag(blueprint, dragEvent); adjustOrigins(maniaBlueprint);
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent);
base.HandleMovement(moveEvent);
} }
/// <summary> /// <summary>
@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Edit
b.HitObject.Y += movementDelta; b.HitObject.Y += movementDelta;
} }
private void performDragMovement(DragEvent dragEvent) private void performDragMovement(MoveSelectionEvent moveEvent)
{ {
foreach (var b in SelectedBlueprints) foreach (var b in SelectedBlueprints)
{ {
@ -72,7 +73,7 @@ namespace osu.Game.Rulesets.Mania.Edit
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
// without the position having been updated by the parenting ScrollingHitObjectContainer // without the position having been updated by the parenting ScrollingHitObjectContainer
hitObject.Y += dragEvent.Delta.Y; hitObject.Y += moveEvent.InstantDelta.Y;
float targetPosition; float targetPosition;
@ -94,14 +95,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;

@ -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;
} }
} }

@ -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;
} }
} }

@ -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)",

@ -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" />

@ -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);
} }
} }
} }

@ -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)

@ -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;
} }
} }

@ -205,6 +205,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;
} }
} }

@ -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;
} }
} }

@ -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;
} }
} }

@ -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;
} }
} }

@ -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)",

@ -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" />

@ -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;
} }
} }

@ -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;
} }
} }

@ -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;
} }
} }

@ -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;
} }
} }

@ -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;
} }
} }

@ -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>();

@ -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 System.IO; using System.IO;
using System.Text; using System.Text;
using NUnit.Framework; using NUnit.Framework;
@ -14,9 +15,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test] [Test]
public void TestReadLineByLine() public void TestReadLineByLine()
{ {
const string contents = @"line 1 const string contents = "line 1\rline 2\nline 3";
line 2
line 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
@ -31,9 +30,7 @@ line 3";
[Test] [Test]
public void TestPeekLineOnce() public void TestPeekLineOnce()
{ {
const string contents = @"line 1 const string contents = "line 1\r\npeek this\nline 3";
peek this
line 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
@ -49,9 +46,7 @@ line 3";
[Test] [Test]
public void TestPeekLineMultipleTimes() public void TestPeekLineMultipleTimes()
{ {
const string contents = @"peek this once const string contents = "peek this once\nline 2\rpeek this a lot";
line 2
peek this a lot";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
@ -70,8 +65,7 @@ peek this a lot";
[Test] [Test]
public void TestPeekLineAtEndOfStream() public void TestPeekLineAtEndOfStream()
{ {
const string contents = @"first line const string contents = "first line\r\nsecond line";
second line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
@ -100,8 +94,7 @@ second line";
[Test] [Test]
public void TestReadToEndNoPeeks() public void TestReadToEndNoPeeks()
{ {
const string contents = @"first line const string contents = "first line\r\nsecond line";
second line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
@ -113,20 +106,19 @@ second line";
[Test] [Test]
public void TestReadToEndAfterReadsAndPeeks() public void TestReadToEndAfterReadsAndPeeks()
{ {
const string contents = @"this line is gone const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not";
this one shouldn't be
these ones
definitely not";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents))) using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream)) using (var bufferedReader = new LineBufferedReader(stream))
{ {
Assert.AreEqual("this line is gone", bufferedReader.ReadLine()); Assert.AreEqual("this line is gone", bufferedReader.ReadLine());
Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine()); Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine());
const string ending = @"this one shouldn't be
these ones var endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
definitely not"; Assert.AreEqual(3, endingLines.Length);
Assert.AreEqual(ending, bufferedReader.ReadToEnd()); Assert.AreEqual("this one shouldn't be", endingLines[0]);
Assert.AreEqual("these ones", endingLines[1]);
Assert.AreEqual("definitely not", endingLines[2]);
} }
} }
} }

@ -11,13 +11,13 @@ namespace osu.Game.Tests.Resources
{ {
public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}"); public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz"); public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport() public static string GetTestBeatmapForImport(bool virtualTrack = false)
{ {
var temp = Path.GetTempFileName() + ".osz"; var temp = Path.GetTempFileName() + ".osz";
using (var stream = GetTestBeatmapStream()) using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(temp)) using (var newFile = File.Create(temp))
stream.CopyTo(newFile); stream.CopyTo(newFile);

@ -0,0 +1,213 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
public class TestSceneBeatSnapGrid : EditorClockTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
private TestBeatSnapGrid grid;
public TestSceneBeatSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
createGrid();
}
[SetUp]
public void Setup() => Schedule(() =>
{
Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
BeatDivisor.Value = 1;
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestInitialBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
createGrid();
float expectedDistance = (float)beat_length / divisor;
AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance));
}
[Test]
public void TestChangeBeatDivisor()
{
createGrid();
AddStep("set beat divisor = 2", () => BeatDivisor.Value = 2);
const float expected_distance = (float)beat_length / 2;
AddAssert($"spacing is {expected_distance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expected_distance));
}
[TestCase(100)]
[TestCase(200)]
public void TestBeatLength(double beatLength)
{
AddStep($"set beat length = {beatLength}", () =>
{
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength });
});
createGrid();
AddAssert($"spacing is {beatLength}", () => Precision.AlmostEquals(grid.DistanceSpacing, beatLength));
}
[TestCase(1)]
[TestCase(2)]
public void TestGridVelocity(float velocity)
{
createGrid(g => g.Velocity = velocity);
float expectedDistance = (float)beat_length * velocity;
AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance));
}
[Test]
public void TestGetSnappedTime()
{
createGrid();
Vector2 snapPosition = Vector2.Zero;
AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0));
AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(snapPosition), 0.01));
createGrid(g => g.Velocity = 2, "with velocity = 2");
AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01));
}
private void createGrid(Action<TestBeatSnapGrid> func = null, string description = null)
{
AddStep($"create grid {description ?? string.Empty}", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestBeatSnapGrid(new HitObject(), grid_position)
};
func?.Invoke(grid);
});
}
private class TestBeatSnapGrid : BeatSnapGrid
{
public new float Velocity = 1;
public new float DistanceSpacing => base.DistanceSpacing;
public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition)
: base(hitObject, centrePosition)
{
}
protected override void CreateContent(Vector2 centrePosition)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
Position = centrePosition
});
int beatIndex = 0;
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
}
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
=> Velocity;
public override Vector2 GetSnapPosition(Vector2 screenSpacePosition)
=> Vector2.Zero;
}
}
}

@ -1,8 +1,6 @@
// 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.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -11,10 +9,8 @@ using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor namespace osu.Game.Tests.Visual.Editor
{ {
[TestFixture] [TestFixture]
public class TestSceneEditorCompose : EditorClockTestScene public class TestSceneComposeScreen : EditorClockTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {

@ -5,12 +5,15 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
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.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -18,7 +21,9 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -31,11 +36,11 @@ namespace osu.Game.Tests.Visual.Menus
private const float click_padding = 25; private const float click_padding = 25;
private GameHost host; private GameHost host;
private TestOsuGame osuGame; private TestOsuGame game;
private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding)); private Vector2 backButtonPosition => game.ToScreenSpace(new Vector2(click_padding, game.LayoutRectangle.Bottom - click_padding));
private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding)); private Vector2 optionsButtonPosition => game.ToScreenSpace(new Vector2(click_padding, click_padding));
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host) private void load(GameHost host)
@ -54,23 +59,23 @@ namespace osu.Game.Tests.Visual.Menus
{ {
AddStep("Create new game instance", () => AddStep("Create new game instance", () =>
{ {
if (osuGame != null) if (game != null)
{ {
Remove(osuGame); Remove(game);
osuGame.Dispose(); game.Dispose();
} }
osuGame = new TestOsuGame(LocalStorage, API); game = new TestOsuGame(LocalStorage, API);
osuGame.SetHost(host); game.SetHost(host);
// todo: this can be removed once we can run audio trakcs without a device present // todo: this can be removed once we can run audio trakcs without a device present
// see https://github.com/ppy/osu/issues/1302 // see https://github.com/ppy/osu/issues/1302
osuGame.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles); game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(osuGame); Add(game);
}); });
AddUntilStep("Wait for load", () => osuGame.IsLoaded); AddUntilStep("Wait for load", () => game.IsLoaded);
AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen); AddUntilStep("Wait for intro", () => game.ScreenStack.CurrentScreen is IntroScreen);
confirmAtMainMenu(); confirmAtMainMenu();
} }
@ -82,11 +87,43 @@ namespace osu.Game.Tests.Visual.Menus
pushAndConfirm(() => songSelect = new TestSongSelect()); pushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show()); AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Press escape", () => pressAndRelease(Key.Escape)); pushEscape();
AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
exitViaEscapeAndConfirm(); exitViaEscapeAndConfirm();
} }
[TestCase(true)]
[TestCase(false)]
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
{
Player player = null;
WorkingBeatmap beatmap() => game.Beatmap.Value;
Track track() => beatmap().Track;
pushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Wait());
AddUntilStep("wait for selected", () => !game.Beatmap.IsDefault);
if (withUserPause)
AddStep("pause", () => game.Dependencies.Get<MusicController>().Stop());
AddStep("press enter", () => pressAndRelease(Key.Enter));
AddUntilStep("wait for player", () => (player = game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !track().IsRunning);
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
pushEscape();
AddUntilStep("wait for track playing", () => track().IsRunning);
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
}
[Test] [Test]
public void TestExitSongSelectWithClick() public void TestExitSongSelectWithClick()
{ {
@ -98,7 +135,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition)); AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered. // BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton)); AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == game.BackButton));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left)); AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden); AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
@ -122,25 +159,28 @@ namespace osu.Game.Tests.Visual.Menus
[Test] [Test]
public void TestOpenOptionsAndExitWithEscape() public void TestOpenOptionsAndExitWithEscape()
{ {
AddUntilStep("Wait for options to load", () => osuGame.Settings.IsLoaded); AddUntilStep("Wait for options to load", () => game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter)); AddStep("Enter menu", () => pressAndRelease(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => osuGame.Settings.State.Value == Visibility.Visible); AddAssert("Options overlay was opened", () => game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape)); AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden); AddAssert("Options overlay was closed", () => game.Settings.State.Value == Visibility.Hidden);
} }
private void pushAndConfirm(Func<Screen> newScreen) private void pushAndConfirm(Func<Screen> newScreen)
{ {
Screen screen = null; Screen screen = null;
AddStep("Push new screen", () => osuGame.ScreenStack.Push(screen = newScreen())); AddStep("Push new screen", () => game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded); AddUntilStep("Wait for new screen", () => game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
} }
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));
private void exitViaEscapeAndConfirm() private void exitViaEscapeAndConfirm()
{ {
AddStep("Press escape", () => pressAndRelease(Key.Escape)); pushEscape();
confirmAtMainMenu(); confirmAtMainMenu();
} }
@ -151,7 +191,7 @@ namespace osu.Game.Tests.Visual.Menus
confirmAtMainMenu(); confirmAtMainMenu();
} }
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
private void pressAndRelease(Key key) private void pressAndRelease(Key key)
{ {
@ -169,6 +209,8 @@ namespace osu.Game.Tests.Visual.Menus
public new OsuConfigManager LocalConfig => base.LocalConfig; public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
protected override Loader CreateLoader() => new TestLoader(); protected override Loader CreateLoader() => new TestLoader();
public TestOsuGame(Storage storage, IAPIProvider api) public TestOsuGame(Storage storage, IAPIProvider api)

@ -16,6 +16,7 @@ using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -34,6 +35,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private RulesetStore rulesets; private RulesetStore rulesets;
private MusicController music;
private WorkingBeatmap defaultBeatmap; private WorkingBeatmap defaultBeatmap;
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
@ -79,6 +82,11 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default));
Dependencies.Cache(music = new MusicController());
// required to get bindables attached
Add(music);
Beatmap.SetDefault(); Beatmap.SetDefault();
Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); Dependencies.Cache(config = new OsuConfigManager(LocalStorage));
@ -93,6 +101,57 @@ namespace osu.Game.Tests.Visual.SongSelect
manager?.Delete(manager.GetAllUsableBeatmapSets()); manager?.Delete(manager.GetAllUsableBeatmapSets());
}); });
[Test]
public void TestAudioResuming()
{
createSongSelect();
addRulesetImportStep(0);
addRulesetImportStep(0);
checkMusicPlaying(true);
AddStep("select first", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.First()));
checkMusicPlaying(true);
AddStep("manual pause", () => music.TogglePause());
checkMusicPlaying(false);
AddStep("select next difficulty", () => songSelect.Carousel.SelectNext(skipDifficulties: false));
checkMusicPlaying(false);
AddStep("select next set", () => songSelect.Carousel.SelectNext());
checkMusicPlaying(true);
}
[TestCase(false)]
[TestCase(true)]
public void TestAudioRemainsCorrectOnRulesetChange(bool rulesetsInSameBeatmap)
{
createSongSelect();
// start with non-osu! to avoid convert confusion
changeRuleset(1);
if (rulesetsInSameBeatmap)
AddStep("import multi-ruleset map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
manager.Import(createTestBeatmapSet(0, usableRulesets)).Wait();
});
else
{
addRulesetImportStep(1);
addRulesetImportStep(0);
}
checkMusicPlaying(true);
AddStep("manual pause", () => music.TogglePause());
checkMusicPlaying(false);
changeRuleset(0);
checkMusicPlaying(!rulesetsInSameBeatmap);
}
[Test] [Test]
public void TestDummy() public void TestDummy()
{ {
@ -128,12 +187,10 @@ namespace osu.Game.Tests.Visual.SongSelect
} }
[Test] [Test]
[Ignore("needs fixing")]
public void TestImportUnderDifferentRuleset() public void TestImportUnderDifferentRuleset()
{ {
createSongSelect(); createSongSelect();
changeRuleset(2); addRulesetImportStep(2);
addRulesetImportStep(0);
AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
} }
@ -224,6 +281,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private static int importId; private static int importId;
private int getImportId() => ++importId; private int getImportId() => ++importId;
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods);
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));

@ -10,7 +10,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" />

@ -11,7 +11,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" />

@ -1,15 +1,30 @@
// 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.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
using SharpCompress.Common;
namespace osu.Game.IO.Archives namespace osu.Game.IO.Archives
{ {
public sealed class ZipArchiveReader : ArchiveReader public sealed class ZipArchiveReader : ArchiveReader
{ {
/// <summary>
/// List of substrings that indicate a file should be ignored during the import process
/// (usually due to representing no useful data and being autogenerated by the OS).
/// </summary>
private static readonly string[] filename_ignore_list =
{
// Mac-specific
"__MACOSX",
".DS_Store",
// Windows-specific
"Thumbs.db"
};
private readonly Stream archiveStream; private readonly Stream archiveStream;
private readonly ZipArchive archive; private readonly ZipArchive archive;
@ -43,7 +58,9 @@ namespace osu.Game.IO.Archives
archiveStream.Dispose(); archiveStream.Dispose();
} }
public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ToArray(); private static bool canBeIgnored(IEntry entry) => filename_ignore_list.Any(ignoredName => entry.Key.IndexOf(ignoredName, StringComparison.OrdinalIgnoreCase) >= 0);
public override IEnumerable<string> Filenames => archive.Entries.Where(e => !canBeIgnored(e)).Select(e => e.Key).ToArray();
public override Stream GetUnderlyingStream() => archiveStream; public override Stream GetUnderlyingStream() => archiveStream;
} }

@ -1,12 +1,9 @@
// 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.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUsersRequest : APIRequest<List<APIUser>> public class GetUsersRequest : APIRequest<GetUsersResponse>
{ {
protected override string Target => @"rankings/osu/performance"; protected override string Target => @"rankings/osu/performance";
} }

@ -0,0 +1,15 @@
// 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.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUsersResponse : ResponseWithCursor
{
[JsonProperty("ranking")]
public List<APIUser> Users;
}
}

@ -0,0 +1,16 @@
// 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 Newtonsoft.Json;
namespace osu.Game.Online.API.Requests
{
public abstract class ResponseWithCursor
{
/// <summary>
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
/// </summary>
[JsonProperty("cursor")]
public dynamic CursorJson;
}
}

@ -2,19 +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 Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class SearchBeatmapSetsResponse public class SearchBeatmapSetsResponse : ResponseWithCursor
{ {
public IEnumerable<APIBeatmapSet> BeatmapSets; public IEnumerable<APIBeatmapSet> BeatmapSets;
/// <summary>
/// A collection of parameters which should be passed to the search endpoint to fetch the next page.
/// </summary>
[JsonProperty("cursor")]
public dynamic CursorJson;
} }
} }

@ -98,20 +98,13 @@ namespace osu.Game.Overlays
/// <summary> /// <summary>
/// Start playing the current track (if not already playing). /// Start playing the current track (if not already playing).
/// </summary> /// </summary>
public void Play()
{
if (!IsPlaying)
TogglePause();
}
/// <summary>
/// Toggle pause / play.
/// </summary>
/// <returns>Whether the operation was successful.</returns> /// <returns>Whether the operation was successful.</returns>
public bool TogglePause() public bool Play(bool restart = false)
{ {
var track = current?.Track; var track = current?.Track;
IsUserPaused = false;
if (track == null) if (track == null)
{ {
if (beatmap.Disabled) if (beatmap.Disabled)
@ -121,16 +114,38 @@ namespace osu.Game.Overlays
return true; return true;
} }
if (track.IsRunning) if (restart)
{ track.Restart();
IsUserPaused = true; else if (!IsPlaying)
track.Stop();
}
else
{
track.Start(); track.Start();
IsUserPaused = false;
} return true;
}
/// <summary>
/// Stop playing the current track and pause at the current position.
/// </summary>
public void Stop()
{
var track = current?.Track;
IsUserPaused = true;
if (track?.IsRunning == true)
track.Stop();
}
/// <summary>
/// Toggle pause / play.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool TogglePause()
{
var track = current?.Track;
if (track?.IsRunning == true)
Stop();
else
Play();
return true; return true;
} }

@ -118,7 +118,7 @@ namespace osu.Game.Overlays
default: default:
var userRequest = new GetUsersRequest(); // TODO filter arguments! var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User)); userRequest.Success += res => updateUsers(res.Users.Select(r => r.User));
API.Queue(getUsersRequest = userRequest); API.Queue(getUsersRequest = userRequest);
break; break;
} }

@ -45,6 +45,11 @@ namespace osu.Game.Rulesets.Edit
/// </summary> /// </summary>
public readonly DrawableHitObject HitObject; public readonly DrawableHitObject HitObject;
/// <summary>
/// The screen-space position of <see cref="HitObject"/> prior to handling a movement event.
/// </summary>
internal Vector2 ScreenSpaceMovementStartPosition { get; private set; }
protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected; protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected;
public override bool HandlePositionalInput => ShouldBeAlive; public override bool HandlePositionalInput => ShouldBeAlive;
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
@ -131,7 +136,11 @@ namespace osu.Game.Rulesets.Edit
return base.OnClick(e); return base.OnClick(e);
} }
protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDragStart(DragStartEvent e)
{
ScreenSpaceMovementStartPosition = HitObject.ToScreenSpace(HitObject.OriginPosition);
return true;
}
protected override bool OnDrag(DragEvent e) protected override bool OnDrag(DragEvent e)
{ {

@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
UpdateInitialTransforms(); UpdateInitialTransforms();
var judgementOffset = Math.Min(HitObject.HitWindows?.WindowFor(HitResult.Miss) ?? double.MaxValue, Result?.TimeOffset ?? 0); var judgementOffset = Result?.TimeOffset ?? 0;
using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true)) using (BeginDelayedSequence(InitialLifetimeOffset + judgementOffset, true))
{ {
@ -379,7 +379,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
// Ensure that the judgement is given a valid time offset, because this may not get set by the caller // Ensure that the judgement is given a valid time offset, because this may not get set by the caller
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Result.TimeOffset = Time.Current - endTime;
Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime);
switch (Result.Type) switch (Result.Type)
{ {

@ -66,7 +66,6 @@ namespace osu.Game.Rulesets.Objects
/// <summary> /// <summary>
/// The hit windows for this <see cref="HitObject"/>. /// The hit windows for this <see cref="HitObject"/>.
/// </summary> /// </summary>
[CanBeNull]
public HitWindows HitWindows { get; set; } public HitWindows HitWindows { get; set; }
private readonly List<HitObject> nestedHitObjects = new List<HitObject>(); private readonly List<HitObject> nestedHitObjects = new List<HitObject>();
@ -113,10 +112,7 @@ namespace osu.Game.Rulesets.Objects
nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
foreach (var h in nestedHitObjects) foreach (var h in nestedHitObjects)
{
h.HitWindows = HitWindows;
h.ApplyDefaults(controlPointInfo, difficulty); h.ApplyDefaults(controlPointInfo, difficulty);
}
} }
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
@ -147,7 +143,7 @@ namespace osu.Game.Rulesets.Objects
/// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter{T}"/>. /// This will only be invoked if <see cref="HitWindows"/> hasn't been set externally (e.g. from a <see cref="BeatmapConverter{T}"/>.
/// </para> /// </para>
/// </summary> /// </summary>
[CanBeNull] [NotNull]
protected virtual HitWindows CreateHitWindows() => new HitWindows(); protected virtual HitWindows CreateHitWindows() => new HitWindows();
} }
} }

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
{ {
public float X { get; set; } public float X { get; set; }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
{ {
public float X { get; set; } public float X { get; set; }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania
public float X { get; set; } public float X { get; set; }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public int ComboOffset { get; set; } public int ComboOffset { get; set; }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -22,6 +22,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public int ComboOffset { get; set; } public int ComboOffset { get; set; }
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu
public float Y => Position.Y; public float Y => Position.Y;
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
public bool NewCombo { get; set; } public bool NewCombo { get; set; }

@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// </summary> /// </summary>
internal sealed class ConvertHit : HitObject internal sealed class ConvertHit : HitObject
{ {
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
/// </summary> /// </summary>
internal sealed class ConvertSlider : Legacy.ConvertSlider internal sealed class ConvertSlider : Legacy.ConvertSlider
{ {
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
protected override HitWindows CreateHitWindows() => null; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
} }
} }

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -30,6 +32,18 @@ namespace osu.Game.Rulesets.Scoring
private double meh; private double meh;
private double miss; private double miss;
/// <summary>
/// An empty <see cref="HitWindows"/> with only <see cref="HitResult.Miss"/> and <see cref="HitResult.Perfect"/>.
/// No time values are provided (meaning instantaneous hit or miss).
/// </summary>
public static HitWindows Empty => new EmptyHitWindows();
public HitWindows()
{
Debug.Assert(GetRanges().Any(r => r.Result == HitResult.Miss), $"{nameof(GetRanges)} should always contain {nameof(HitResult.Miss)}");
Debug.Assert(GetRanges().Any(r => r.Result != HitResult.Miss), $"{nameof(GetRanges)} should always contain at least one result type other than {nameof(HitResult.Miss)}.");
}
/// <summary> /// <summary>
/// Retrieves the <see cref="HitResult"/> with the largest hit window that produces a successful hit. /// Retrieves the <see cref="HitResult"/> with the largest hit window that produces a successful hit.
/// </summary> /// </summary>
@ -168,6 +182,29 @@ namespace osu.Game.Rulesets.Scoring
/// Defaults are provided but can be overridden to customise for a ruleset. /// Defaults are provided but can be overridden to customise for a ruleset.
/// </summary> /// </summary>
protected virtual DifficultyRange[] GetRanges() => base_ranges; protected virtual DifficultyRange[] GetRanges() => base_ranges;
public class EmptyHitWindows : HitWindows
{
private static readonly DifficultyRange[] ranges =
{
new DifficultyRange(HitResult.Perfect, 0, 0, 0),
new DifficultyRange(HitResult.Miss, 0, 0, 0),
};
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Miss:
return true;
}
return false;
}
protected override DifficultyRange[] GetRanges() => ranges;
}
} }
public struct DifficultyRange public struct DifficultyRange

@ -426,11 +426,11 @@ namespace osu.Game.Rulesets.UI
{ {
foreach (var h in Objects) foreach (var h in Objects)
{ {
if (h.HitWindows != null) if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return h.HitWindows; return h.HitWindows;
foreach (var n in h.NestedHitObjects) foreach (var n in h.NestedHitObjects)
if (n.HitWindows != null) if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return n.HitWindows; return n.HitWindows;
} }

@ -0,0 +1,153 @@
// 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.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
public abstract class BeatSnapGrid : CompositeDrawable
{
/// <summary>
/// The velocity of the beatmap at the point of placement in pixels per millisecond.
/// </summary>
protected double Velocity { get; private set; }
/// <summary>
/// The spacing between each tick of the beat snapping grid.
/// </summary>
protected float DistanceSpacing { get; private set; }
/// <summary>
/// The position which the grid is centred on.
/// The first beat snapping tick is located at <see cref="CentrePosition"/> + <see cref="DistanceSpacing"/> in the desired direction.
/// </summary>
protected readonly Vector2 CentrePosition;
[Resolved]
private IEditorBeatmap beatmap { get; set; }
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
[Resolved]
private OsuColour colours { get; set; }
private readonly Cached gridCache = new Cached();
private readonly HitObject hitObject;
private double startTime;
private double beatLength;
protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition)
{
this.hitObject = hitObject;
this.CentrePosition = centrePosition;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
startTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime;
beatLength = beatmap.ControlPointInfo.TimingPointAt(startTime).BeatLength;
}
protected override void LoadComplete()
{
base.LoadComplete();
beatDivisor.BindValueChanged(_ => updateSpacing(), true);
}
private void updateSpacing()
{
Velocity = GetVelocity(startTime, beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
DistanceSpacing = (float)(beatLength / beatDivisor.Value * Velocity);
gridCache.Invalidate();
}
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)
{
if ((invalidation & Invalidation.RequiredParentSizeToFit) > 0)
gridCache.Invalidate();
return base.Invalidate(invalidation, source, shallPropagate);
}
protected override void Update()
{
base.Update();
if (!gridCache.IsValid)
{
ClearInternal();
CreateContent(CentrePosition);
gridCache.Validate();
}
}
/// <summary>
/// Creates the content which visualises the grid ticks.
/// </summary>
protected abstract void CreateContent(Vector2 centrePosition);
/// <summary>
/// Retrieves the velocity of gameplay at a point in time in pixels per millisecond.
/// </summary>
/// <param name="time">The time to retrieve the velocity at.</param>
/// <param name="controlPointInfo">The beatmap's <see cref="ControlPointInfo"/> at the point in time.</param>
/// <param name="difficulty">The beatmap's <see cref="BeatmapDifficulty"/> at the point in time.</param>
/// <returns>The velocity.</returns>
protected abstract float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty);
/// <summary>
/// Snaps a position to this grid.
/// </summary>
/// <param name="position">The original position in coordinate space local to this <see cref="BeatSnapGrid"/>.</param>
/// <returns>The snapped position in coordinate space local to this <see cref="BeatSnapGrid"/>.</returns>
public abstract Vector2 GetSnapPosition(Vector2 position);
/// <summary>
/// Retrieves the time at a snapped position.
/// </summary>
/// <param name="position">The snapped position in coordinate space local to this <see cref="BeatSnapGrid"/>.</param>
/// <returns>The time at the snapped position.</returns>
public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity;
/// <summary>
/// Retrieves the applicable colour for a beat index.
/// </summary>
/// <param name="index">The 0-based beat index.</param>
/// <returns>The applicable colour.</returns>
protected ColourInfo GetColourForBeatIndex(int index)
{
int beat = (index + 1) % beatDivisor.Value;
ColourInfo colour = colours.Gray5;
for (int i = 0; i < BindableBeatDivisor.VALID_DIVISORS.Length; i++)
{
int divisor = BindableBeatDivisor.VALID_DIVISORS[i];
if ((beat * divisor) % beatDivisor.Value == 0)
{
colour = BindableBeatDivisor.GetColourFor(divisor, colours);
break;
}
}
int repeatIndex = index / beatDivisor.Value;
return colour.MultiplyAlpha(0.5f / (repeatIndex + 1));
}
}
}

@ -216,7 +216,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state);
private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent)
{
var movePosition = blueprint.ScreenSpaceMovementStartPosition + dragEvent.ScreenSpaceMousePosition - dragEvent.ScreenSpaceMouseDownPosition;
selectionHandler.HandleMovement(new MoveSelectionEvent(blueprint, blueprint.ScreenSpaceMovementStartPosition, movePosition));
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {

@ -0,0 +1,46 @@
// 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.Edit;
using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components
{
/// <summary>
/// An event which occurs when a <see cref="SelectionBlueprint"/> is moved.
/// </summary>
public class MoveSelectionEvent
{
/// <summary>
/// The <see cref="SelectionBlueprint"/> that triggered this <see cref="MoveSelectionEvent"/>.
/// </summary>
public readonly SelectionBlueprint Blueprint;
/// <summary>
/// The starting screen-space position of the hitobject.
/// </summary>
public readonly Vector2 ScreenSpaceStartPosition;
/// <summary>
/// The expected screen-space position of the hitobject at the current cursor position.
/// </summary>
public readonly Vector2 ScreenSpacePosition;
/// <summary>
/// The distance between <see cref="ScreenSpacePosition"/> and the hitobject's current position, in the coordinate-space of the hitobject's parent.
/// </summary>
/// <remarks>
/// This does not use <see cref="ScreenSpaceStartPosition"/> and does not represent the cumulative movement distance.
/// </remarks>
public readonly Vector2 InstantDelta;
public MoveSelectionEvent(SelectionBlueprint blueprint, Vector2 screenSpaceStartPosition, Vector2 screenSpacePosition)
{
Blueprint = blueprint;
ScreenSpaceStartPosition = screenSpaceStartPosition;
ScreenSpacePosition = screenSpacePosition;
InstantDelta = Blueprint.HitObject.Parent.ToLocalSpace(ScreenSpacePosition) - Blueprint.HitObject.Position;
}
}
}

@ -65,11 +65,10 @@ namespace osu.Game.Screens.Edit.Compose.Components
#region User Input Handling #region User Input Handling
/// <summary> /// <summary>
/// Handles the selected <see cref="DrawableHitObject"/>s being dragged. /// Handles the selected <see cref="DrawableHitObject"/>s being moved.
/// </summary> /// </summary>
/// <param name="blueprint">The <see cref="SelectionBlueprint"/> that received the drag event.</param> /// <param name="moveEvent">The move event.</param>
/// <param name="dragEvent">The drag event.</param> public virtual void HandleMovement(MoveSelectionEvent moveEvent)
public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
{ {
} }

@ -1,132 +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 JetBrains.Annotations;
using osu.Framework.Allocation;
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.Shapes;
using osu.Framework.Logging;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Compose namespace osu.Game.Screens.Edit.Compose
{ {
public class ComposeScreen : EditorScreen public class ComposeScreen : EditorScreenWithTimeline
{ {
private const float vertical_margins = 10; protected override Drawable CreateMainContent()
private const float horizontal_margins = 20;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private HitObjectComposer composer;
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{ {
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
Container composerContainer;
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
Name = "Timeline",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f)
},
new Container
{
Name = "Timeline content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new TimelineArea { RelativeSizeAxes = Axes.Both }
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 90),
}
},
}
}
}
},
new Drawable[]
{
composerContainer = new Container
{
Name = "Composer content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
}
}
},
RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
},
};
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
if (ruleset == null) var composer = ruleset?.CreateHitObjectComposer();
if (composer != null)
{ {
Logger.Log("Beatmap doesn't have a ruleset assigned."); var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// ExitRequested?.Invoke();
return; // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(ruleset.CreateHitObjectComposer()));
} }
composer = ruleset.CreateHitObjectComposer(); return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer");
if (composer == null)
{
Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition.");
// ExitRequested?.Invoke();
return;
}
var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin);
// the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation
// full access to all skin sources.
var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider));
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
composerContainer.Add(
beatmapSkinProvider.WithChild(
rulesetSkinProvider.WithChild(composer)));
} }
} }
} }

@ -1,52 +1,13 @@
// 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;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Design namespace osu.Game.Screens.Edit.Design
{ {
public class DesignScreen : EditorScreen public class DesignScreen : EditorScreen
{ {
public DesignScreen() public DesignScreen()
{ {
Add(new Container Child = new ScreenWhiteBox.UnderConstructionMessage("Design mode");
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.35f
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f
},
new Container
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding(20),
Child = new OsuSpriteText { Text = "Design screen" }
}
}
}
}
});
} }
} }
} }

@ -19,13 +19,15 @@ using osu.Framework.Timing;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components;
using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Components.Menus;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Design; using osu.Game.Screens.Edit.Design;
using osuTK.Input; using osuTK.Input;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework; using osu.Framework;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Setup;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
@ -258,6 +260,10 @@ namespace osu.Game.Screens.Edit
switch (e.NewValue) switch (e.NewValue)
{ {
case EditorScreenMode.SongSetup:
currentScreen = new SetupScreen();
break;
case EditorScreenMode.Compose: case EditorScreenMode.Compose:
currentScreen = new ComposeScreen(); currentScreen = new ComposeScreen();
break; break;
@ -266,8 +272,8 @@ namespace osu.Game.Screens.Edit
currentScreen = new DesignScreen(); currentScreen = new DesignScreen();
break; break;
default: case EditorScreenMode.Timing:
currentScreen = new EditorScreen(); currentScreen = new TimingScreen();
break; break;
} }

@ -10,16 +10,17 @@ using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
/// <summary> /// <summary>
/// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. /// TODO: eventually make this inherit Screen and add a local screen stack inside the Editor.
/// </summary> /// </summary>
public class EditorScreen : Container public abstract class EditorScreen : Container
{ {
protected readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>(); [Resolved]
protected IBindable<WorkingBeatmap> Beatmap { get; private set; }
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container content; private readonly Container content;
public EditorScreen() protected EditorScreen()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -28,12 +29,6 @@ namespace osu.Game.Screens.Edit
InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
} }
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap)
{
Beatmap.BindTo(beatmap);
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();

@ -0,0 +1,107 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osuTK.Graphics;
namespace osu.Game.Screens.Edit
{
public abstract class EditorScreenWithTimeline : EditorScreen
{
private const float vertical_margins = 10;
private const float horizontal_margins = 20;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[BackgroundDependencyLoader(true)]
private void load([CanBeNull] BindableBeatDivisor beatDivisor)
{
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
Container mainContent;
Children = new Drawable[]
{
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
Name = "Timeline",
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f)
},
new Container
{
Name = "Timeline content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = CreateTimeline()
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 90),
}
},
}
}
}
},
new Drawable[]
{
mainContent = new Container
{
Name = "Main content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
}
}
},
RowDimensions = new[] { new Dimension(GridSizeMode.Absolute, 110) }
},
};
LoadComponentAsync(CreateMainContent(), content =>
{
mainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint);
});
}
protected abstract Drawable CreateMainContent();
protected virtual Drawable CreateTimeline() => new TimelineArea { RelativeSizeAxes = Axes.Both };
}
}

@ -0,0 +1,13 @@
// 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.
namespace osu.Game.Screens.Edit.Setup
{
public class SetupScreen : EditorScreen
{
public SetupScreen()
{
Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode");
}
}
}

@ -0,0 +1,13 @@
// 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.
namespace osu.Game.Screens.Edit.Timing
{
public class TimingScreen : EditorScreen
{
public TimingScreen()
{
Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode");
}
}
}

@ -2,87 +2,42 @@
// 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.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.IO.Archives;
namespace osu.Game.Screens.Menu namespace osu.Game.Screens.Menu
{ {
public class IntroCircles : IntroScreen public class IntroCircles : IntroScreen
{ {
private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; protected override string BeatmapHash => "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83";
private SampleChannel welcome; protected override string BeatmapFile => "circles.osz";
private Bindable<bool> menuMusic;
private Track track;
private WorkingBeatmap introBeatmap;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, ISampleStore samples)
{
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
BeatmapSetInfo setInfo = null;
if (!menuMusic.Value)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count > 0)
setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
}
if (setInfo == null)
{
setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash);
if (setInfo == null)
{
// we need to import the default menu background beatmap
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")).Result;
setInfo.Protected = true;
beatmaps.Update(setInfo);
}
}
introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
track = introBeatmap.Track;
if (config.Get<bool>(OsuSetting.MenuVoice))
welcome = samples.Get(@"welcome");
}
private const double delay_step_one = 2300; private const double delay_step_one = 2300;
private const double delay_step_two = 600; private const double delay_step_two = 600;
private SampleChannel welcome;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
if (MenuVoice.Value)
welcome = audio.Samples.Get(@"welcome");
}
protected override void LogoArriving(OsuLogo logo, bool resuming) protected override void LogoArriving(OsuLogo logo, bool resuming)
{ {
base.LogoArriving(logo, resuming); base.LogoArriving(logo, resuming);
if (!resuming) if (!resuming)
{ {
Beatmap.Value = introBeatmap;
introBeatmap = null;
welcome?.Play(); welcome?.Play();
Scheduler.AddDelayed(delegate Scheduler.AddDelayed(delegate
{ {
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. StartTrack();
if (menuMusic.Value)
{
track.Restart();
track = null;
}
PrepareMenuLoad(); PrepareMenuLoad();
@ -97,8 +52,6 @@ namespace osu.Game.Screens.Menu
public override void OnSuspending(IScreen next) public override void OnSuspending(IScreen next)
{ {
track = null;
this.FadeOut(300); this.FadeOut(300);
base.OnSuspending(next); base.OnSuspending(next);
} }

@ -4,12 +4,16 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.IO.Archives;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -17,51 +21,90 @@ namespace osu.Game.Screens.Menu
{ {
public abstract class IntroScreen : StartupScreen public abstract class IntroScreen : StartupScreen
{ {
private readonly BindableDouble exitingVolumeFade = new BindableDouble(1);
public const int EXIT_DELAY = 3000;
[Resolved]
private AudioManager audio { get; set; }
private SampleChannel seeya;
private Bindable<bool> menuVoice;
private LeasedBindable<WorkingBeatmap> beatmap;
public new Bindable<WorkingBeatmap> Beatmap => beatmap;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
{
// prevent user from changing beatmap while the intro is still runnning.
beatmap = base.Beatmap.BeginLease(false);
menuVoice = config.GetBindable<bool>(OsuSetting.MenuVoice);
seeya = audio.Samples.Get(@"seeya");
}
/// <summary> /// <summary>
/// Whether we have loaded the menu previously. /// Whether we have loaded the menu previously.
/// </summary> /// </summary>
public bool DidLoadMenu { get; private set; } public bool DidLoadMenu { get; private set; }
public override bool OnExiting(IScreen next) /// <summary>
/// A hash used to find the associated beatmap if already imported.
/// </summary>
protected abstract string BeatmapHash { get; }
/// <summary>
/// A source file to use as an import source if the intro beatmap is not yet present.
/// Should be within the "Tracks" namespace of game resources.
/// </summary>
protected abstract string BeatmapFile { get; }
protected IBindable<bool> MenuVoice { get; private set; }
protected IBindable<bool> MenuMusic { get; private set; }
private WorkingBeatmap introBeatmap;
protected Track Track { get; private set; }
private readonly BindableDouble exitingVolumeFade = new BindableDouble(1);
private const int exit_delay = 3000;
private SampleChannel seeya;
private LeasedBindable<WorkingBeatmap> beatmap;
private MainMenu mainMenu;
[Resolved]
private AudioManager audio { get; set; }
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game)
{ {
//cancel exiting if we haven't loaded the menu yet. // prevent user from changing beatmap while the intro is still runnning.
return !DidLoadMenu; beatmap = Beatmap.BeginLease(false);
MenuVoice = config.GetBindable<bool>(OsuSetting.MenuVoice);
MenuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
seeya = audio.Samples.Get(@"seeya");
BeatmapSetInfo setInfo = null;
if (!MenuMusic.Value)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count > 0)
setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
}
if (setInfo == null)
{
setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash);
if (setInfo == null)
{
// we need to import the default menu background beatmap
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result;
setInfo.Protected = true;
beatmaps.Update(setInfo);
}
}
introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
Track = introBeatmap.Track;
} }
public override bool OnExiting(IScreen next) => !DidLoadMenu;
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
this.FadeIn(300); this.FadeIn(300);
double fadeOutTime = EXIT_DELAY; double fadeOutTime = exit_delay;
//we also handle the exit transition. //we also handle the exit transition.
if (menuVoice.Value) if (MenuVoice.Value)
seeya.Play(); seeya.Play();
else else
fadeOutTime = 500; fadeOutTime = 500;
@ -75,6 +118,21 @@ namespace osu.Game.Screens.Menu
base.OnResuming(last); base.OnResuming(last);
} }
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
Track = null;
}
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
protected void StartTrack()
{
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
if (MenuMusic.Value)
Track.Restart();
}
protected override void LogoArriving(OsuLogo logo, bool resuming) protected override void LogoArriving(OsuLogo logo, bool resuming)
{ {
base.LogoArriving(logo, resuming); base.LogoArriving(logo, resuming);
@ -85,6 +143,9 @@ namespace osu.Game.Screens.Menu
if (!resuming) if (!resuming)
{ {
beatmap.Value = introBeatmap;
introBeatmap = null;
logo.MoveTo(new Vector2(0.5f)); logo.MoveTo(new Vector2(0.5f));
logo.ScaleTo(Vector2.One); logo.ScaleTo(Vector2.One);
logo.Hide(); logo.Hide();
@ -92,7 +153,7 @@ namespace osu.Game.Screens.Menu
else else
{ {
const int quick_appear = 350; const int quick_appear = 350;
int initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0; var initialMovementTime = logo.Alpha > 0.2f ? quick_appear : 0;
logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint); logo.MoveTo(new Vector2(0.5f), initialMovementTime, Easing.OutQuint);
@ -100,17 +161,12 @@ namespace osu.Game.Screens.Menu
.ScaleTo(1, initialMovementTime, Easing.OutQuint) .ScaleTo(1, initialMovementTime, Easing.OutQuint)
.FadeIn(quick_appear, Easing.OutQuint) .FadeIn(quick_appear, Easing.OutQuint)
.Then() .Then()
.RotateTo(20, EXIT_DELAY * 1.5f) .RotateTo(20, exit_delay * 1.5f)
.FadeOut(EXIT_DELAY); .FadeOut(exit_delay);
} }
} }
private MainMenu mainMenu; protected void PrepareMenuLoad() => LoadComponentAsync(mainMenu = new MainMenu());
protected void PrepareMenuLoad()
{
LoadComponentAsync(mainMenu = new MainMenu());
}
protected void LoadMenu() protected void LoadMenu()
{ {

@ -7,8 +7,6 @@ using System.IO;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -17,12 +15,9 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video; using osu.Framework.Graphics.Video;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.IO.Archives;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osuTK; using osuTK;
@ -32,9 +27,9 @@ namespace osu.Game.Screens.Menu
{ {
public class IntroTriangles : IntroScreen public class IntroTriangles : IntroScreen
{ {
private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5"; protected override string BeatmapHash => "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5";
private SampleChannel welcome; protected override string BeatmapFile => "triangles.osz";
protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false)
{ {
@ -44,47 +39,14 @@ namespace osu.Game.Screens.Menu
[Resolved] [Resolved]
private AudioManager audio { get; set; } private AudioManager audio { get; set; }
private Bindable<bool> menuMusic;
private Track track;
private WorkingBeatmap introBeatmap;
private BackgroundScreenDefault background; private BackgroundScreenDefault background;
private SampleChannel welcome;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) private void load(AudioManager audio)
{ {
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic); if (MenuVoice.Value && !MenuMusic.Value)
BeatmapSetInfo setInfo = null;
if (!menuMusic.Value)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count > 0)
setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
}
if (setInfo == null)
{
setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash);
if (setInfo == null)
{
// we need to import the default menu background beatmap
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result;
setInfo.Protected = true;
beatmaps.Update(setInfo);
}
}
introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
track = introBeatmap.Track;
track.Reset();
if (config.Get<bool>(OsuSetting.MenuVoice) && !menuMusic.Value)
// triangles has welcome sound included in the track. only play this if the user doesn't want menu music.
welcome = audio.Samples.Get(@"welcome"); welcome = audio.Samples.Get(@"welcome");
} }
@ -96,24 +58,19 @@ namespace osu.Game.Screens.Menu
if (!resuming) if (!resuming)
{ {
Beatmap.Value = introBeatmap;
introBeatmap = null;
PrepareMenuLoad(); PrepareMenuLoad();
LoadComponentAsync(new TrianglesIntroSequence(logo, background) LoadComponentAsync(new TrianglesIntroSequence(logo, background)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(menuMusic.Value ? track : null), Clock = new FramedClock(MenuMusic.Value ? Track : null),
LoadMenu = LoadMenu LoadMenu = LoadMenu
}, t => }, t =>
{ {
AddInternal(t); AddInternal(t);
welcome?.Play(); welcome?.Play();
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. StartTrack();
if (menuMusic.Value)
track.Start();
}); });
} }
} }
@ -124,12 +81,6 @@ namespace osu.Game.Screens.Menu
background.FadeOut(100); background.FadeOut(100);
} }
public override void OnSuspending(IScreen next)
{
track = null;
base.OnSuspending(next);
}
private class TrianglesIntroSequence : CompositeDrawable private class TrianglesIntroSequence : CompositeDrawable
{ {
private readonly OsuLogo logo; private readonly OsuLogo logo;

@ -48,7 +48,7 @@ namespace osu.Game.Screens.Play.HUD
private void onNewJudgement(JudgementResult result) private void onNewJudgement(JudgementResult result)
{ {
if (result.HitObject.HitWindows == null) if (result.HitObject.HitWindows.WindowFor(HitResult.Miss) == 0)
return; return;
foreach (var c in Children) foreach (var c in Children)

@ -20,38 +20,17 @@ namespace osu.Game.Screens
{ {
public class ScreenWhiteBox : OsuScreen public class ScreenWhiteBox : OsuScreen
{ {
private readonly UnderConstructionMessage message;
private const double transition_time = 1000; private const double transition_time = 1000;
protected virtual IEnumerable<Type> PossibleChildren => null; protected virtual IEnumerable<Type> PossibleChildren => null;
private readonly FillFlowContainer textContainer;
private readonly Container boxContainer;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2"); protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2");
public override void OnEntering(IScreen last)
{
base.OnEntering(last);
Alpha = 0;
textContainer.Position = new Vector2(DrawSize.X / 16, 0);
boxContainer.ScaleTo(0.2f);
boxContainer.RotateTo(-20);
using (BeginDelayedSequence(300, true))
{
boxContainer.ScaleTo(1, transition_time, Easing.OutElastic);
boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint);
textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
this.FadeIn(transition_time, Easing.OutExpo);
}
}
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
textContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo); message.TextContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo); this.FadeOut(transition_time, Easing.OutExpo);
return base.OnExiting(next); return base.OnExiting(next);
@ -61,7 +40,7 @@ namespace osu.Game.Screens
{ {
base.OnSuspending(next); base.OnSuspending(next);
textContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo); message.TextContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo);
this.FadeOut(transition_time, Easing.OutExpo); this.FadeOut(transition_time, Easing.OutExpo);
} }
@ -69,7 +48,7 @@ namespace osu.Game.Screens
{ {
base.OnResuming(last); base.OnResuming(last);
textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); message.TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
this.FadeIn(transition_time, Easing.OutExpo); this.FadeIn(transition_time, Easing.OutExpo);
} }
@ -79,65 +58,7 @@ namespace osu.Game.Screens
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
boxContainer = new Container message = new UnderConstructionMessage(GetType().Name),
{
Size = new Vector2(0.3f),
RelativeSizeAxes = Axes.Both,
CornerRadius = 20,
Masking = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = getColourFor(GetType()),
Alpha = 0.2f,
Blending = BlendingParameters.Additive,
},
textContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.Solid.UniversalAccess,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = GetType().Name,
Colour = getColourFor(GetType()).Lighten(0.8f),
Font = OsuFont.GetFont(size: 50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "is not yet ready for use!",
Font = OsuFont.GetFont(size: 20),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "please check back a bit later.",
Font = OsuFont.GetFont(size: 14),
},
}
},
}
},
childModeButtons = new FillFlowContainer childModeButtons = new FillFlowContainer
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
@ -155,24 +76,24 @@ namespace osu.Game.Screens
childModeButtons.Add(new ChildModeButton childModeButtons.Add(new ChildModeButton
{ {
Text = $@"{t.Name}", Text = $@"{t.Name}",
BackgroundColour = getColourFor(t), BackgroundColour = getColourFor(t.Name),
HoverColour = getColourFor(t).Lighten(0.2f), HoverColour = getColourFor(t.Name).Lighten(0.2f),
Action = delegate { this.Push(Activator.CreateInstance(t) as Screen); } Action = delegate { this.Push(Activator.CreateInstance(t) as Screen); }
}); });
} }
} }
} }
private Color4 getColourFor(Type type) private static Color4 getColourFor(object type)
{ {
int hash = type.Name.GetHashCode(); int hash = type.GetHashCode();
byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255); byte r = (byte)MathHelper.Clamp(((hash & 0xFF0000) >> 16) * 0.8f, 20, 255);
byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255); byte g = (byte)MathHelper.Clamp(((hash & 0x00FF00) >> 8) * 0.8f, 20, 255);
byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255); byte b = (byte)MathHelper.Clamp((hash & 0x0000FF) * 0.8f, 20, 255);
return new Color4(r, g, b, 255); return new Color4(r, g, b, 255);
} }
public class ChildModeButton : TwoLayerButton private class ChildModeButton : TwoLayerButton
{ {
public ChildModeButton() public ChildModeButton()
{ {
@ -181,5 +102,104 @@ namespace osu.Game.Screens
Origin = Anchor.BottomRight; Origin = Anchor.BottomRight;
} }
} }
public class UnderConstructionMessage : CompositeDrawable
{
public FillFlowContainer TextContainer { get; }
private readonly Container boxContainer;
public UnderConstructionMessage(string name)
{
RelativeSizeAxes = Axes.Both;
Size = new Vector2(0.3f);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
var colour = getColourFor(name);
InternalChildren = new Drawable[]
{
boxContainer = new Container
{
CornerRadius = 20,
Masking = true,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colour,
Alpha = 0.2f,
Blending = BlendingParameters.Additive,
},
TextContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.Solid.UniversalAccess,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(50),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = name,
Colour = colour.Lighten(0.8f),
Font = OsuFont.GetFont(size: 36),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "is not yet ready for use!",
Font = OsuFont.GetFont(size: 20),
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "please check back a bit later.",
Font = OsuFont.GetFont(size: 14),
},
}
},
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
TextContainer.Position = new Vector2(DrawSize.X / 16, 0);
boxContainer.Hide();
boxContainer.ScaleTo(0.2f);
boxContainer.RotateTo(-20);
using (BeginDelayedSequence(300, true))
{
boxContainer.ScaleTo(1, transition_time, Easing.OutElastic);
boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint);
TextContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo);
boxContainer.FadeIn(transition_time, Easing.OutExpo);
}
}
}
} }
} }

@ -66,11 +66,7 @@ namespace osu.Game.Screens.Select
/// </summary> /// </summary>
protected readonly Container FooterPanels; protected readonly Container FooterPanels;
protected override BackgroundScreen CreateBackground() protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap();
{
var background = new BackgroundScreenBeatmap();
return background;
}
protected readonly BeatmapCarousel Carousel; protected readonly BeatmapCarousel Carousel;
private readonly BeatmapInfoWedge beatmapInfoWedge; private readonly BeatmapInfoWedge beatmapInfoWedge;
@ -412,9 +408,6 @@ namespace osu.Game.Screens.Select
WorkingBeatmap previous = Beatmap.Value; WorkingBeatmap previous = Beatmap.Value;
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, previous);
if (this.IsCurrentScreen() && Beatmap.Value?.Track != previous?.Track)
ensurePlayingSelected(true);
if (beatmap != null) if (beatmap != null)
{ {
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID) if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
@ -424,6 +417,9 @@ namespace osu.Game.Screens.Select
} }
} }
if (this.IsCurrentScreen())
ensurePlayingSelected();
UpdateBeatmap(Beatmap.Value); UpdateBeatmap(Beatmap.Value);
} }
} }
@ -490,7 +486,9 @@ namespace osu.Game.Screens.Select
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{ {
UpdateBeatmap(Beatmap.Value); UpdateBeatmap(Beatmap.Value);
ensurePlayingSelected();
// restart playback on returning to song select, regardless.
music?.Play();
} }
base.OnResuming(last); base.OnResuming(last);
@ -581,19 +579,24 @@ namespace osu.Game.Screens.Select
beatmap.Track.Looping = true; beatmap.Track.Looping = true;
} }
private void ensurePlayingSelected(bool restart = false) private readonly WeakReference<Track> lastTrack = new WeakReference<Track>(null);
/// <summary>
/// Ensures some music is playing for the current track.
/// Will resume playback from a manual user pause if the track has changed.
/// </summary>
private void ensurePlayingSelected()
{ {
Track track = Beatmap.Value.Track; Track track = Beatmap.Value.Track;
if (!track.IsRunning) bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track;
{
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
if (restart) track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
track.Restart();
else if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack))
track.Start(); music?.Play(true);
}
lastTrack.SetTarget(track);
} }
private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s);

@ -25,8 +25,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<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" Version="2019.930.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.1011.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

@ -117,13 +117,13 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<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" Version="2019.930.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.1011.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.930.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1011.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.6.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="4.6.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.813.0" ExcludeAssets="all" /> <PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.1010.0" ExcludeAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>