mirror of
https://github.com/ppy/osu.git
synced 2025-02-21 22:12:53 +08:00
Merge branch 'master' into difficulty-adjustment-extension
This commit is contained in:
commit
be9b07a4cf
@ -15,7 +15,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"jetbrains.resharper.globaltools": {
|
"jetbrains.resharper.globaltools": {
|
||||||
"version": "2020.2.4",
|
"version": "2020.3.2",
|
||||||
"commands": [
|
"commands": [
|
||||||
"jb"
|
"jb"
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net5.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/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net5.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/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net5.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/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net5.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/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net5.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/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! SDL" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<configuration default="false" name="osu! (legacy osuTK)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--sdl" />
|
<option name="PROGRAM_PARAMETERS" value="--tk" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -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=v3.1" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Debug)",
|
"preLaunchTask": "Build osu! (Debug)",
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Release)",
|
"preLaunchTask": "Build osu! (Release)",
|
||||||
@ -31,7 +31,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
|
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net5.0/osu.Game.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Debug)",
|
"preLaunchTask": "Build tests (Debug)",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
|
"${workspaceRoot}/osu.Game.Tests/bin/Release/net5.0/osu.Game.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Release)",
|
"preLaunchTask": "Build tests (Release)",
|
||||||
@ -55,7 +55,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
|
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
|
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"name": "Benchmark",
|
"name": "Benchmark",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll",
|
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll",
|
||||||
"args": [
|
"args": [
|
||||||
"--filter",
|
"--filter",
|
||||||
"*"
|
"*"
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Code Analysis">
|
<PropertyGroup Label="Code Analysis">
|
||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||||
@ -28,9 +28,17 @@
|
|||||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<!-- DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
<!--
|
||||||
This is required due to https://github.com/NuGet/Home/issues/5740 -->
|
NU1701:
|
||||||
<NoWarn>$(NoWarn);NU1701</NoWarn>
|
DeepEqual is not netstandard-compatible. This is fine since we run tests with .NET Framework anyway.
|
||||||
|
This is required due to https://github.com/NuGet/Home/issues/5740
|
||||||
|
|
||||||
|
CA9998:
|
||||||
|
Microsoft.CodeAnalysis.FxCopAnalyzers has been deprecated.
|
||||||
|
The entire package will be able to be removed after migrating to .NET 5,
|
||||||
|
as analysers are shipped as part of the .NET 5 SDK anyway.
|
||||||
|
-->
|
||||||
|
<NoWarn>$(NoWarn);NU1701;CA9998</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
@ -40,7 +48,7 @@
|
|||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<Company>ppy Pty Ltd</Company>
|
<Company>ppy Pty Ltd</Company>
|
||||||
<Copyright>Copyright (c) 2020 ppy Pty Ltd</Copyright>
|
<Copyright>Copyright (c) 2021 ppy Pty Ltd</Copyright>
|
||||||
<PackageTags>osu game</PackageTags>
|
<PackageTags>osu game</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
56
Gemfile.lock
56
Gemfile.lock
@ -1,27 +1,27 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.2)
|
CFPropertyList (3.0.3)
|
||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.1.0)
|
aws-eventstream (1.1.0)
|
||||||
aws-partitions (1.354.0)
|
aws-partitions (1.413.0)
|
||||||
aws-sdk-core (3.104.3)
|
aws-sdk-core (3.110.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.239.0)
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-kms (1.36.0)
|
aws-sdk-kms (1.40.0)
|
||||||
aws-sdk-core (~> 3, >= 3.99.0)
|
aws-sdk-core (~> 3, >= 3.109.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.78.0)
|
aws-sdk-s3 (1.87.0)
|
||||||
aws-sdk-core (~> 3, >= 3.104.3)
|
aws-sdk-core (~> 3, >= 3.109.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.2.1)
|
aws-sigv4 (1.2.2)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.4)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
@ -29,22 +29,23 @@ GEM
|
|||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.6.1)
|
digest-crc (0.6.3)
|
||||||
rake (~> 13.0)
|
rake (>= 12.0.0, < 14.0.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.6)
|
dotenv (2.7.6)
|
||||||
emoji_regex (3.0.0)
|
emoji_regex (3.2.1)
|
||||||
excon (0.76.0)
|
excon (0.78.1)
|
||||||
faraday (1.0.1)
|
faraday (1.2.0)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
ruby2_keywords
|
||||||
faraday (>= 0.7.4)
|
faraday-cookie_jar (0.0.7)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (1.0.0)
|
faraday_middleware (1.0.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.2.0)
|
fastimage (2.2.1)
|
||||||
fastlane (2.156.0)
|
fastlane (2.170.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)
|
||||||
aws-sdk-s3 (~> 1.0)
|
aws-sdk-s3 (~> 1.0)
|
||||||
@ -96,17 +97,17 @@ GEM
|
|||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.3)
|
google-cloud-env (1.4.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.1)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.27.0)
|
google-cloud-storage (1.29.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.13.1)
|
googleauth (0.14.0)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
@ -118,10 +119,10 @@ GEM
|
|||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.4.0)
|
jmespath (1.4.0)
|
||||||
json (2.3.1)
|
json (2.5.1)
|
||||||
jwt (2.2.1)
|
jwt (2.2.2)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.11.0)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
@ -132,14 +133,15 @@ GEM
|
|||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
plist (3.5.0)
|
plist (3.5.0)
|
||||||
public_suffix (4.0.5)
|
public_suffix (4.0.6)
|
||||||
rake (13.0.1)
|
rake (13.0.3)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
|
ruby2_keywords (0.0.2)
|
||||||
rubyzip (2.3.0)
|
rubyzip (2.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.14.0)
|
signet (0.14.0)
|
||||||
@ -168,7 +170,7 @@ GEM
|
|||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.18.0)
|
xcodeproj (1.19.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
2
LICENCE
2
LICENCE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020 ppy Pty Ltd <contact@ppy.sh>.
|
Copyright (c) 2021 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
# osu!
|
# osu!
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/peppy/osu)
|
[](https://ci.appveyor.com/project/peppy/osu)
|
||||||
[]()
|
[](https://github.com/ppy/osu/releases/latest)
|
||||||
[](https://www.codefactor.io/repository/github/ppy/osu)
|
[](https://www.codefactor.io/repository/github/ppy/osu)
|
||||||
[](https://discord.gg/ppy)
|
[](https://discord.gg/ppy)
|
||||||
|
|
||||||
@ -36,8 +36,7 @@ If you are looking to install or test osu! without setting up a development envi
|
|||||||
|
|
||||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||||
|
|
||||||
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
|
||||||
|
|
||||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||||
|
|
||||||
## Developing a custom ruleset
|
## Developing a custom ruleset
|
||||||
@ -50,7 +49,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
|
|||||||
|
|
||||||
Please make sure you have the following prerequisites:
|
Please make sure you have the following prerequisites:
|
||||||
|
|
||||||
- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
|
- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
|
||||||
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
|
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
|
||||||
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
||||||
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
|
||||||
|
@ -1,21 +1,68 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
image: Visual Studio 2019
|
image: Visual Studio 2019
|
||||||
dotnet_csproj:
|
|
||||||
patch: true
|
|
||||||
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
|
||||||
version: $(APPVEYOR_REPO_TAG_NAME)
|
|
||||||
before_build:
|
|
||||||
- ps: dotnet --info # Useful when version mismatch between CI and local
|
|
||||||
- ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
|
||||||
test: off
|
test: off
|
||||||
skip_non_tags: true
|
skip_non_tags: true
|
||||||
configuration: Release
|
configuration: Release
|
||||||
build:
|
|
||||||
project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
|
environment:
|
||||||
parallel: true
|
matrix:
|
||||||
verbosity: minimal
|
- job_name: osu-game
|
||||||
publish_nuget: true
|
- job_name: osu-ruleset
|
||||||
|
job_depends_on: osu-game
|
||||||
|
- job_name: taiko-ruleset
|
||||||
|
job_depends_on: osu-game
|
||||||
|
- job_name: catch-ruleset
|
||||||
|
job_depends_on: osu-game
|
||||||
|
- job_name: mania-ruleset
|
||||||
|
job_depends_on: osu-game
|
||||||
|
|
||||||
|
nuget:
|
||||||
|
project_feed: true
|
||||||
|
|
||||||
|
for:
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- job_name: osu-game
|
||||||
|
build_script:
|
||||||
|
- cmd: dotnet pack osu.Game\osu.Game.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- job_name: osu-ruleset
|
||||||
|
build_script:
|
||||||
|
- cmd: dotnet remove osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj reference osu.Game\osu.Game.csproj
|
||||||
|
- cmd: dotnet add osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||||
|
- cmd: dotnet pack osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- job_name: taiko-ruleset
|
||||||
|
build_script:
|
||||||
|
- cmd: dotnet remove osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj reference osu.Game\osu.Game.csproj
|
||||||
|
- cmd: dotnet add osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||||
|
- cmd: dotnet pack osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- job_name: catch-ruleset
|
||||||
|
build_script:
|
||||||
|
- cmd: dotnet remove osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj reference osu.Game\osu.Game.csproj
|
||||||
|
- cmd: dotnet add osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||||
|
- cmd: dotnet pack osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||||
|
-
|
||||||
|
matrix:
|
||||||
|
only:
|
||||||
|
- job_name: mania-ruleset
|
||||||
|
build_script:
|
||||||
|
- cmd: dotnet remove osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj reference osu.Game\osu.Game.csproj
|
||||||
|
- cmd: dotnet add osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||||
|
- cmd: dotnet pack osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||||
|
|
||||||
|
artifacts:
|
||||||
|
- path: '**\*.nupkg'
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: Environment
|
- provider: Environment
|
||||||
name: nuget
|
name: nuget
|
@ -48,9 +48,12 @@ desc 'Deploy to play store'
|
|||||||
|
|
||||||
desc 'Compile the project'
|
desc 'Compile the project'
|
||||||
lane :build do |options|
|
lane :build do |options|
|
||||||
nuget_restore(
|
nuget_restore(project_path: 'osu.Android/osu.Android.csproj')
|
||||||
project_path: 'osu.sln'
|
nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
|
||||||
)
|
nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
|
||||||
|
|
||||||
souyuz(
|
souyuz(
|
||||||
build_configuration: 'Release',
|
build_configuration: 'Release',
|
||||||
@ -107,9 +110,12 @@ platform :ios do
|
|||||||
|
|
||||||
desc 'Compile the project'
|
desc 'Compile the project'
|
||||||
lane :build do
|
lane :build do
|
||||||
nuget_restore(
|
nuget_restore(project_path: 'osu.iOS/osu.iOS.csproj')
|
||||||
project_path: 'osu.sln'
|
nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
|
||||||
)
|
nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
|
||||||
|
nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
|
||||||
|
|
||||||
souyuz(
|
souyuz(
|
||||||
platform: "ios",
|
platform: "ios",
|
||||||
|
10
global.json
10
global.json
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"sdk": {
|
|
||||||
"allowPrerelease": false,
|
|
||||||
"rollForward": "minor",
|
|
||||||
"version": "3.1.100"
|
|
||||||
},
|
|
||||||
"msbuild-sdks": {
|
|
||||||
"Microsoft.Build.Traversal": "3.0.2"
|
|
||||||
}
|
|
||||||
}
|
|
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.1202.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.1203.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.128.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -1,18 +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 System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
|
using Android.Net;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Android.Provider;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using osu.Framework.Android;
|
using osu.Framework.Android;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance)]
|
||||||
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream" })]
|
||||||
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
protected override Framework.Game CreateGame() => new OsuGameAndroid(this);
|
private static readonly string[] osu_url_schemes = { "osu", "osump" };
|
||||||
|
|
||||||
|
private OsuGameAndroid game;
|
||||||
|
|
||||||
|
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
@ -23,8 +39,76 @@ namespace osu.Android
|
|||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.
|
||||||
|
// on first launch we still have to fire manually.
|
||||||
|
// reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
|
||||||
|
handleIntent(Intent);
|
||||||
|
|
||||||
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
||||||
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
|
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnNewIntent(Intent intent) => handleIntent(intent);
|
||||||
|
|
||||||
|
private void handleIntent(Intent intent)
|
||||||
|
{
|
||||||
|
switch (intent.Action)
|
||||||
|
{
|
||||||
|
case Intent.ActionDefault:
|
||||||
|
if (intent.Scheme == ContentResolver.SchemeContent)
|
||||||
|
handleImportFromUris(intent.Data);
|
||||||
|
else if (osu_url_schemes.Contains(intent.Scheme))
|
||||||
|
game.HandleLink(intent.DataString);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Intent.ActionSend:
|
||||||
|
case Intent.ActionSendMultiple:
|
||||||
|
{
|
||||||
|
var uris = new List<Uri>();
|
||||||
|
for (int i = 0; i < intent.ClipData?.ItemCount; i++)
|
||||||
|
{
|
||||||
|
var content = intent.ClipData?.GetItemAt(i);
|
||||||
|
if (content != null)
|
||||||
|
uris.Add(content.Uri);
|
||||||
|
}
|
||||||
|
handleImportFromUris(uris.ToArray());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleImportFromUris(params Uri[] uris) => Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
var tasks = new List<ImportTask>();
|
||||||
|
|
||||||
|
await Task.WhenAll(uris.Select(async uri =>
|
||||||
|
{
|
||||||
|
// there are more performant overloads of this method, but this one is the most backwards-compatible
|
||||||
|
// (dates back to API 1).
|
||||||
|
var cursor = ContentResolver?.Query(uri, null, null, null, null);
|
||||||
|
|
||||||
|
if (cursor == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cursor.MoveToFirst();
|
||||||
|
|
||||||
|
var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
|
||||||
|
string filename = cursor.GetString(filenameColumn);
|
||||||
|
|
||||||
|
// SharpCompress requires archive streams to be seekable, which the stream opened by
|
||||||
|
// OpenInputStream() seems to not necessarily be.
|
||||||
|
// copy to an arbitrary-access memory stream to be able to proceed with the import.
|
||||||
|
var copy = new MemoryStream();
|
||||||
|
using (var stream = ContentResolver.OpenInputStream(uri))
|
||||||
|
await stream.CopyToAsync(copy);
|
||||||
|
|
||||||
|
lock (tasks)
|
||||||
|
{
|
||||||
|
tasks.Add(new ImportTask(copy, filename));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
await game.Import(tasks.ToArray());
|
||||||
|
}, TaskCreationOptions.LongRunning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
@ -26,18 +27,20 @@ namespace osu.Desktop
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
private Bindable<User> user;
|
private IBindable<User> user;
|
||||||
|
|
||||||
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
private readonly IBindable<UserStatus> status = new Bindable<UserStatus>();
|
||||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||||
|
|
||||||
|
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
|
||||||
|
|
||||||
private readonly RichPresence presence = new RichPresence
|
private readonly RichPresence presence = new RichPresence
|
||||||
{
|
{
|
||||||
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
||||||
};
|
};
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IAPIProvider provider)
|
private void load(IAPIProvider provider, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
client = new DiscordRpcClient(client_id)
|
client = new DiscordRpcClient(client_id)
|
||||||
{
|
{
|
||||||
@ -51,6 +54,8 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
||||||
|
|
||||||
|
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
||||||
|
|
||||||
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
|
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
|
||||||
{
|
{
|
||||||
status.UnbindBindings();
|
status.UnbindBindings();
|
||||||
@ -63,6 +68,7 @@ namespace osu.Desktop
|
|||||||
ruleset.BindValueChanged(_ => updateStatus());
|
ruleset.BindValueChanged(_ => updateStatus());
|
||||||
status.BindValueChanged(_ => updateStatus());
|
status.BindValueChanged(_ => updateStatus());
|
||||||
activity.BindValueChanged(_ => updateStatus());
|
activity.BindValueChanged(_ => updateStatus());
|
||||||
|
privacyMode.BindValueChanged(_ => updateStatus());
|
||||||
|
|
||||||
client.Initialize();
|
client.Initialize();
|
||||||
}
|
}
|
||||||
@ -78,7 +84,7 @@ namespace osu.Desktop
|
|||||||
if (!client.IsInitialized)
|
if (!client.IsInitialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (status.Value is UserStatusOffline)
|
if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
|
||||||
{
|
{
|
||||||
client.ClearPresence();
|
client.ClearPresence();
|
||||||
return;
|
return;
|
||||||
@ -96,7 +102,10 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update user information
|
// update user information
|
||||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
||||||
|
presence.Assets.LargeImageText = string.Empty;
|
||||||
|
else
|
||||||
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
|
||||||
|
|
||||||
// update ruleset
|
// update ruleset
|
||||||
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
|
||||||
@ -137,7 +146,7 @@ namespace osu.Desktop
|
|||||||
return edit.Beatmap.ToString();
|
return edit.Beatmap.ToString();
|
||||||
|
|
||||||
case UserActivity.InLobby lobby:
|
case UserActivity.InLobby lobby:
|
||||||
return lobby.Room.Name.Value;
|
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using osu.Desktop.Overlays;
|
using osu.Desktop.Overlays;
|
||||||
@ -56,16 +57,16 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
string stableInstallPath;
|
string stableInstallPath;
|
||||||
|
|
||||||
try
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
try
|
||||||
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
{
|
||||||
|
stableInstallPath = getStableInstallPathFromRegistry();
|
||||||
|
|
||||||
if (checkExists(stableInstallPath))
|
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
|
||||||
return stableInstallPath;
|
return stableInstallPath;
|
||||||
}
|
}
|
||||||
catch
|
catch { }
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
||||||
@ -79,6 +80,13 @@ namespace osu.Desktop
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
private string getStableInstallPathFromRegistry()
|
||||||
|
{
|
||||||
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
|
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager()
|
protected override UpdateManager CreateUpdateManager()
|
||||||
{
|
{
|
||||||
switch (RuntimeInfo.OS)
|
switch (RuntimeInfo.OS)
|
||||||
|
@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
|
|||||||
|
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
|
|
||||||
|
FillFlowContainer mainFill;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
mainFill = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -55,23 +57,30 @@ namespace osu.Desktop.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Font = OsuFont.Numeric.With(size: 12),
|
|
||||||
Colour = colours.Yellow,
|
|
||||||
Text = @"Development Build"
|
|
||||||
},
|
|
||||||
new Sprite
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Texture = textures.Get(@"Menu/dev-build-footer"),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!game.IsDeployedBuild)
|
||||||
|
{
|
||||||
|
mainFill.AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Font = OsuFont.Numeric.With(size: 12),
|
||||||
|
Colour = colours.Yellow,
|
||||||
|
Text = @"Development Build"
|
||||||
|
},
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Texture = textures.Get(@"Menu/dev-build-footer"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||||
@ -24,16 +24,13 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
|
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="5.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="5.0.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.169" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||||
<!-- .NET 3.1 SDK seems to cause issues with a runtime specification. This will likely be resolved in .NET 5. -->
|
|
||||||
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
|
||||||
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
|
||||||
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||||
<releaseNotes>testing</releaseNotes>
|
<releaseNotes>testing</releaseNotes>
|
||||||
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2021 ppy Pty Ltd</copyright>
|
||||||
<language>en-AU</language>
|
<language>en-AU</language>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
|
"${workspaceRoot}/bin/Debug/net5.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/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
|
"${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
public float Position
|
public float Position
|
||||||
{
|
{
|
||||||
get => HitObject?.X ?? position;
|
get => HitObject?.EffectiveX ?? position;
|
||||||
set => position = value;
|
set => position = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
|||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
private Container droppedObjectContainer;
|
private Container<CaughtObject> droppedObjectContainer;
|
||||||
|
|
||||||
private TestCatcher catcher;
|
private TestCatcher catcher;
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
var trailContainer = new Container();
|
var trailContainer = new Container();
|
||||||
droppedObjectContainer = new Container();
|
droppedObjectContainer = new Container<CaughtObject>();
|
||||||
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
|
||||||
|
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -65,13 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
JudgementResult result2 = null;
|
JudgementResult result2 = null;
|
||||||
AddStep("catch hyper fruit", () =>
|
AddStep("catch hyper fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject1 = createDrawableObject(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
|
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
|
||||||
result1 = attemptCatch(drawableObject1);
|
|
||||||
});
|
});
|
||||||
AddStep("catch normal fruit", () =>
|
AddStep("catch normal fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject2 = createDrawableObject(new Fruit());
|
attemptCatch(new Fruit(), out drawableObject2, out result2);
|
||||||
result2 = attemptCatch(drawableObject2);
|
|
||||||
});
|
});
|
||||||
AddStep("revert second result", () =>
|
AddStep("revert second result", () =>
|
||||||
{
|
{
|
||||||
@ -92,8 +91,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
JudgementResult result = null;
|
JudgementResult result = null;
|
||||||
AddStep("catch kiai fruit", () =>
|
AddStep("catch kiai fruit", () =>
|
||||||
{
|
{
|
||||||
drawableObject = createDrawableObject(new TestKiaiFruit());
|
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
|
||||||
result = attemptCatch(drawableObject);
|
|
||||||
});
|
});
|
||||||
checkState(CatcherAnimationState.Kiai);
|
checkState(CatcherAnimationState.Kiai);
|
||||||
AddStep("revert result", () =>
|
AddStep("revert result", () =>
|
||||||
@ -200,13 +198,22 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[Test]
|
||||||
[TestCase(false)]
|
public void TestHitLightingColour()
|
||||||
public void TestHitLighting(bool enabled)
|
|
||||||
{
|
{
|
||||||
AddStep($"{(enabled ? "enable" : "disable")} hit lighting", () => config.Set(OsuSetting.HitLighting, enabled));
|
var fruitColour = SkinConfiguration.DefaultComboColours[1];
|
||||||
|
AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true));
|
||||||
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
AddAssert("check hit lighting", () => catcher.ChildrenOfType<HitExplosion>().Any() == enabled);
|
AddAssert("correct hit lighting colour", () =>
|
||||||
|
catcher.ChildrenOfType<HitExplosion>().First()?.ObjectColour == fruitColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitLightingDisabled()
|
||||||
|
{
|
||||||
|
AddStep("disable hit lighting", () => config.Set(OsuSetting.HitLighting, false));
|
||||||
|
AddStep("catch fruit", () => attemptCatch(new Fruit()));
|
||||||
|
AddAssert("no hit lighting", () => !catcher.ChildrenOfType<HitExplosion>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
|
||||||
@ -218,18 +225,34 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
private void attemptCatch(CatchHitObject hitObject, int count = 1)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
attemptCatch(createDrawableObject(hitObject));
|
attemptCatch(hitObject, out _, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JudgementResult attemptCatch(DrawableCatchHitObject drawableObject)
|
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
|
||||||
{
|
{
|
||||||
drawableObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
var result = new CatchJudgementResult(drawableObject.HitObject, drawableObject.HitObject.CreateJudgement())
|
drawableObject = createDrawableObject(hitObject);
|
||||||
|
result = createResult(hitObject);
|
||||||
|
applyResult(drawableObject, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||||
|
{
|
||||||
|
// Load DHO to set colour of hit explosion correctly
|
||||||
|
Add(drawableObject);
|
||||||
|
drawableObject.OnLoadComplete += _ =>
|
||||||
{
|
{
|
||||||
Type = catcher.CanCatch(drawableObject.HitObject) ? HitResult.Great : HitResult.Miss
|
catcher.OnNewResult(drawableObject, result);
|
||||||
|
drawableObject.Expire();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JudgementResult createResult(CatchHitObject hitObject)
|
||||||
|
{
|
||||||
|
return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
|
||||||
|
{
|
||||||
|
Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
|
||||||
};
|
};
|
||||||
catcher.OnNewResult(drawableObject, result);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
|
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
|
||||||
@ -252,9 +275,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
public class TestCatcher : Catcher
|
public class TestCatcher : Catcher
|
||||||
{
|
{
|
||||||
public IEnumerable<DrawablePalpableCatchHitObject> CaughtObjects => this.ChildrenOfType<DrawablePalpableCatchHitObject>();
|
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
|
||||||
|
|
||||||
public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
|
public TestCatcher(Container trailsTarget, Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty)
|
||||||
: base(trailsTarget, droppedObjectTarget, difficulty)
|
: base(trailsTarget, droppedObjectTarget, difficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private void attemptCatch(Fruit fruit)
|
private void attemptCatch(Fruit fruit)
|
||||||
{
|
{
|
||||||
fruit.X += catcher.X;
|
fruit.X = fruit.OriginalX + catcher.X;
|
||||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
|
||||||
{
|
{
|
||||||
CircleSize = circleSize
|
CircleSize = circleSize
|
||||||
@ -73,7 +73,10 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
SetContents(() =>
|
SetContents(() =>
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container();
|
var droppedObjectContainer = new Container<CaughtObject>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
|
};
|
||||||
|
|
||||||
return new CatchInputManager(catchRuleset)
|
return new CatchInputManager(catchRuleset)
|
||||||
{
|
{
|
||||||
@ -99,7 +102,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private class TestCatcherArea : CatcherArea
|
private class TestCatcherArea : CatcherArea
|
||||||
{
|
{
|
||||||
public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
public TestCatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
|
||||||
: base(droppedObjectContainer, beatmapDifficulty)
|
: base(droppedObjectContainer, beatmapDifficulty)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -5,19 +5,20 @@ using NUnit.Framework;
|
|||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneFruitRandomness : OsuTestScene
|
public class TestSceneFruitRandomness : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly TestDrawableFruit drawableFruit;
|
private readonly DrawableFruit drawableFruit;
|
||||||
private readonly TestDrawableBanana drawableBanana;
|
private readonly DrawableBanana drawableBanana;
|
||||||
|
|
||||||
public TestSceneFruitRandomness()
|
public TestSceneFruitRandomness()
|
||||||
{
|
{
|
||||||
drawableFruit = new TestDrawableFruit(new Fruit());
|
drawableFruit = new DrawableFruit(new Fruit());
|
||||||
drawableBanana = new TestDrawableBanana(new Banana());
|
drawableBanana = new DrawableBanana(new Banana());
|
||||||
|
|
||||||
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
|
||||||
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
|
||||||
@ -37,16 +38,16 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
float fruitRotation = 0;
|
float fruitRotation = 0;
|
||||||
float bananaRotation = 0;
|
float bananaRotation = 0;
|
||||||
float bananaScale = 0;
|
Vector2 bananaSize = new Vector2();
|
||||||
Color4 bananaColour = new Color4();
|
Color4 bananaColour = new Color4();
|
||||||
|
|
||||||
AddStep("Initialize start time", () =>
|
AddStep("Initialize start time", () =>
|
||||||
{
|
{
|
||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
|
|
||||||
fruitRotation = drawableFruit.InnerRotation;
|
fruitRotation = drawableFruit.DisplayRotation;
|
||||||
bananaRotation = drawableBanana.InnerRotation;
|
bananaRotation = drawableBanana.DisplayRotation;
|
||||||
bananaScale = drawableBanana.InnerScale;
|
bananaSize = drawableBanana.DisplaySize;
|
||||||
bananaColour = drawableBanana.AccentColour.Value;
|
bananaColour = drawableBanana.AccentColour.Value;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("fruit rotation is changed", () => drawableFruit.InnerRotation != fruitRotation);
|
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
|
||||||
AddAssert("banana rotation is changed", () => drawableBanana.InnerRotation != bananaRotation);
|
AddAssert("banana rotation is changed", () => drawableBanana.DisplayRotation != bananaRotation);
|
||||||
AddAssert("banana scale is changed", () => drawableBanana.InnerScale != bananaScale);
|
AddAssert("banana size is changed", () => drawableBanana.DisplaySize != bananaSize);
|
||||||
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
|
||||||
|
|
||||||
AddStep("reset start time", () =>
|
AddStep("reset start time", () =>
|
||||||
@ -65,32 +66,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("rotation and scale restored", () =>
|
AddAssert("rotation and size restored", () =>
|
||||||
drawableFruit.InnerRotation == fruitRotation &&
|
drawableFruit.DisplayRotation == fruitRotation &&
|
||||||
drawableBanana.InnerRotation == bananaRotation &&
|
drawableBanana.DisplayRotation == bananaRotation &&
|
||||||
drawableBanana.InnerScale == bananaScale &&
|
drawableBanana.DisplaySize == bananaSize &&
|
||||||
drawableBanana.AccentColour.Value == bananaColour);
|
drawableBanana.AccentColour.Value == bananaColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableFruit : DrawableFruit
|
|
||||||
{
|
|
||||||
public float InnerRotation => ScaleContainer.Rotation;
|
|
||||||
|
|
||||||
public TestDrawableFruit(Fruit h)
|
|
||||||
: base(h)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestDrawableBanana : DrawableBanana
|
|
||||||
{
|
|
||||||
public float InnerRotation => ScaleContainer.Rotation;
|
|
||||||
public float InnerScale => ScaleContainer.Scale.X;
|
|
||||||
|
|
||||||
public TestDrawableBanana(Banana h)
|
|
||||||
: base(h)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddStep("create hyper-dashing catcher", () =>
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
{
|
{
|
||||||
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container<CaughtObject>())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
151
osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
Normal file
151
osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audio { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.BeatmapSkins, BeatmapSkins);
|
||||||
|
config.BindWith(OsuSetting.BeatmapColours, BeatmapColours);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, true)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||||
|
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||||
|
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||||
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true, true)]
|
||||||
|
[TestCase(false, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||||
|
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||||
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true, true)]
|
||||||
|
[TestCase(false, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
|
||||||
|
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||||
|
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
ConfigureTest(useBeatmapSkin, true, true);
|
||||||
|
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestBeatmapSkin.HYPER_DASH_COLOUR);
|
||||||
|
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestBeatmapSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||||
|
AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestBeatmapSkin.HYPER_DASH_FRUIT_COLOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
ConfigureTest(useBeatmapSkin, false, true);
|
||||||
|
AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestSkin.HYPER_DASH_COLOUR);
|
||||||
|
AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
|
||||||
|
AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestSkin.HYPER_DASH_FRUIT_COLOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours);
|
||||||
|
|
||||||
|
private class CatchExposedPlayer : ExposedPlayer
|
||||||
|
{
|
||||||
|
public CatchExposedPlayer(bool userHasCustomColours)
|
||||||
|
: base(userHasCustomColours)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color4 UsableHyperDashColour =>
|
||||||
|
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
|
||||||
|
.First()
|
||||||
|
.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup(CatchSkinColour.HyperDash))?
|
||||||
|
.Value ?? Color4.Red;
|
||||||
|
|
||||||
|
public Color4 UsableHyperDashAfterImageColour =>
|
||||||
|
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
|
||||||
|
.First()
|
||||||
|
.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup(CatchSkinColour.HyperDashAfterImage))?
|
||||||
|
.Value ?? Color4.Red;
|
||||||
|
|
||||||
|
public Color4 UsableHyperDashFruitColour =>
|
||||||
|
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
|
||||||
|
.First()
|
||||||
|
.GetConfig<SkinCustomColourLookup, Color4>(new SkinCustomColourLookup(CatchSkinColour.HyperDashFruit))?
|
||||||
|
.Value ?? Color4.Red;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CatchCustomSkinWorkingBeatmap : CustomSkinWorkingBeatmap
|
||||||
|
{
|
||||||
|
public CatchCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
|
||||||
|
: base(createBeatmap(), audio, hasColours)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBeatmap createBeatmap() =>
|
||||||
|
new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BeatmapSet = new BeatmapSetInfo(),
|
||||||
|
Ruleset = new CatchRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
HitObjects = { new Fruit { StartTime = 1816, X = 56, NewCombo = true } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,14 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.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" />
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
case JuiceStream juiceStream:
|
case JuiceStream juiceStream:
|
||||||
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
||||||
lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
||||||
|
|
||||||
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
||||||
lastStartTime = juiceStream.StartTime;
|
lastStartTime = juiceStream.StartTime;
|
||||||
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
catchObject.XOffset = 0;
|
catchObject.XOffset = 0;
|
||||||
|
|
||||||
if (catchObject is TinyDroplet)
|
if (catchObject is TinyDroplet)
|
||||||
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X);
|
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.OriginalX, CatchPlayfield.WIDTH - catchObject.OriginalX);
|
||||||
else if (catchObject is Droplet)
|
else if (catchObject is Droplet)
|
||||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
|
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
|
||||||
{
|
{
|
||||||
float offsetPosition = hitObject.X;
|
float offsetPosition = hitObject.OriginalX;
|
||||||
double startTime = hitObject.StartTime;
|
double startTime = hitObject.StartTime;
|
||||||
|
|
||||||
if (lastPosition == null)
|
if (lastPosition == null)
|
||||||
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
if (positionDiff == 0)
|
if (positionDiff == 0)
|
||||||
{
|
{
|
||||||
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
|
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
|
||||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
if (Math.Abs(positionDiff) < timeDiff / 3)
|
if (Math.Abs(positionDiff) < timeDiff / 3)
|
||||||
applyOffset(ref offsetPosition, positionDiff);
|
applyOffset(ref offsetPosition, positionDiff);
|
||||||
|
|
||||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
|
||||||
|
|
||||||
lastPosition = offsetPosition;
|
lastPosition = offsetPosition;
|
||||||
lastStartTime = startTime;
|
lastStartTime = startTime;
|
||||||
@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
currentObject.HyperDashTarget = null;
|
currentObject.HyperDashTarget = null;
|
||||||
currentObject.DistanceToHyperDash = 0;
|
currentObject.DistanceToHyperDash = 0;
|
||||||
|
|
||||||
int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
|
int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
|
||||||
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
|
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
|
||||||
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
|
double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
|
||||||
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
|
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
|
||||||
|
|
||||||
if (distanceToHyper < 0)
|
if (distanceToHyper < 0)
|
||||||
|
@ -32,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
||||||
|
|
||||||
NormalizedPosition = BaseObject.X * scalingFactor;
|
NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
|
||||||
LastNormalizedPosition = LastObject.X * scalingFactor;
|
LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
|
||||||
|
|
||||||
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||||
StrainTime = Math.Max(40, DeltaTime);
|
StrainTime = Math.Max(40, DeltaTime);
|
||||||
|
@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
difficulty.CircleSize = CircleSize.Value;
|
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||||
difficulty.ApproachRate = ApproachRate.Value;
|
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
@ -16,38 +15,46 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
public const float OBJECT_RADIUS = 64;
|
public const float OBJECT_RADIUS = 64;
|
||||||
|
|
||||||
// This value is after XOffset applied.
|
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
|
||||||
public readonly Bindable<float> XBindable = new Bindable<float>();
|
|
||||||
|
|
||||||
// This value is before XOffset applied.
|
|
||||||
private float originalX;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
// TODO: I don't like this asymmetry.
|
set => OriginalXBindable.Value = value;
|
||||||
get => XBindable.Value;
|
|
||||||
// originalX is set by `XBindable.BindValueChanged`
|
|
||||||
set => XBindable.Value = value + xOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float xOffset;
|
float IHasXPosition.X => OriginalXBindable.Value;
|
||||||
|
|
||||||
|
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A random offset applied to <see cref="X"/>, set by the <see cref="CatchBeatmapProcessor"/>.
|
/// A random offset applied to the horizontal position, set by the beatmap processing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal float XOffset
|
public float XOffset
|
||||||
{
|
{
|
||||||
get => xOffset;
|
set => XOffsetBindable.Value = value;
|
||||||
set
|
|
||||||
{
|
|
||||||
xOffset = value;
|
|
||||||
XBindable.Value = originalX + xOffset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
|
||||||
|
/// Use <see cref="EffectiveX"/> for a gameplay.
|
||||||
|
/// </remarks>
|
||||||
|
public float OriginalX => OriginalXBindable.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
|
||||||
|
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
|
||||||
|
/// </remarks>
|
||||||
|
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
|
||||||
|
|
||||||
public double TimePreempt = 1000;
|
public double TimePreempt = 1000;
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
|
||||||
@ -113,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
|
|
||||||
protected CatchHitObject()
|
|
||||||
{
|
|
||||||
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
Normal file
18
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtBanana.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Banana"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtBanana : CaughtObject
|
||||||
|
{
|
||||||
|
public CaughtBanana()
|
||||||
|
: base(CatchSkinComponents.Banana, _ => new BananaPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
Normal file
20
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtDroplet.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Droplet"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtDroplet : CaughtObject
|
||||||
|
{
|
||||||
|
public override bool StaysOnPlate => false;
|
||||||
|
|
||||||
|
public CaughtDroplet()
|
||||||
|
: base(CatchSkinComponents.Droplet, _ => new DropletPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
Normal file
29
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtFruit.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="Fruit"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public class CaughtFruit : CaughtObject, IHasFruitState
|
||||||
|
{
|
||||||
|
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
||||||
|
|
||||||
|
public CaughtFruit()
|
||||||
|
: base(CatchSkinComponents.Fruit, _ => new FruitPiece())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void CopyStateFrom(IHasCatchObjectState objectState)
|
||||||
|
{
|
||||||
|
base.CopyStateFrom(objectState);
|
||||||
|
|
||||||
|
var fruitState = (IHasFruitState)objectState;
|
||||||
|
VisualRepresentation.Value = fruitState.VisualRepresentation.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
Normal file
64
osu.Game.Rulesets.Catch/Objects/Drawables/CaughtObject.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a <see cref="PalpableCatchHitObject"/> caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
[Cached(typeof(IHasCatchObjectState))]
|
||||||
|
public abstract class CaughtObject : SkinnableDrawable, IHasCatchObjectState
|
||||||
|
{
|
||||||
|
public PalpableCatchHitObject HitObject { get; private set; }
|
||||||
|
public Bindable<Color4> AccentColour { get; } = new Bindable<Color4>();
|
||||||
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
|
public Vector2 DisplaySize => Size * Scale;
|
||||||
|
|
||||||
|
public float DisplayRotation => Rotation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool StaysOnPlate => true;
|
||||||
|
|
||||||
|
public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
|
protected CaughtObject(CatchSkinComponents skinComponent, Func<ISkinComponent, Drawable> defaultImplementation)
|
||||||
|
: base(new CatchSkinComponent(skinComponent), defaultImplementation)
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.None;
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies the hit object visual state from another <see cref="IHasCatchObjectState"/> object.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void CopyStateFrom(IHasCatchObjectState objectState)
|
||||||
|
{
|
||||||
|
HitObject = objectState.HitObject;
|
||||||
|
Scale = Vector2.Divide(objectState.DisplaySize, Size);
|
||||||
|
Rotation = objectState.DisplayRotation;
|
||||||
|
AccentColour.Value = objectState.AccentColour.Value;
|
||||||
|
HyperDash.Value = objectState.HyperDash.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void FreeAfterUse()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 1;
|
||||||
|
|
||||||
|
base.FreeAfterUse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Banana),
|
new CatchSkinComponent(CatchSkinComponents.Banana),
|
||||||
_ => new BananaPiece());
|
_ => new BananaPiece());
|
||||||
}
|
}
|
||||||
@ -44,12 +44,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
const float end_scale = 0.6f;
|
const float end_scale = 0.6f;
|
||||||
const float random_scale_range = 1.6f;
|
const float random_scale_range = 1.6f;
|
||||||
|
|
||||||
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
|
||||||
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
|
||||||
|
|
||||||
ScaleContainer.RotateTo(getRandomAngle(1))
|
ScalingContainer.RotateTo(getRandomAngle(1))
|
||||||
.Then()
|
.Then()
|
||||||
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
|
||||||
|
|
||||||
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
|
||||||
}
|
}
|
||||||
|
@ -15,11 +15,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
|
||||||
{
|
{
|
||||||
public readonly Bindable<float> XBindable = new Bindable<float>();
|
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
|
||||||
|
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
|
||||||
|
|
||||||
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
protected override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
public int RandomSeed => HitObject?.RandomSeed ?? 0;
|
||||||
|
|
||||||
@ -38,22 +39,20 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
|
|
||||||
XBindable.BindTo(HitObject.XBindable);
|
OriginalXBindable.BindTo(HitObject.OriginalXBindable);
|
||||||
|
XOffsetBindable.BindTo(HitObject.XOffsetBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
|
||||||
XBindable.UnbindFrom(HitObject.XBindable);
|
OriginalXBindable.UnbindFrom(HitObject.OriginalXBindable);
|
||||||
|
XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<CatchHitObject, bool> CheckPosition;
|
public Func<CatchHitObject, bool> CheckPosition;
|
||||||
|
|
||||||
public bool IsOnPlate;
|
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => IsOnPlate;
|
|
||||||
|
|
||||||
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
|
protected override JudgementResult CreateResult(Judgement judgement) => new CatchJudgementResult(HitObject, judgement);
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
|
@ -11,8 +11,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
public class DrawableDroplet : DrawablePalpableCatchHitObject
|
||||||
{
|
{
|
||||||
public override bool StaysOnPlate => false;
|
|
||||||
|
|
||||||
public DrawableDroplet()
|
public DrawableDroplet()
|
||||||
: this(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
@ -26,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Droplet),
|
new CatchSkinComponent(CatchSkinComponents.Droplet),
|
||||||
_ => new DropletPiece());
|
_ => new DropletPiece());
|
||||||
}
|
}
|
||||||
@ -39,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
float startRotation = RandomSingle(1) * 20;
|
float startRotation = RandomSingle(1) * 20;
|
||||||
double duration = HitObject.TimePreempt + 2000;
|
double duration = HitObject.TimePreempt + 2000;
|
||||||
|
|
||||||
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableFruit : DrawablePalpableCatchHitObject
|
public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
|
||||||
{
|
{
|
||||||
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
|
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
|
||||||
|
|
||||||
public DrawableFruit()
|
public DrawableFruit()
|
||||||
: this(null)
|
: this(null)
|
||||||
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
ScaleContainer.Child = new SkinnableDrawable(
|
ScalingContainer.Child = new SkinnableDrawable(
|
||||||
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
new CatchSkinComponent(CatchSkinComponents.Fruit),
|
||||||
_ => new FruitPiece());
|
_ => new FruitPiece());
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
ScaleContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,32 +7,36 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject
|
[Cached(typeof(IHasCatchObjectState))]
|
||||||
|
public abstract class DrawablePalpableCatchHitObject : DrawableCatchHitObject, IHasCatchObjectState
|
||||||
{
|
{
|
||||||
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
public new PalpableCatchHitObject HitObject => (PalpableCatchHitObject)base.HitObject;
|
||||||
|
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
Bindable<Color4> IHasCatchObjectState.AccentColour => AccentColour;
|
||||||
|
|
||||||
public readonly Bindable<float> ScaleBindable = new Bindable<float>(1);
|
public Bindable<bool> HyperDash { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
public readonly Bindable<int> IndexInBeatmap = new Bindable<int>();
|
public Bindable<float> ScaleBindable { get; } = new Bindable<float>(1);
|
||||||
|
|
||||||
|
public Bindable<int> IndexInBeatmap { get; } = new Bindable<int>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The multiplicative factor applied to <see cref="ScaleContainer"/> scale relative to <see cref="HitObject"/> scale.
|
/// The multiplicative factor applied to <see cref="Drawable.Scale"/> relative to <see cref="HitObject"/> scale.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual float ScaleFactor => 1;
|
protected virtual float ScaleFactor => 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this hit object should stay on the catcher plate when the object is caught by the catcher.
|
/// The container internal transforms (such as scaling based on the circle size) are applied to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool StaysOnPlate => true;
|
protected readonly Container ScalingContainer;
|
||||||
|
|
||||||
public float DisplayRadius => CatchHitObject.OBJECT_RADIUS * HitObject.Scale * ScaleFactor;
|
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
|
||||||
|
|
||||||
protected readonly Container ScaleContainer;
|
public float DisplayRotation => ScalingContainer.Rotation;
|
||||||
|
|
||||||
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
|
||||||
: base(h)
|
: base(h)
|
||||||
@ -40,30 +44,34 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
AddInternal(ScaleContainer = new Container
|
AddInternal(ScalingContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
XBindable.BindValueChanged(x =>
|
OriginalXBindable.BindValueChanged(updateXPosition);
|
||||||
{
|
XOffsetBindable.BindValueChanged(updateXPosition, true);
|
||||||
if (!IsOnPlate) X = x.NewValue;
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
ScaleBindable.BindValueChanged(scale =>
|
ScaleBindable.BindValueChanged(scale =>
|
||||||
{
|
{
|
||||||
ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
|
ScalingContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
|
||||||
|
Size = DisplaySize;
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateXPosition(ValueChangedEvent<float> _)
|
||||||
|
{
|
||||||
|
X = OriginalXBindable.Value + XOffsetBindable.Value;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a visual state of a <see cref="PalpableCatchHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasCatchObjectState
|
||||||
|
{
|
||||||
|
PalpableCatchHitObject HitObject { get; }
|
||||||
|
|
||||||
|
Bindable<Color4> AccentColour { get; }
|
||||||
|
|
||||||
|
Bindable<bool> HyperDash { get; }
|
||||||
|
|
||||||
|
Vector2 DisplaySize { get; }
|
||||||
|
|
||||||
|
float DisplayRotation { get; }
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
Normal file
15
osu.Game.Rulesets.Catch/Objects/Drawables/IHasFruitState.cs
Normal file
@ -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 osu.Framework.Bindables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a visual state of a <see cref="Fruit"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHasFruitState : IHasCatchObjectState
|
||||||
|
{
|
||||||
|
Bindable<FruitVisualRepresentation> VisualRepresentation { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
AddNested(new TinyDroplet
|
AddNested(new TinyDroplet
|
||||||
{
|
{
|
||||||
StartTime = t + lastEvent.Value.Time,
|
StartTime = t + lastEvent.Value.Time,
|
||||||
X = X + Path.PositionAt(
|
X = OriginalX + Path.PositionAt(
|
||||||
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = dropletSamples,
|
Samples = dropletSamples,
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = X + Path.PositionAt(e.PathProgress).X,
|
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -104,14 +104,14 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = this.GetNodeSamples(nodeIndex++),
|
Samples = this.GetNodeSamples(nodeIndex++),
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = X + Path.PositionAt(e.PathProgress).X,
|
X = OriginalX + Path.PositionAt(e.PathProgress).X,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float EndX => X + this.CurvePositionAt(1).X;
|
public float EndX => OriginalX + this.CurvePositionAt(1).X;
|
||||||
|
|
||||||
public double Duration
|
public double Duration
|
||||||
{
|
{
|
||||||
|
@ -42,9 +42,14 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
void moveToNext(PalpableCatchHitObject h)
|
void moveToNext(PalpableCatchHitObject h)
|
||||||
{
|
{
|
||||||
float positionChange = Math.Abs(lastPosition - h.X);
|
float positionChange = Math.Abs(lastPosition - h.EffectiveX);
|
||||||
double timeAvailable = h.StartTime - lastTime;
|
double timeAvailable = h.StartTime - lastTime;
|
||||||
|
|
||||||
|
if (timeAvailable < 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// So we can either make it there without a dash or not.
|
// So we can either make it there without a dash or not.
|
||||||
// If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
|
// If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
|
||||||
// The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
|
// The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
|
||||||
@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
// todo: get correct catcher size, based on difficulty CS.
|
// todo: get correct catcher size, based on difficulty CS.
|
||||||
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
|
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
|
||||||
|
|
||||||
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
|
if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
|
||||||
{
|
{
|
||||||
// we are already in the correct range.
|
// we are already in the correct range.
|
||||||
lastTime = h.StartTime;
|
lastTime = h.StartTime;
|
||||||
@ -66,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
if (impossibleJump)
|
if (impossibleJump)
|
||||||
{
|
{
|
||||||
addFrame(h.StartTime, h.X);
|
addFrame(h.StartTime, h.EffectiveX);
|
||||||
}
|
}
|
||||||
else if (h.HyperDash)
|
else if (h.HyperDash)
|
||||||
{
|
{
|
||||||
addFrame(h.StartTime - timeAvailable, lastPosition);
|
addFrame(h.StartTime - timeAvailable, lastPosition);
|
||||||
addFrame(h.StartTime, h.X);
|
addFrame(h.StartTime, h.EffectiveX);
|
||||||
}
|
}
|
||||||
else if (dashRequired)
|
else if (dashRequired)
|
||||||
{
|
{
|
||||||
@ -80,23 +85,23 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
|
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
|
||||||
double timeAtDashSpeed = timeWeNeedToSave / 2;
|
double timeAtDashSpeed = timeWeNeedToSave / 2;
|
||||||
|
|
||||||
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
|
float midPosition = (float)Interpolation.Lerp(lastPosition, h.EffectiveX, (float)timeAtDashSpeed / timeAvailable);
|
||||||
|
|
||||||
// dash movement
|
// dash movement
|
||||||
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
|
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
|
||||||
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
|
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
|
||||||
addFrame(h.StartTime, h.X);
|
addFrame(h.StartTime, h.EffectiveX);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double timeBefore = positionChange / movement_speed;
|
double timeBefore = positionChange / movement_speed;
|
||||||
|
|
||||||
addFrame(h.StartTime - timeBefore, lastPosition);
|
addFrame(h.StartTime - timeBefore, lastPosition);
|
||||||
addFrame(h.StartTime, h.X);
|
addFrame(h.StartTime, h.EffectiveX);
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTime = h.StartTime;
|
lastTime = h.StartTime;
|
||||||
lastPosition = h.X;
|
lastPosition = h.EffectiveX;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var obj in Beatmap.HitObjects)
|
foreach (var obj in Beatmap.HitObjects)
|
||||||
|
@ -29,4 +29,3 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
@ -17,9 +16,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
protected DrawableHitObject DrawableHitObject { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A part of this piece that will be faded out while falling in the playfield.
|
/// A part of this piece that will be faded out while falling in the playfield.
|
||||||
@ -37,13 +35,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
if (hitObject != null)
|
|
||||||
{
|
|
||||||
AccentColour.BindTo(hitObject.AccentColour);
|
|
||||||
HyperDash.BindTo(hitObject.HyperDash);
|
|
||||||
}
|
|
||||||
|
|
||||||
HyperDash.BindValueChanged(hyper =>
|
HyperDash.BindValueChanged(hyper =>
|
||||||
{
|
{
|
||||||
@ -54,8 +47,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
if (BorderPiece != null && DrawableHitObject?.HitObject != null)
|
if (BorderPiece != null)
|
||||||
BorderPiece.Alpha = (float)Math.Clamp((DrawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
|
BorderPiece.Alpha = (float)Math.Clamp((ObjectState.HitObject.StartTime - Time.Current) / 500, 0, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,10 +39,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruit = (DrawableFruit)DrawableHitObject;
|
var fruitState = (IHasFruitState)ObjectState;
|
||||||
|
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
||||||
if (fruit != null)
|
|
||||||
VisualRepresentation.BindTo(fruit.VisualRepresentation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,4 +19,3 @@ namespace osu.Game.Rulesets.Catch.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,8 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var fruit = (DrawableFruit)DrawableHitObject;
|
var fruitState = (IHasFruitState)ObjectState;
|
||||||
|
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
|
||||||
if (fruit != null)
|
|
||||||
VisualRepresentation.BindTo(fruit.VisualRepresentation);
|
|
||||||
|
|
||||||
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
|
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +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 JetBrains.Annotations;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -10,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -29,9 +27,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
protected ISkinSource Skin { get; private set; }
|
protected ISkinSource Skin { get; private set; }
|
||||||
|
|
||||||
[Resolved(canBeNull: true)]
|
[Resolved]
|
||||||
[CanBeNull]
|
protected IHasCatchObjectState ObjectState { get; private set; }
|
||||||
protected DrawableHitObject DrawableHitObject { get; private set; }
|
|
||||||
|
|
||||||
protected LegacyCatchHitObjectPiece()
|
protected LegacyCatchHitObjectPiece()
|
||||||
{
|
{
|
||||||
@ -65,13 +62,8 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
var hitObject = (DrawablePalpableCatchHitObject)DrawableHitObject;
|
AccentColour.BindTo(ObjectState.AccentColour);
|
||||||
|
HyperDash.BindTo(ObjectState.HyperDash);
|
||||||
if (hitObject != null)
|
|
||||||
{
|
|
||||||
AccentColour.BindTo(hitObject.AccentColour);
|
|
||||||
HyperDash.BindTo(hitObject.HyperDash);
|
|
||||||
}
|
|
||||||
|
|
||||||
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
||||||
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
var droppedObjectContainer = new Container
|
var droppedObjectContainer = new Container<CaughtObject>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
// 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 osu.Game.Replays;
|
|
||||||
using osu.Game.Rulesets.Catch.Replays;
|
using osu.Game.Rulesets.Catch.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
private readonly CatchPlayfield playfield;
|
private readonly CatchPlayfield playfield;
|
||||||
|
|
||||||
public CatchReplayRecorder(Replay target, CatchPlayfield playfield)
|
public CatchReplayRecorder(Score target, CatchPlayfield playfield)
|
||||||
: base(target)
|
: base(target)
|
||||||
{
|
{
|
||||||
this.playfield = playfield;
|
this.playfield = playfield;
|
||||||
|
@ -53,9 +53,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private CatcherTrailDisplay trails;
|
private CatcherTrailDisplay trails;
|
||||||
|
|
||||||
private readonly Container droppedObjectTarget;
|
/// <summary>
|
||||||
|
/// Contains caught objects on the plate.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container<CaughtObject> caughtObjectContainer;
|
||||||
|
|
||||||
private readonly Container<DrawablePalpableCatchHitObject> caughtFruitContainer;
|
/// <summary>
|
||||||
|
/// Contains objects dropped from the plate.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Container<CaughtObject> droppedObjectTarget;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState { get; private set; }
|
||||||
|
|
||||||
@ -108,7 +114,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
private readonly DrawablePool<HitExplosion> hitExplosionPool;
|
||||||
private readonly Container<HitExplosion> hitExplosionContainer;
|
private readonly Container<HitExplosion> hitExplosionContainer;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, [NotNull] Container droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
private readonly DrawablePool<CaughtFruit> caughtFruitPool;
|
||||||
|
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
|
||||||
|
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
|
||||||
|
|
||||||
|
public Catcher([NotNull] Container trailsTarget, [NotNull] Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
this.trailsTarget = trailsTarget;
|
this.trailsTarget = trailsTarget;
|
||||||
this.droppedObjectTarget = droppedObjectTarget;
|
this.droppedObjectTarget = droppedObjectTarget;
|
||||||
@ -124,7 +134,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
hitExplosionPool = new DrawablePool<HitExplosion>(10),
|
||||||
caughtFruitContainer = new Container<DrawablePalpableCatchHitObject>
|
caughtFruitPool = new DrawablePool<CaughtFruit>(50),
|
||||||
|
caughtBananaPool = new DrawablePool<CaughtBanana>(100),
|
||||||
|
// less capacity is needed compared to fruit because droplet is not stacked
|
||||||
|
caughtDropletPool = new DrawablePool<CaughtDroplet>(25),
|
||||||
|
caughtObjectContainer = new Container<CaughtObject>
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
@ -172,7 +186,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates proxied content to be displayed beneath hitobjects.
|
/// Creates proxied content to be displayed beneath hitobjects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
|
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
@ -202,7 +216,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
var halfCatchWidth = catchWidth * 0.5f;
|
var halfCatchWidth = catchWidth * 0.5f;
|
||||||
|
|
||||||
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
|
||||||
var catchObjectPosition = fruit.X;
|
var catchObjectPosition = fruit.EffectiveX;
|
||||||
var catcherPosition = Position.X;
|
var catcherPosition = Position.X;
|
||||||
|
|
||||||
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
@ -215,10 +229,19 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
catchResult.CatcherAnimationState = CurrentState;
|
catchResult.CatcherAnimationState = CurrentState;
|
||||||
catchResult.CatcherHyperDash = HyperDashing;
|
catchResult.CatcherHyperDash = HyperDashing;
|
||||||
|
|
||||||
if (!(drawableObject.HitObject is PalpableCatchHitObject hitObject)) return;
|
if (!(drawableObject is DrawablePalpableCatchHitObject palpableObject)) return;
|
||||||
|
|
||||||
|
var hitObject = palpableObject.HitObject;
|
||||||
|
|
||||||
if (result.IsHit)
|
if (result.IsHit)
|
||||||
placeCaughtObject(hitObject);
|
{
|
||||||
|
var positionInStack = computePositionInStack(new Vector2(palpableObject.X - X, 0), palpableObject.DisplaySize.X / 2);
|
||||||
|
|
||||||
|
placeCaughtObject(palpableObject, positionInStack);
|
||||||
|
|
||||||
|
if (hitLighting.Value)
|
||||||
|
addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
|
||||||
|
}
|
||||||
|
|
||||||
// droplet doesn't affect the catcher state
|
// droplet doesn't affect the catcher state
|
||||||
if (hitObject is TinyDroplet) return;
|
if (hitObject is TinyDroplet) return;
|
||||||
@ -227,10 +250,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
var target = hitObject.HyperDashTarget;
|
var target = hitObject.HyperDashTarget;
|
||||||
var timeDifference = target.StartTime - hitObject.StartTime;
|
var timeDifference = target.StartTime - hitObject.StartTime;
|
||||||
double positionDifference = target.X - X;
|
double positionDifference = target.EffectiveX - X;
|
||||||
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||||
|
|
||||||
SetHyperDashState(Math.Abs(velocity), target.X);
|
SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
@ -256,8 +279,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtFruitContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
droppedObjectTarget.RemoveAll(d => (d as DrawableCatchHitObject)?.HitObject == drawableObject.HitObject);
|
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -441,138 +464,121 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void placeCaughtObject(PalpableCatchHitObject source)
|
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
|
||||||
{
|
{
|
||||||
var caughtObject = createCaughtObject(source);
|
var caughtObject = getCaughtObject(drawableObject.HitObject);
|
||||||
|
|
||||||
if (caughtObject == null) return;
|
if (caughtObject == null) return;
|
||||||
|
|
||||||
caughtObject.RelativePositionAxes = Axes.None;
|
caughtObject.CopyStateFrom(drawableObject);
|
||||||
caughtObject.X = source.X - X;
|
|
||||||
caughtObject.IsOnPlate = true;
|
|
||||||
|
|
||||||
caughtObject.Anchor = Anchor.TopCentre;
|
caughtObject.Anchor = Anchor.TopCentre;
|
||||||
caughtObject.Origin = Anchor.Centre;
|
caughtObject.Position = position;
|
||||||
caughtObject.Scale *= 0.5f;
|
caughtObject.Scale /= 2;
|
||||||
caughtObject.LifetimeStart = source.StartTime;
|
|
||||||
caughtObject.LifetimeEnd = double.MaxValue;
|
|
||||||
|
|
||||||
adjustPositionInStack(caughtObject);
|
caughtObjectContainer.Add(caughtObject);
|
||||||
|
|
||||||
caughtFruitContainer.Add(caughtObject);
|
|
||||||
|
|
||||||
addLighting(caughtObject);
|
|
||||||
|
|
||||||
if (!caughtObject.StaysOnPlate)
|
if (!caughtObject.StaysOnPlate)
|
||||||
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustPositionInStack(DrawablePalpableCatchHitObject caughtObject)
|
private Vector2 computePositionInStack(Vector2 position, float displayRadius)
|
||||||
{
|
{
|
||||||
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
|
||||||
const float allowance = 10;
|
const float allowance = 10;
|
||||||
|
|
||||||
float caughtObjectRadius = caughtObject.DisplayRadius;
|
while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2)))
|
||||||
|
|
||||||
while (caughtFruitContainer.Any(f => Vector2Extensions.Distance(f.Position, caughtObject.Position) < (caughtObjectRadius + radius_div_2) / (allowance / 2)))
|
|
||||||
{
|
{
|
||||||
float diff = (caughtObjectRadius + radius_div_2) / allowance;
|
float diff = (displayRadius + radius_div_2) / allowance;
|
||||||
|
|
||||||
caughtObject.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
position.X += (RNG.NextSingle() - 0.5f) * diff * 2;
|
||||||
caughtObject.Y -= RNG.NextSingle() * diff;
|
position.Y -= RNG.NextSingle() * diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtObject.X = Math.Clamp(caughtObject.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(DrawablePalpableCatchHitObject caughtObject)
|
private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
|
||||||
{
|
{
|
||||||
if (!hitLighting.Value) return;
|
|
||||||
|
|
||||||
HitExplosion hitExplosion = hitExplosionPool.Get();
|
HitExplosion hitExplosion = hitExplosionPool.Get();
|
||||||
hitExplosion.HitObject = caughtObject.HitObject;
|
hitExplosion.HitObject = hitObject;
|
||||||
hitExplosion.X = caughtObject.X;
|
hitExplosion.X = x;
|
||||||
hitExplosion.Scale = new Vector2(caughtObject.HitObject.Scale);
|
hitExplosion.Scale = new Vector2(hitObject.Scale);
|
||||||
hitExplosion.ObjectColour = caughtObject.AccentColour.Value;
|
hitExplosion.ObjectColour = colour;
|
||||||
hitExplosionContainer.Add(hitExplosion);
|
hitExplosionContainer.Add(hitExplosion);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrawablePalpableCatchHitObject createCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
switch (source)
|
switch (source)
|
||||||
{
|
{
|
||||||
case Banana banana:
|
case Fruit _:
|
||||||
return new DrawableBanana(banana);
|
return caughtFruitPool.Get();
|
||||||
|
|
||||||
case Fruit fruit:
|
case Banana _:
|
||||||
return new DrawableFruit(fruit);
|
return caughtBananaPool.Get();
|
||||||
|
|
||||||
case TinyDroplet tiny:
|
case Droplet _:
|
||||||
return new DrawableTinyDroplet(tiny);
|
return caughtDropletPool.Get();
|
||||||
|
|
||||||
case Droplet droplet:
|
|
||||||
return new DrawableDroplet(droplet);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CaughtObject getDroppedObject(CaughtObject caughtObject)
|
||||||
|
{
|
||||||
|
var droppedObject = getCaughtObject(caughtObject.HitObject);
|
||||||
|
|
||||||
|
droppedObject.CopyStateFrom(caughtObject);
|
||||||
|
droppedObject.Anchor = Anchor.TopLeft;
|
||||||
|
droppedObject.Position = caughtObjectContainer.ToSpaceOfOtherDrawable(caughtObject.DrawPosition, droppedObjectTarget);
|
||||||
|
|
||||||
|
return droppedObject;
|
||||||
|
}
|
||||||
|
|
||||||
private void clearPlate(DroppedObjectAnimation animation)
|
private void clearPlate(DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var caughtObjects = caughtFruitContainer.Children.ToArray();
|
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
|
||||||
caughtFruitContainer.Clear(false);
|
|
||||||
|
|
||||||
droppedObjectTarget.AddRange(caughtObjects);
|
caughtObjectContainer.Clear(false);
|
||||||
|
|
||||||
foreach (var caughtObject in caughtObjects)
|
droppedObjectTarget.AddRange(droppedObjects);
|
||||||
drop(caughtObject, animation);
|
|
||||||
|
foreach (var droppedObject in droppedObjects)
|
||||||
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromPlate(DrawablePalpableCatchHitObject caughtObject, DroppedObjectAnimation animation)
|
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
if (!caughtFruitContainer.Remove(caughtObject))
|
var droppedObject = getDroppedObject(caughtObject);
|
||||||
throw new InvalidOperationException("Can only drop a caught object on the plate");
|
|
||||||
|
|
||||||
droppedObjectTarget.Add(caughtObject);
|
caughtObjectContainer.Remove(caughtObject);
|
||||||
|
|
||||||
drop(caughtObject, animation);
|
droppedObjectTarget.Add(droppedObject);
|
||||||
|
|
||||||
|
applyDropAnimation(droppedObject, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drop(DrawablePalpableCatchHitObject d, DroppedObjectAnimation animation)
|
private void applyDropAnimation(Drawable d, DroppedObjectAnimation animation)
|
||||||
{
|
{
|
||||||
var originalX = d.X * Scale.X;
|
switch (animation)
|
||||||
var startTime = Clock.CurrentTime;
|
|
||||||
|
|
||||||
d.Anchor = Anchor.TopLeft;
|
|
||||||
d.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(d.DrawPosition, droppedObjectTarget);
|
|
||||||
|
|
||||||
// we cannot just apply the transforms because DHO clears transforms when state is updated
|
|
||||||
d.ApplyCustomUpdateState += (o, state) => animate(o, animation, originalX, startTime);
|
|
||||||
if (d.IsLoaded)
|
|
||||||
animate(d, animation, originalX, startTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animate(Drawable d, DroppedObjectAnimation animation, float originalX, double startTime)
|
|
||||||
{
|
|
||||||
using (d.BeginAbsoluteSequence(startTime))
|
|
||||||
{
|
{
|
||||||
switch (animation)
|
case DroppedObjectAnimation.Drop:
|
||||||
{
|
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
||||||
case DroppedObjectAnimation.Drop:
|
d.FadeOut(750);
|
||||||
d.MoveToY(d.Y + 75, 750, Easing.InSine);
|
break;
|
||||||
d.FadeOut(750);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DroppedObjectAnimation.Explode:
|
case DroppedObjectAnimation.Explode:
|
||||||
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
|
var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X;
|
||||||
d.MoveToX(d.X + originalX * 6, 1000);
|
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
|
||||||
d.FadeOut(750);
|
d.MoveToX(d.X + originalX * 6, 1000);
|
||||||
break;
|
d.FadeOut(750);
|
||||||
}
|
break;
|
||||||
|
|
||||||
d.Expire();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum DroppedObjectAnimation
|
private enum DroppedObjectAnimation
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
public readonly Catcher MovableCatcher;
|
public readonly Catcher MovableCatcher;
|
||||||
private readonly CatchComboDisplay comboDisplay;
|
private readonly CatchComboDisplay comboDisplay;
|
||||||
|
|
||||||
public CatcherArea(Container droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
|
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
|
||||||
|
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>catch the fruit. to the beat.</Description>
|
<Description>catch the fruit. to the beat.</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="Nuget">
|
||||||
|
<Title>osu!catch (ruleset)</Title>
|
||||||
|
<PackageId>ppy.osu.Game.Rulesets.Catch</PackageId>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Label="Project References">
|
<ItemGroup Label="Project References">
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
|
"${workspaceRoot}/bin/Debug/net5.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/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll"
|
"${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||||
|
{
|
||||||
|
public class TestSceneManiaModConstantSpeed : ModTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestConstantScroll() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new ManiaModConstantSpeed(),
|
||||||
|
PassCondition = () =>
|
||||||
|
{
|
||||||
|
var hitObject = Player.ChildrenOfType<DrawableManiaHitObject>().FirstOrDefault();
|
||||||
|
return hitObject?.Dependencies.Get<IScrollingInfo>().Algorithm is ConstantScrollAlgorithm;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -355,7 +355,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
public ScoreAccessibleReplayPlayer(Score score)
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
: base(score, false, false)
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -176,7 +176,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
public ScoreAccessibleReplayPlayer(Score score)
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
: base(score, false, false)
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.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" />
|
||||||
|
@ -238,6 +238,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
new ManiaModMirror(),
|
new ManiaModMirror(),
|
||||||
new ManiaModDifficultyAdjust(),
|
new ManiaModDifficultyAdjust(),
|
||||||
new ManiaModInvert(),
|
new ManiaModInvert(),
|
||||||
|
new ManiaModConstantSpeed()
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
|
35
osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs
Normal file
35
osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModConstantSpeed : Mod, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||||
|
{
|
||||||
|
public override string Name => "Constant Speed";
|
||||||
|
|
||||||
|
public override string Acronym => "CS";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
public override string Description => "No more tricky speed changes!";
|
||||||
|
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.Equals;
|
||||||
|
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
public override bool Ranked => false;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
var maniaRuleset = (DrawableManiaRuleset)drawableRuleset;
|
||||||
|
maniaRuleset.ScrollMethod = ScrollVisualisationMethod.Constant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyManiaJudgementPiece : CompositeDrawable, IAnimatableJudgement
|
||||||
|
{
|
||||||
|
private readonly HitResult result;
|
||||||
|
private readonly Drawable animation;
|
||||||
|
|
||||||
|
public LegacyManiaJudgementPiece(HitResult result, Drawable animation)
|
||||||
|
{
|
||||||
|
this.result = result;
|
||||||
|
this.animation = animation;
|
||||||
|
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Both;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(ISkinSource skin)
|
||||||
|
{
|
||||||
|
float? scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
|
||||||
|
|
||||||
|
if (scorePosition != null)
|
||||||
|
scorePosition -= Stage.HIT_TARGET_POSITION + 150;
|
||||||
|
|
||||||
|
Y = scorePosition ?? 0;
|
||||||
|
|
||||||
|
if (animation != null)
|
||||||
|
{
|
||||||
|
InternalChild = animation.With(d =>
|
||||||
|
{
|
||||||
|
d.Anchor = Anchor.Centre;
|
||||||
|
d.Origin = Anchor.Centre;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayAnimation()
|
||||||
|
{
|
||||||
|
if (animation == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
(animation as IFramedAnimation)?.GotoFrame(0);
|
||||||
|
|
||||||
|
this.FadeInFromZero(20, Easing.Out)
|
||||||
|
.Then().Delay(160)
|
||||||
|
.FadeOutFromOne(40, Easing.In);
|
||||||
|
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case HitResult.None:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HitResult.Miss:
|
||||||
|
animation.ScaleTo(1.2f).Then().ScaleTo(1, 100, Easing.Out);
|
||||||
|
|
||||||
|
animation.RotateTo(0);
|
||||||
|
animation.RotateTo(RNG.NextSingle(-5.73f, 5.73f), 100, Easing.Out);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
animation.ScaleTo(0.8f)
|
||||||
|
.Then().ScaleTo(1, 40)
|
||||||
|
// this is actually correct to match stable; there were overlapping transforms.
|
||||||
|
.Then().ScaleTo(0.85f)
|
||||||
|
.Then().ScaleTo(0.7f, 40)
|
||||||
|
.Then().Delay(100)
|
||||||
|
.Then().ScaleTo(0.4f, 40, Easing.In);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable GetAboveHitObjectsProxiedContent() => null;
|
||||||
|
}
|
||||||
|
}
|
@ -136,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
|
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
|
||||||
?? default_hitresult_skin_filenames[result];
|
?? default_hitresult_skin_filenames[result];
|
||||||
|
|
||||||
return this.GetAnimation(filename, true, true);
|
var animation = this.GetAnimation(filename, true, true);
|
||||||
|
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SampleChannel GetSample(ISampleInfo sampleInfo)
|
public override SampleChannel GetSample(ISampleInfo sampleInfo)
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -20,37 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyMissAnimations()
|
|
||||||
{
|
|
||||||
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
|
|
||||||
{
|
|
||||||
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
|
|
||||||
JudgementBody.ScaleTo(1.6f);
|
|
||||||
JudgementBody.ScaleTo(1, 100, Easing.In);
|
|
||||||
|
|
||||||
JudgementBody.MoveTo(Vector2.Zero);
|
|
||||||
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
|
|
||||||
|
|
||||||
JudgementBody.RotateTo(0);
|
|
||||||
JudgementBody.RotateTo(40, 800, Easing.InQuint);
|
|
||||||
JudgementBody.FadeOutFromOne(800);
|
|
||||||
|
|
||||||
LifetimeEnd = JudgementBody.LatestTransformEndTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.ApplyMissAnimations();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplyHitAnimations()
|
|
||||||
{
|
|
||||||
JudgementBody.ScaleTo(0.8f);
|
|
||||||
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
|
|
||||||
|
|
||||||
JudgementBody.Delay(50)
|
|
||||||
.ScaleTo(0.75f, 250)
|
|
||||||
.FadeOut(200);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
|
||||||
|
|
||||||
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
|
||||||
@ -66,6 +34,27 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
JudgementText.Font = JudgementText.Font.With(size: 25);
|
JudgementText.Font = JudgementText.Font.With(size: 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void PlayAnimation()
|
||||||
|
{
|
||||||
|
base.PlayAnimation();
|
||||||
|
|
||||||
|
switch (Result)
|
||||||
|
{
|
||||||
|
case HitResult.None:
|
||||||
|
case HitResult.Miss:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.ScaleTo(0.8f);
|
||||||
|
this.ScaleTo(1, 250, Easing.OutElastic);
|
||||||
|
|
||||||
|
this.Delay(50)
|
||||||
|
.ScaleTo(0.75f, 250)
|
||||||
|
.FadeOut(200);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -11,6 +12,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
@ -23,6 +25,7 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -48,6 +51,22 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config;
|
||||||
|
|
||||||
|
public ScrollVisualisationMethod ScrollMethod
|
||||||
|
{
|
||||||
|
get => scrollMethod;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (IsLoaded)
|
||||||
|
throw new InvalidOperationException($"Can't alter {nameof(ScrollMethod)} after ruleset is already loaded");
|
||||||
|
|
||||||
|
scrollMethod = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScrollVisualisationMethod scrollMethod = ScrollVisualisationMethod.Sequential;
|
||||||
|
|
||||||
|
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
|
||||||
|
|
||||||
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
|
||||||
private readonly Bindable<double> configTimeRange = new BindableDouble();
|
private readonly Bindable<double> configTimeRange = new BindableDouble();
|
||||||
|
|
||||||
@ -132,6 +151,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
// 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 osu.Game.Replays;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
public class ManiaReplayRecorder : ReplayRecorder<ManiaAction>
|
public class ManiaReplayRecorder : ReplayRecorder<ManiaAction>
|
||||||
{
|
{
|
||||||
public ManiaReplayRecorder(Replay replay)
|
public ManiaReplayRecorder(Score score)
|
||||||
: base(replay)
|
: base(score)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Y = HIT_TARGET_POSITION + 150,
|
Y = HIT_TARGET_POSITION + 150
|
||||||
},
|
},
|
||||||
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,13 @@
|
|||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>smash the keys. to the beat.</Description>
|
<Description>smash the keys. to the beat.</Description>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Label="Nuget">
|
||||||
|
<Title>osu!mania (ruleset)</Title>
|
||||||
|
<PackageId>ppy.osu.Game.Rulesets.Mania</PackageId>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Label="Project References">
|
<ItemGroup Label="Project References">
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
|
"${workspaceRoot}/bin/Debug/net5.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/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
|
"${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
// 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 System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||||
@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
|||||||
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
public void TestNoAdjustment() => CreateModTest(new ModTestData
|
||||||
{
|
{
|
||||||
Mod = new OsuModDifficultyAdjust(),
|
Mod = new OsuModDifficultyAdjust(),
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
CircleSize = 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new HitCircle { StartTime = 1000 },
|
||||||
|
new HitCircle { StartTime = 2000 }
|
||||||
|
}
|
||||||
|
},
|
||||||
Autoplay = true,
|
Autoplay = true,
|
||||||
PassCondition = checkSomeHit
|
PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
|
||||||
});
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -1,103 +1,91 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.IO.Stores;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Play;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneLegacyBeatmapSkin : ScreenTestScene
|
public class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audio { get; set; }
|
private AudioManager audio { get; set; }
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuConfigManager config)
|
||||||
|
{
|
||||||
|
config.BindWith(OsuSetting.BeatmapSkins, BeatmapSkins);
|
||||||
|
config.BindWith(OsuSetting.BeatmapColours, BeatmapColours);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(true, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, true)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
|
||||||
|
{
|
||||||
|
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
|
||||||
|
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
public void TestBeatmapComboColours(bool customSkinColoursPresent)
|
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
ExposedPlayer player = null;
|
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
|
||||||
AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
|
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
AddUntilStep("wait for player", () => player.IsLoaded);
|
|
||||||
|
|
||||||
AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(true)]
|
||||||
public void TestBeatmapNoComboColours()
|
[TestCase(false)]
|
||||||
|
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
|
||||||
{
|
{
|
||||||
ExposedPlayer player = null;
|
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
|
||||||
|
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
|
||||||
AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
AddUntilStep("wait for player", () => player.IsLoaded);
|
|
||||||
|
|
||||||
AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(true, true)]
|
||||||
public void TestBeatmapNoComboColoursSkinOverride()
|
[TestCase(false, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
ExposedPlayer player = null;
|
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||||
|
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
|
||||||
AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
|
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
|
||||||
AddUntilStep("wait for player", () => player.IsLoaded);
|
|
||||||
|
|
||||||
AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
|
[TestCase(true, true)]
|
||||||
|
[TestCase(false, true)]
|
||||||
|
[TestCase(true, false)]
|
||||||
|
[TestCase(false, false)]
|
||||||
|
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
|
||||||
{
|
{
|
||||||
ExposedPlayer player;
|
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
|
||||||
|
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
|
||||||
Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
|
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
|
||||||
|
|
||||||
LoadScreen(player = new ExposedPlayer(userHasCustomColours));
|
|
||||||
|
|
||||||
return player;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ExposedPlayer : Player
|
private class OsuCustomSkinWorkingBeatmap : CustomSkinWorkingBeatmap
|
||||||
{
|
{
|
||||||
private readonly bool userHasCustomColours;
|
public OsuCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
|
||||||
|
: base(createBeatmap(), audio, hasColours)
|
||||||
public ExposedPlayer(bool userHasCustomColours)
|
|
||||||
: base(false, false)
|
|
||||||
{
|
{
|
||||||
this.userHasCustomColours = userHasCustomColours;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
private static IBeatmap createBeatmap() =>
|
||||||
{
|
new Beatmap
|
||||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
|
||||||
dependencies.CacheAs<ISkinSource>(new TestSkin(userHasCustomColours));
|
|
||||||
return dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<Color4> UsableComboColours =>
|
|
||||||
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
|
|
||||||
.First()
|
|
||||||
.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
|
||||||
{
|
|
||||||
private readonly bool hasColours;
|
|
||||||
|
|
||||||
public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
|
|
||||||
: base(new Beatmap
|
|
||||||
{
|
{
|
||||||
BeatmapInfo =
|
BeatmapInfo =
|
||||||
{
|
{
|
||||||
@ -105,50 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
},
|
},
|
||||||
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
|
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
|
||||||
}, null, null, audio)
|
};
|
||||||
{
|
|
||||||
this.hasColours = hasColours;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestBeatmapSkin : LegacyBeatmapSkin
|
|
||||||
{
|
|
||||||
public static Color4[] Colours { get; } =
|
|
||||||
{
|
|
||||||
new Color4(50, 100, 150, 255),
|
|
||||||
new Color4(40, 80, 120, 255),
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
|
|
||||||
: base(beatmap, new ResourceStore<byte[]>(), null)
|
|
||||||
{
|
|
||||||
if (hasColours)
|
|
||||||
Configuration.AddComboColours(Colours);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestSkin : LegacySkin, ISkinSource
|
|
||||||
{
|
|
||||||
public static Color4[] Colours { get; } =
|
|
||||||
{
|
|
||||||
new Color4(150, 100, 50, 255),
|
|
||||||
new Color4(20, 20, 20, 255),
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestSkin(bool hasCustomColours)
|
|
||||||
: base(new SkinInfo(), null, null, string.Empty)
|
|
||||||
{
|
|
||||||
if (hasCustomColours)
|
|
||||||
Configuration.AddComboColours(Colours);
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action SourceChanged
|
|
||||||
{
|
|
||||||
add { }
|
|
||||||
remove { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,31 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
checkNextHitObject(null);
|
checkNextHitObject(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapColourDefault()
|
||||||
|
{
|
||||||
|
AddStep("enable user provider", () => testUserSkin.Enabled = true);
|
||||||
|
|
||||||
|
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true));
|
||||||
|
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true));
|
||||||
|
checkNextHitObject("beatmap");
|
||||||
|
|
||||||
|
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true));
|
||||||
|
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false));
|
||||||
|
checkNextHitObject("beatmap");
|
||||||
|
|
||||||
|
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false));
|
||||||
|
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true));
|
||||||
|
checkNextHitObject("user");
|
||||||
|
|
||||||
|
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false));
|
||||||
|
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false));
|
||||||
|
checkNextHitObject("user");
|
||||||
|
|
||||||
|
AddStep("disable user provider", () => testUserSkin.Enabled = false);
|
||||||
|
checkNextHitObject(null);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkNextHitObject(string skin) =>
|
private void checkNextHitObject(string skin) =>
|
||||||
AddUntilStep($"check skin from {skin}", () =>
|
AddUntilStep($"check skin from {skin}", () =>
|
||||||
{
|
{
|
||||||
|
@ -378,7 +378,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
public ScoreAccessibleReplayPlayer(Score score)
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
: base(score, false, false)
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestLongSpinner(bool autoplay)
|
public void TestLongSpinner(bool autoplay)
|
||||||
{
|
{
|
||||||
AddStep("Very short spinner", () => SetContents(() => testSingle(5, autoplay, 2000)));
|
AddStep("Very long spinner", () => SetContents(() => testSingle(5, autoplay, 4000)));
|
||||||
AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult);
|
AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult);
|
||||||
AddUntilStep("Check correct progress", () => drawableSpinner.Progress == (autoplay ? 1 : 0));
|
AddUntilStep("Check correct progress", () => drawableSpinner.Progress == (autoplay ? 1 : 0));
|
||||||
}
|
}
|
||||||
@ -55,7 +57,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var spinner = new Spinner
|
var spinner = new Spinner
|
||||||
{
|
{
|
||||||
StartTime = Time.Current + delay,
|
StartTime = Time.Current + delay,
|
||||||
EndTime = Time.Current + delay + length
|
EndTime = Time.Current + delay + length,
|
||||||
|
Samples = new List<HitSampleInfo>
|
||||||
|
{
|
||||||
|
new HitSampleInfo("hitnormal")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
||||||
|
@ -25,7 +25,7 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
|
public class TestSceneStartTimeOrderedHitPolicy : RateAdjustedBeatmapTestScene
|
||||||
{
|
{
|
||||||
private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss
|
private const double early_miss_window = 1000; // time after -1000 to -500 is considered a miss
|
||||||
private const double late_miss_window = 500; // time after +500 is considered a miss
|
private const double late_miss_window = 500; // time after +500 is considered a miss
|
||||||
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
addJudgementAssert(hitObjects[0], HitResult.Great);
|
addJudgementAssert(hitObjects[0], HitResult.Great);
|
||||||
addJudgementAssert(hitObjects[1], HitResult.Great);
|
addJudgementAssert(hitObjects[1], HitResult.Great);
|
||||||
addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200
|
addJudgementOffsetAssert(hitObjects[0], -200); // time_first_circle - 200
|
||||||
addJudgementOffsetAssert(hitObjects[0], -200); // time_second_circle - first_circle_time - 100
|
addJudgementOffsetAssert(hitObjects[1], -200); // time_second_circle - first_circle_time - 100
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -439,7 +439,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
protected override bool PauseOnFocusLost => false;
|
protected override bool PauseOnFocusLost => false;
|
||||||
|
|
||||||
public ScoreAccessibleReplayPlayer(Score score)
|
public ScoreAccessibleReplayPlayer(Score score)
|
||||||
: base(score, false, false)
|
: base(score, new PlayerConfiguration
|
||||||
|
{
|
||||||
|
AllowPause = false,
|
||||||
|
ShowResults = false,
|
||||||
|
})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,14 +2,14 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>net5.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" />
|
||||||
|
@ -12,5 +12,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
public double ApproachRate;
|
public double ApproachRate;
|
||||||
public double OverallDifficulty;
|
public double OverallDifficulty;
|
||||||
public int HitCircleCount;
|
public int HitCircleCount;
|
||||||
|
public int SpinnerCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
maxCombo += beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
|
||||||
|
|
||||||
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
int hitCirclesCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
||||||
|
int spinnerCount = beatmap.HitObjects.Count(h => h is Spinner);
|
||||||
|
|
||||||
return new OsuDifficultyAttributes
|
return new OsuDifficultyAttributes
|
||||||
{
|
{
|
||||||
@ -59,6 +60,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
OverallDifficulty = (80 - hitWindowGreat) / 6,
|
||||||
MaxCombo = maxCombo,
|
MaxCombo = maxCombo,
|
||||||
HitCircleCount = hitCirclesCount,
|
HitCircleCount = hitCirclesCount,
|
||||||
|
SpinnerCount = spinnerCount,
|
||||||
Skills = skills
|
Skills = skills
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModNoFail))
|
if (mods.Any(m => m is OsuModNoFail))
|
||||||
multiplier *= 0.90;
|
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
|
||||||
|
|
||||||
if (mods.Any(m => m is OsuModSpunOut))
|
if (mods.Any(m => m is OsuModSpunOut))
|
||||||
multiplier *= 0.95;
|
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
|
||||||
|
|
||||||
double aimValue = computeAimValue();
|
double aimValue = computeAimValue();
|
||||||
double speedValue = computeSpeedValue();
|
double speedValue = computeSpeedValue();
|
||||||
@ -92,8 +92,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
aimValue *= lengthBonus;
|
aimValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
aimValue *= Math.Pow(0.97, countMiss);
|
if (countMiss > 0)
|
||||||
|
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
|
||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
@ -103,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (Attributes.ApproachRate > 10.33)
|
if (Attributes.ApproachRate > 10.33)
|
||||||
approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
|
approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
|
||||||
else if (Attributes.ApproachRate < 8.0)
|
else if (Attributes.ApproachRate < 8.0)
|
||||||
approachRateFactor += 0.1 * (8.0 - Attributes.ApproachRate);
|
approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
|
||||||
|
|
||||||
aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
|
aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
|
||||||
|
|
||||||
@ -138,8 +139,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||||
speedValue *= lengthBonus;
|
speedValue *= lengthBonus;
|
||||||
|
|
||||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||||
speedValue *= Math.Pow(0.97, countMiss);
|
if (countMiss > 0)
|
||||||
|
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
|
||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
@ -154,10 +156,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
if (mods.Any(m => m is OsuModHidden))
|
if (mods.Any(m => m is OsuModHidden))
|
||||||
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
|
||||||
|
|
||||||
// Scale the speed value with accuracy _slightly_
|
// Scale the speed value with accuracy and OD
|
||||||
speedValue *= 0.02 + accuracy;
|
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
|
||||||
// It is important to also consider accuracy difficulty when doing that
|
// Scale the speed value with # of 50s to punish doubletapping.
|
||||||
speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
|
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
|
||||||
|
|
||||||
return speedValue;
|
return speedValue;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => CirclePiece.ScreenSpaceDrawQuad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IEditorChangeHandler changeHandler { get; set; }
|
private IEditorChangeHandler changeHandler { get; set; }
|
||||||
|
|
||||||
|
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||||
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
private readonly IBindable<int> pathVersion = new Bindable<int>();
|
||||||
|
|
||||||
|
@ -157,10 +157,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
{
|
||||||
h.Position = new Vector2(
|
var newPosition = h.Position;
|
||||||
quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X),
|
|
||||||
quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y)
|
// guard against no-ops and NaN.
|
||||||
);
|
if (scale.X != 0 && quad.Width > 0)
|
||||||
|
newPosition.X = quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X);
|
||||||
|
|
||||||
|
if (scale.Y != 0 && quad.Height > 0)
|
||||||
|
newPosition.Y = quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y);
|
||||||
|
|
||||||
|
h.Position = newPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +236,20 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
||||||
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
||||||
getSurroundingQuad(hitObjects.SelectMany(h => new[] { h.Position, h.EndPosition }));
|
getSurroundingQuad(hitObjects.SelectMany(h =>
|
||||||
|
{
|
||||||
|
if (h is IHasPath path)
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
h.Position,
|
||||||
|
// can't use EndPosition for reverse slider cases.
|
||||||
|
h.Position + path.Path.PositionAt(1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new[] { h.Position };
|
||||||
|
}));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a gamefield-space quad surrounding the provided points.
|
/// Returns a gamefield-space quad surrounding the provided points.
|
||||||
|
@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
base.ApplySettings(difficulty);
|
base.ApplySettings(difficulty);
|
||||||
|
|
||||||
difficulty.CircleSize = CircleSize.Value;
|
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
|
||||||
difficulty.ApproachRate = ApproachRate.Value;
|
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
var destination = e.MousePosition;
|
var destination = e.MousePosition;
|
||||||
|
|
||||||
FlashlightPosition = Interpolation.ValueAt(
|
FlashlightPosition = Interpolation.ValueAt(
|
||||||
Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
Math.Min(Math.Abs(Clock.ElapsedFrameTime), follow_delay), position, destination, 0, follow_delay, Easing.Out);
|
||||||
|
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
@ -130,12 +130,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
if (tracking.NewValue)
|
if (tracking.NewValue)
|
||||||
{
|
{
|
||||||
spinningSample?.Play();
|
spinningSample?.Play(!spinningSample.IsPlaying);
|
||||||
spinningSample?.VolumeTo(1, 200);
|
spinningSample?.VolumeTo(1, 300);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
spinningSample?.VolumeTo(0, 200).Finally(_ => spinningSample.Stop());
|
spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +243,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
if (HandleUserInput)
|
if (HandleUserInput)
|
||||||
RotationTracker.Tracking = !Result.HasResult && (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
{
|
||||||
|
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
||||||
|
bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||||
|
|
||||||
|
RotationTracker.Tracking = !Result.HasResult
|
||||||
|
&& correctButtonPressed
|
||||||
|
&& isValidSpinningTime;
|
||||||
|
}
|
||||||
|
|
||||||
if (spinningSample != null && spinnerFrequencyModulate)
|
if (spinningSample != null && spinnerFrequencyModulate)
|
||||||
spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress;
|
spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress;
|
||||||
@ -255,6 +262,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
if (!SpmCounter.IsPresent && RotationTracker.Tracking)
|
||||||
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
SpmCounter.FadeIn(HitObject.TimeFadeIn);
|
||||||
|
|
||||||
SpmCounter.SetRotation(Result.RateAdjustedRotation);
|
SpmCounter.SetRotation(Result.RateAdjustedRotation);
|
||||||
|
|
||||||
updateBonusScore();
|
updateBonusScore();
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
protected override bool Handle(UIEvent e)
|
protected override bool Handle(UIEvent e)
|
||||||
{
|
{
|
||||||
if (e is MouseMoveEvent && !AllowUserCursorMovement) return false;
|
if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false;
|
||||||
|
|
||||||
return base.Handle(e);
|
return base.Handle(e);
|
||||||
}
|
}
|
||||||
|
@ -4,16 +4,21 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||||
{
|
{
|
||||||
public class SpinnerSpmCounter : Container
|
public class SpinnerSpmCounter : Container
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject drawableSpinner { get; set; }
|
||||||
|
|
||||||
private readonly OsuSpriteText spmText;
|
private readonly OsuSpriteText spmText;
|
||||||
|
|
||||||
public SpinnerSpmCounter()
|
public SpinnerSpmCounter()
|
||||||
@ -38,6 +43,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
drawableSpinner.HitObjectApplied += resetState;
|
||||||
|
}
|
||||||
|
|
||||||
private double spm;
|
private double spm;
|
||||||
|
|
||||||
public double SpinsPerMinute
|
public double SpinsPerMinute
|
||||||
@ -82,5 +93,19 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
|
|
||||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void resetState(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
SpinsPerMinute = 0;
|
||||||
|
records.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableSpinner != null)
|
||||||
|
drawableSpinner.HitObjectApplied -= resetState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
private double lastTrailTime;
|
private double lastTrailTime;
|
||||||
private IBindable<float> cursorSize;
|
private IBindable<float> cursorSize;
|
||||||
|
|
||||||
public LegacyCursorTrail()
|
|
||||||
{
|
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, OsuConfigManager config)
|
private void load(ISkinSource skin, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture("cursortrail");
|
Texture = skin.GetTexture("cursortrail");
|
||||||
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
disjointTrail = skin.GetTexture("cursormiddle") == null;
|
||||||
|
|
||||||
|
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
|
||||||
|
|
||||||
if (Texture != null)
|
if (Texture != null)
|
||||||
{
|
{
|
||||||
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
|
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
|
||||||
|
@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Configuration;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Replays;
|
using osu.Game.Rulesets.Osu.Replays;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
|
protected override ReplayRecorder CreateReplayRecorder(Score score) => new OsuReplayRecorder(score);
|
||||||
|
|
||||||
public override double GameplayStartTime
|
public override double GameplayStartTime
|
||||||
{
|
{
|
||||||
|
31
osu.Game.Rulesets.Osu/UI/IHitPolicy.cs
Normal file
31
osu.Game.Rulesets.Osu/UI/IHitPolicy.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
public interface IHitPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="IHitObjectContainer"/> containing the <see cref="DrawableHitObject"/>s which this <see cref="IHitPolicy"/> applies to.
|
||||||
|
/// </summary>
|
||||||
|
IHitObjectContainer HitObjectContainer { set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether a <see cref="DrawableHitObject"/> can be hit at a point in time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to check.</param>
|
||||||
|
/// <param name="time">The time to check.</param>
|
||||||
|
/// <returns>Whether <paramref name="hitObject"/> can be hit at the given <paramref name="time"/>.</returns>
|
||||||
|
bool IsHittable(DrawableHitObject hitObject, double time);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles a <see cref="HitObject"/> being hit.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="HitObject"/> that was hit.</param>
|
||||||
|
void HandleHit(DrawableHitObject hitObject);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user