1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 17:33:02 +08:00

Compare commits

..

1804 Commits

796 changed files with 19347 additions and 6689 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
]
},
"jetbrains.resharper.globaltools": {
"version": "2020.2.4",
"version": "2020.3.2",
"commands": [
"jb"
]
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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">
<option name="Build" enabled="true" />
</method>
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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" />
<method v="2">
<option name="Build" />
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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" />
<method v="2">
<option name="Build" />
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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" />
<method v="2">
<option name="Build" />
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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" />
<method v="2">
<option name="Build" />
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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">
<option name="Build" />
</method>
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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" />
<method v="2">
<option name="Build" />
+3 -3
View File
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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">
<option name="Build" />
</method>
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<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="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="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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">
<option name="Build" />
</method>
@@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! SDL" 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="PROGRAM_PARAMETERS" value="--sdl" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
<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/net5.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tk" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<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">
<option name="Build" />
</method>
+9 -9
View File
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
@@ -19,7 +19,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
@@ -31,7 +31,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build tests (Debug)",
@@ -43,7 +43,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build tests (Release)",
@@ -55,7 +55,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -68,7 +68,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -81,7 +81,7 @@
"request": "launch",
"program": "dotnet",
"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"
],
"cwd": "${workspaceRoot}",
@@ -94,7 +94,7 @@
"request": "launch",
"program": "dotnet",
"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"
],
"cwd": "${workspaceRoot}",
@@ -105,7 +105,7 @@
"name": "Benchmark",
"type": "coreclr",
"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": [
"--filter",
"*"
+15 -7
View File
@@ -16,9 +16,9 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<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" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.1" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2" PrivateAssets="All" />
</ItemGroup>
<PropertyGroup Label="Code Analysis">
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
@@ -28,9 +28,17 @@
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<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 -->
<NoWarn>$(NoWarn);NU1701</NoWarn>
<!--
NU1701:
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 Label="Nuget">
<IsPackable>false</IsPackable>
@@ -40,7 +48,7 @@
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
<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>
</PropertyGroup>
</Project>
</Project>
+29 -27
View File
@@ -1,27 +1,27 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.2)
CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
aws-partitions (1.354.0)
aws-sdk-core (3.104.3)
aws-partitions (1.413.0)
aws-sdk-core (3.110.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.36.0)
aws-sdk-core (~> 3, >= 3.99.0)
aws-sdk-kms (1.40.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.78.0)
aws-sdk-core (~> 3, >= 3.104.3)
aws-sdk-s3 (1.87.0)
aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.1)
aws-sigv4 (1.2.2)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.3)
babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
@@ -29,22 +29,23 @@ GEM
highline (~> 1.7.2)
declarative (0.0.20)
declarative-option (0.1.0)
digest-crc (0.6.1)
rake (~> 13.0)
digest-crc (0.6.3)
rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.0.0)
excon (0.76.0)
faraday (1.0.1)
emoji_regex (3.2.1)
excon (0.78.1)
faraday (1.2.0)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
ruby2_keywords
faraday-cookie_jar (0.0.7)
faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
fastimage (2.2.0)
fastlane (2.156.0)
fastimage (2.2.1)
fastlane (2.170.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
@@ -96,17 +97,17 @@ GEM
google-cloud-core (1.5.0)
google-cloud-env (~> 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)
google-cloud-errors (1.0.1)
google-cloud-storage (1.27.0)
google-cloud-storage (1.29.2)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.13.1)
googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -118,10 +119,10 @@ GEM
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
json (2.3.1)
jwt (2.2.1)
json (2.5.1)
jwt (2.2.2)
memoist (0.16.2)
mini_magick (4.10.1)
mini_magick (4.11.0)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
multi_json (1.15.0)
@@ -132,14 +133,15 @@ GEM
mini_portile2 (~> 2.4.0)
os (1.1.1)
plist (3.5.0)
public_suffix (4.0.5)
rake (13.0.1)
public_suffix (4.0.6)
rake (13.0.3)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
ruby2_keywords (0.0.2)
rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
@@ -168,7 +170,7 @@ GEM
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
xcodeproj (1.18.0)
xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
+1 -1
View File
@@ -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
of this software and associated documentation files (the "Software"), to deal
+3 -4
View File
@@ -5,7 +5,7 @@
# osu!
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu)
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)]()
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
@@ -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.
- 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.
## 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:
- 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 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.
+60 -13
View File
@@ -1,21 +1,68 @@
clone_depth: 1
version: '{build}'
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
skip_non_tags: true
configuration: Release
build:
project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
parallel: true
verbosity: minimal
publish_nuget: true
environment:
matrix:
- job_name: osu-game
- 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:
- provider: Environment
name: nuget
name: nuget
+12 -6
View File
@@ -48,9 +48,12 @@ desc 'Deploy to play store'
desc 'Compile the project'
lane :build do |options|
nuget_restore(
project_path: 'osu.sln'
)
nuget_restore(project_path: 'osu.Android/osu.Android.csproj')
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(
build_configuration: 'Release',
@@ -107,9 +110,12 @@ platform :ios do
desc 'Compile the project'
lane :build do
nuget_restore(
project_path: 'osu.sln'
)
nuget_restore(project_path: 'osu.iOS/osu.iOS.csproj')
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(
platform: "ios",
-10
View File
@@ -1,10 +0,0 @@
{
"sdk": {
"allowPrerelease": false,
"rollForward": "minor",
"version": "3.1.100"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "3.0.2"
}
}
+1 -1
View File
@@ -52,6 +52,6 @@
</ItemGroup>
<ItemGroup>
<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>
</Project>
+86 -2
View File
@@ -1,18 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Net;
using Android.OS;
using Android.Provider;
using Android.Views;
using osu.Framework.Android;
using osu.Game.Database;
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
{
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)
{
@@ -23,8 +39,76 @@ namespace osu.Android
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.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);
}
}
+14 -5
View File
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Users;
@@ -26,18 +27,20 @@ namespace osu.Desktop
[Resolved]
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<UserActivity> activity = new Bindable<UserActivity>();
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
private readonly RichPresence presence = new RichPresence
{
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
};
[BackgroundDependencyLoader]
private void load(IAPIProvider provider)
private void load(IAPIProvider provider, OsuConfigManager config)
{
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);
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
{
status.UnbindBindings();
@@ -63,6 +68,7 @@ namespace osu.Desktop
ruleset.BindValueChanged(_ => updateStatus());
status.BindValueChanged(_ => updateStatus());
activity.BindValueChanged(_ => updateStatus());
privacyMode.BindValueChanged(_ => updateStatus());
client.Initialize();
}
@@ -78,7 +84,7 @@ namespace osu.Desktop
if (!client.IsInitialized)
return;
if (status.Value is UserStatusOffline)
if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
{
client.ClearPresence();
return;
@@ -96,7 +102,10 @@ namespace osu.Desktop
}
// 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
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
@@ -137,7 +146,7 @@ namespace osu.Desktop
return edit.Beatmap.ToString();
case UserActivity.InLobby lobby:
return lobby.Room.Name.Value;
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
return string.Empty;
+16 -8
View File
@@ -5,6 +5,7 @@ using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Overlays;
@@ -56,16 +57,16 @@ namespace osu.Desktop
string stableInstallPath;
try
if (OperatingSystem.IsWindows())
{
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
try
{
stableInstallPath = getStableInstallPathFromRegistry();
if (checkExists(stableInstallPath))
return stableInstallPath;
}
catch
{
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
return stableInstallPath;
}
catch { }
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
@@ -79,6 +80,13 @@ namespace osu.Desktop
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()
{
switch (RuntimeInfo.OS)
+24 -15
View File
@@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
Alpha = 0;
FillFlowContainer mainFill;
Children = new Drawable[]
{
new FillFlowContainer
mainFill = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
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()
+4 -7
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Project">
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<OutputType>WinExe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<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" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NETCore.Targets" 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.Design" Version="2.2.6" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
<!-- .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" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
</ItemGroup>
<ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" />
+1 -1
View File
@@ -11,7 +11,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
<releaseNotes>testing</releaseNotes>
<copyright>Copyright (c) 2020 ppy Pty Ltd</copyright>
<copyright>Copyright (c) 2021 ppy Pty Ltd</copyright>
<language>en-AU</language>
</metadata>
<files>
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>
+2 -2
View File
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build (Release)",
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public float Position
{
get => HitObject?.X ?? position;
get => HitObject?.EffectiveX ?? position;
set => position = value;
}
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -1,26 +1,295 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Catch.UI;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestSceneCatcher : CatchSkinnableTestScene
public class TestSceneCatcher : OsuTestScene
{
[BackgroundDependencyLoader]
private void load()
[Resolved]
private OsuConfigManager config { get; set; }
private Container<CaughtObject> droppedObjectContainer;
private TestCatcher catcher;
[SetUp]
public void SetUp() => Schedule(() =>
{
SetContents(() => new Catcher(new Container())
var difficulty = new BeatmapDifficulty
{
CircleSize = 0,
};
var trailContainer = new Container();
droppedObjectContainer = new Container<CaughtObject>();
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
Child = new Container
{
RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
trailContainer,
droppedObjectContainer,
catcher
}
};
});
[Test]
public void TestCatcherHyperStateReverted()
{
DrawableCatchHitObject drawableObject1 = null;
DrawableCatchHitObject drawableObject2 = null;
JudgementResult result1 = null;
JudgementResult result2 = null;
AddStep("catch hyper fruit", () =>
{
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
});
AddStep("catch normal fruit", () =>
{
attemptCatch(new Fruit(), out drawableObject2, out result2);
});
AddStep("revert second result", () =>
{
catcher.OnRevertResult(drawableObject2, result2);
});
checkHyperDash(true);
AddStep("revert first result", () =>
{
catcher.OnRevertResult(drawableObject1, result1);
});
checkHyperDash(false);
}
[Test]
public void TestCatcherAnimationStateReverted()
{
DrawableCatchHitObject drawableObject = null;
JudgementResult result = null;
AddStep("catch kiai fruit", () =>
{
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
});
checkState(CatcherAnimationState.Kiai);
AddStep("revert result", () =>
{
catcher.OnRevertResult(drawableObject, result);
});
checkState(CatcherAnimationState.Idle);
}
[Test]
public void TestCatcherCatchWidth()
{
var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
AddStep("catch fruit", () =>
{
attemptCatch(new Fruit { X = -halfWidth + 1 });
attemptCatch(new Fruit { X = halfWidth - 1 });
});
checkPlate(2);
AddStep("miss fruit", () =>
{
attemptCatch(new Fruit { X = -halfWidth - 1 });
attemptCatch(new Fruit { X = halfWidth + 1 });
});
checkPlate(2);
}
[Test]
public void TestFruitChangesCatcherState()
{
AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 }));
checkState(CatcherAnimationState.Fail);
AddStep("catch fruit", () => attemptCatch(new Fruit()));
checkState(CatcherAnimationState.Idle);
AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit()));
checkState(CatcherAnimationState.Kiai);
}
[Test]
public void TestNormalFruitResetsHyperDashState()
{
AddStep("catch hyper fruit", () => attemptCatch(new Fruit
{
HyperDashTarget = new Fruit { X = 100 }
}));
checkHyperDash(true);
AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
checkHyperDash(false);
}
[Test]
public void TestTinyDropletMissPreservesCatcherState()
{
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
{
HyperDashTarget = new Fruit { X = 100 }
}));
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 }));
// catcher state and hyper dash state is preserved
checkState(CatcherAnimationState.Kiai);
checkHyperDash(true);
}
[Test]
public void TestBananaMissPreservesCatcherState()
{
AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
{
HyperDashTarget = new Fruit { X = 100 }
}));
AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
// catcher state is preserved but hyper dash state is reset
checkState(CatcherAnimationState.Kiai);
checkHyperDash(false);
}
[Test]
public void TestCatcherStacking()
{
AddStep("catch fruit", () => attemptCatch(new Fruit()));
checkPlate(1);
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
checkPlate(10);
AddAssert("caught objects are stacked", () =>
catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
catcher.CaughtObjects.Any(obj => obj.Y < -20));
}
[Test]
public void TestCatcherExplosionAndDropping()
{
AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1);
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
AddStep("explode", () => catcher.Explode());
AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
AddStep("catch fruits", () => attemptCatch(new Fruit(), 10));
AddStep("drop", () => catcher.Drop());
AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
}
[Test]
public void TestHitLightingColour()
{
var fruitColour = SkinConfiguration.DefaultComboColours[1];
AddStep("enable hit lighting", () => config.Set(OsuSetting.HitLighting, true));
AddStep("catch fruit", () => attemptCatch(new Fruit()));
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 checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
private void attemptCatch(CatchHitObject hitObject, int count = 1)
{
for (var i = 0; i < count; i++)
attemptCatch(hitObject, out _, out _);
}
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
{
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
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 += _ =>
{
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
};
}
private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
{
switch (hitObject)
{
case Banana banana:
return new DrawableBanana(banana);
case Droplet droplet:
return new DrawableDroplet(droplet);
case Fruit fruit:
return new DrawableFruit(fruit);
default:
throw new ArgumentOutOfRangeException(nameof(hitObject));
}
}
public class TestCatcher : Catcher
{
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
public TestCatcher(Container trailsTarget, Container<CaughtObject> droppedObjectTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, droppedObjectTarget, difficulty)
{
}
}
public class TestKiaiFruit : Fruit
{
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
}
}
}
}
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -14,7 +15,6 @@ using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Tests
@@ -27,81 +27,70 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved]
private OsuConfigManager config { get; set; }
private Catcher catcher => this.ChildrenOfType<CatcherArea>().First().MovableCatcher;
private Catcher catcher => this.ChildrenOfType<Catcher>().First();
private float circleSize;
public TestSceneCatcherArea()
{
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
AddToggleStep("Hyperdash", t =>
CreatedDrawables.OfType<CatchInputManager>().Select(i => i.Child)
.OfType<TestCatcherArea>().ForEach(c => c.ToggleHyperDash(t)));
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
{
X = catcher.X
}), 20);
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
{
X = catcher.X,
LastInCombo = true,
}), 20);
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
{
X = catcher.X
}), 20);
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
{
X = catcher.X + 100,
LastInCombo = true,
}, true), 20);
AddStep("catch fruit", () => attemptCatch(new Fruit()));
AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true }));
AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit()));
AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true }));
}
[TestCase(true)]
[TestCase(false)]
public void TestHitLighting(bool enable)
private void attemptCatch(Fruit fruit)
{
AddStep("create catcher", () => createCatcher(5));
AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
AddStep("catch fruit", () => catchFruit(new TestFruit(false)
fruit.X = fruit.OriginalX + catcher.X;
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
{
X = catcher.X
}));
AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
{
X = catcher.X,
LastInCombo = true
}));
AddAssert("check hit explosion", () => catcher.ChildrenOfType<HitExplosion>().Any() == enable);
}
CircleSize = circleSize
});
private void catchFruit(Fruit fruit, bool miss = false)
{
this.ChildrenOfType<CatcherArea>().ForEach(area =>
foreach (var area in this.ChildrenOfType<CatcherArea>())
{
DrawableFruit drawable = new DrawableFruit(fruit);
area.Add(drawable);
Schedule(() =>
{
area.AttemptCatch(fruit);
area.OnNewResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
area.OnNewResult(drawable, new CatchJudgementResult(fruit, new CatchJudgement())
{
Type = area.MovableCatcher.CanCatch(fruit) ? HitResult.Great : HitResult.Miss
});
drawable.Expire();
});
});
}
}
private void createCatcher(float size)
{
SetContents(() => new CatchInputManager(catchRuleset)
circleSize = size;
SetContents(() =>
{
RelativeSizeAxes = Axes.Both,
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
var droppedObjectContainer = new Container<CaughtObject>
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
},
RelativeSizeAxes = Axes.Both
};
return new CatchInputManager(catchRuleset)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
droppedObjectContainer,
new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.Centre,
Origin = Anchor.TopCentre,
}
}
};
});
}
@@ -111,26 +100,13 @@ namespace osu.Game.Rulesets.Catch.Tests
catchRuleset = rulesets.GetRuleset(2);
}
public class TestFruit : Fruit
{
public TestFruit(bool kiai)
{
var kiaiCpi = new ControlPointInfo();
kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
}
}
private class TestCatcherArea : CatcherArea
{
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
: base(beatmapDifficulty)
public TestCatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
: base(droppedObjectContainer, beatmapDifficulty)
{
}
public new Catcher MovableCatcher => base.MovableCatcher;
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
@@ -3,11 +3,12 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -37,39 +38,50 @@ namespace osu.Game.Rulesets.Catch.Tests
}
private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
SetProperties(new DrawableFruit(new Fruit
new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{
IndexInBeatmap = indexInBeatmap,
HyperDashBindable = { Value = hyperdash }
}));
private Drawable createDrawableBanana() =>
SetProperties(new DrawableBanana(new Banana()));
new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
private Drawable createDrawableDroplet(bool hyperdash = false) =>
SetProperties(new DrawableDroplet(new Droplet
new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{
HyperDashBindable = { Value = hyperdash }
}));
private Drawable createDrawableTinyDroplet() => SetProperties(new DrawableTinyDroplet(new TinyDroplet()));
private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
}
protected virtual DrawableCatchHitObject SetProperties(DrawableCatchHitObject d)
public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
{
public readonly ManualClock ManualClock;
public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
{
AutoSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
ManualClock = new ManualClock();
Clock = new FramedClock(ManualClock);
var hitObject = d.HitObject;
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 0 });
hitObject.StartTime = 1000000000000;
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
hitObject.Scale = 1.5f;
hitObject.StartTime = 500;
d.Anchor = Anchor.Centre;
d.RelativePositionAxes = Axes.None;
d.Position = Vector2.Zero;
d.HitObjectApplied += _ =>
{
d.LifetimeStart = double.NegativeInfinity;
d.LifetimeEnd = double.PositiveInfinity;
};
return d;
InternalChild = d;
}
}
}
@@ -0,0 +1,76 @@
// 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 NUnit.Framework;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneFruitRandomness : OsuTestScene
{
private readonly DrawableFruit drawableFruit;
private readonly DrawableBanana drawableBanana;
public TestSceneFruitRandomness()
{
drawableFruit = new DrawableFruit(new Fruit());
drawableBanana = new DrawableBanana(new Banana());
Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
AddSliderStep("start time", 500, 600, 0, x =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
});
}
[Test]
public void TestFruitRandomness()
{
// Use values such that the banana colour changes (2/3 of the integers are okay)
const int initial_start_time = 500;
const int another_start_time = 501;
float fruitRotation = 0;
float bananaRotation = 0;
Vector2 bananaSize = new Vector2();
Color4 bananaColour = new Color4();
AddStep("Initialize start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
fruitRotation = drawableFruit.DisplayRotation;
bananaRotation = drawableBanana.DisplayRotation;
bananaSize = drawableBanana.DisplaySize;
bananaColour = drawableBanana.AccentColour.Value;
});
AddStep("change start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
});
AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
AddAssert("banana rotation is changed", () => drawableBanana.DisplayRotation != bananaRotation);
AddAssert("banana size is changed", () => drawableBanana.DisplaySize != bananaSize);
AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
AddStep("reset start time", () =>
{
drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
});
AddAssert("rotation and size restored", () =>
drawableFruit.DisplayRotation == fruitRotation &&
drawableBanana.DisplayRotation == bananaRotation &&
drawableBanana.DisplaySize == bananaSize &&
drawableBanana.AccentColour.Value == bananaColour);
}
}
}
@@ -14,13 +14,13 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override void LoadComplete()
{
AddStep("fruit changes visual and hyper", () => SetContents(() => SetProperties(new DrawableFruit(new Fruit
AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
{
IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
HyperDashBindable = { BindTarget = hyperDash },
}))));
AddStep("droplet changes hyper", () => SetContents(() => SetProperties(new DrawableDroplet(new Droplet
AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
{
HyperDashBindable = { BindTarget = hyperDash },
}))));
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
@@ -117,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("create hyper-dashing catcher", () =>
{
Child = setupSkinHierarchy(catcherArea = new CatcherArea
Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container<CaughtObject>())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -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" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
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.
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.
lastStartTime = juiceStream.StartTime;
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
catchObject.XOffset = 0;
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)
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)
{
float offsetPosition = hitObject.X;
float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime;
if (lastPosition == null)
@@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (positionDiff == 0)
{
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
hitObject.XOffset = offsetPosition - hitObject.X;
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
return;
}
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (Math.Abs(positionDiff) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff);
hitObject.XOffset = offsetPosition - hitObject.X;
hitObject.XOffset = offsetPosition - hitObject.OriginalX;
lastPosition = offsetPosition;
lastStartTime = startTime;
@@ -230,9 +230,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
currentObject.HyperDashTarget = null;
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 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);
if (distanceToHyper < 0)
+1 -1
View File
@@ -21,7 +21,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
@@ -5,11 +5,8 @@ namespace osu.Game.Rulesets.Catch
{
public enum CatchSkinComponents
{
FruitBananas,
FruitApple,
FruitGrapes,
FruitOrange,
FruitPear,
Fruit,
Banana,
Droplet,
CatcherIdle,
CatcherFail,
@@ -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.
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
NormalizedPosition = BaseObject.X * scalingFactor;
LastNormalizedPosition = LastObject.X * scalingFactor;
NormalizedPosition = BaseObject.EffectiveX * scalingFactor;
LastNormalizedPosition = LastObject.EffectiveX * scalingFactor;
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
StrainTime = Math.Max(40, DeltaTime);
@@ -0,0 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Judgements
{
public class CatchJudgementResult : JudgementResult
{
/// <summary>
/// The catcher animation state prior to this judgement.
/// </summary>
public CatcherAnimationState CatcherAnimationState;
/// <summary>
/// Whether the catcher was hyper dashing prior to this judgement.
/// </summary>
public bool CatcherHyperDash;
public CatchJudgementResult([NotNull] HitObject hitObject, [NotNull] Judgement judgement)
: base(hitObject, judgement)
{
}
}
}
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Catch.Mods
{
base.ApplySettings(difficulty);
difficulty.CircleSize = CircleSize.Value;
difficulty.ApproachRate = ApproachRate.Value;
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
+4 -10
View File
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Judgements;
@@ -15,7 +14,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
public class Banana : Fruit, IHasComboInformation
public class Banana : PalpableCatchHitObject, IHasComboInformation
{
/// <summary>
/// Index of banana in current shower.
@@ -31,17 +30,12 @@ namespace osu.Game.Rulesets.Catch.Objects
Samples = samples;
}
private Color4? colour;
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours)
{
// override any external colour changes with banananana
return colour ??= getBananaColour();
}
// override any external colour changes with banananana
Color4 IHasComboInformation.GetComboColour(IReadOnlyList<Color4> comboColours) => getBananaColour();
private Color4 getBananaColour()
{
switch (RNG.Next(0, 3))
switch (StatelessRNG.NextInt(3, RandomSeed))
{
default:
return new Color4(255, 240, 0, 255);
@@ -4,7 +4,6 @@
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -16,38 +15,46 @@ namespace osu.Game.Rulesets.Catch.Objects
{
public const float OBJECT_RADIUS = 64;
// This value is after XOffset applied.
public readonly Bindable<float> XBindable = new Bindable<float>();
// This value is before XOffset applied.
private float originalX;
public readonly Bindable<float> OriginalXBindable = new Bindable<float>();
/// <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>
public float X
{
// TODO: I don't like this asymmetry.
get => XBindable.Value;
// originalX is set by `XBindable.BindValueChanged`
set => XBindable.Value = value + xOffset;
set => OriginalXBindable.Value = value;
}
private float xOffset;
float IHasXPosition.X => OriginalXBindable.Value;
public readonly Bindable<float> XOffsetBindable = new Bindable<float>();
/// <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>
internal float XOffset
public float XOffset
{
get => xOffset;
set
{
xOffset = value;
XBindable.Value = originalX + xOffset;
}
set => XOffsetBindable.Value = value;
}
/// <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 readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();
@@ -97,6 +104,12 @@ namespace osu.Game.Rulesets.Catch.Objects
set => ScaleBindable.Value = value;
}
/// <summary>
/// The seed value used for visual randomness such as fruit rotation.
/// The value is <see cref="HitObject.StartTime"/> truncated to an integer.
/// </summary>
public int RandomSeed => (int)StartTime;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -107,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
protected CatchHitObject()
{
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
}
}
}
@@ -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())
{
}
}
}
@@ -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())
{
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
}
@@ -2,15 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableBanana : DrawableFruit
public class DrawableBanana : DrawablePalpableCatchHitObject
{
protected override FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => FruitVisualRepresentation.Banana;
public DrawableBanana()
: this(null)
{
@@ -21,6 +21,22 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
}
[BackgroundDependencyLoader]
private void load()
{
ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Banana),
_ => new BananaPiece());
}
protected override void LoadComplete()
{
base.LoadComplete();
// start time affects the random seed which is used to determine the banana colour
StartTimeBindable.BindValueChanged(_ => UpdateComboColour());
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
@@ -28,14 +44,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
const float end_scale = 0.6f;
const float random_scale_range = 1.6f;
ScaleContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RNG.NextSingle()))
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
ScalingContainer.ScaleTo(HitObject.Scale * (end_scale + random_scale_range * RandomSingle(3)))
.Then().ScaleTo(HitObject.Scale * end_scale, HitObject.TimePreempt);
ScaleContainer.RotateTo(getRandomAngle())
.Then()
.RotateTo(getRandomAngle(), HitObject.TimePreempt);
ScalingContainer.RotateTo(getRandomAngle(1))
.Then()
.RotateTo(getRandomAngle(2), HitObject.TimePreempt);
float getRandomAngle() => 180 * (RNG.NextSingle() * 2 - 1);
float getRandomAngle(int series) => 180 * (RandomSingle(series) * 2 - 1);
}
public override void PlaySamples()
@@ -5,20 +5,24 @@ using System;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
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;
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
public int RandomSeed => HitObject?.RandomSeed ?? 0;
protected DrawableCatchHitObject([CanBeNull] CatchHitObject hitObject)
: base(hitObject)
@@ -26,25 +30,30 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Anchor = Anchor.BottomLeft;
}
/// <summary>
/// Get a random number in range [0,1) based on seed <see cref="RandomSeed"/>.
/// </summary>
public float RandomSingle(int series) => StatelessRNG.NextSingle(RandomSeed, series);
protected override void OnApply()
{
base.OnApply();
XBindable.BindTo(HitObject.XBindable);
OriginalXBindable.BindTo(HitObject.OriginalXBindable);
XOffsetBindable.BindTo(HitObject.XOffsetBindable);
}
protected override void OnFree()
{
base.OnFree();
XBindable.UnbindFrom(HitObject.XBindable);
OriginalXBindable.UnbindFrom(HitObject.OriginalXBindable);
XOffsetBindable.UnbindFrom(HitObject.XOffsetBindable);
}
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 void CheckForResult(bool userTriggered, double timeOffset)
{
@@ -4,16 +4,13 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableDroplet : DrawablePalpableCatchHitObject
{
public override bool StaysOnPlate => false;
public DrawableDroplet()
: this(null)
{
@@ -27,17 +24,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
HyperDash.BindValueChanged(_ => updatePiece(), true);
}
private void updatePiece()
{
ScaleContainer.Child = new SkinnableDrawable(
ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Droplet),
_ => new DropletPiece
{
HyperDash = { BindTarget = HyperDash }
});
_ => new DropletPiece());
}
protected override void UpdateInitialTransforms()
@@ -45,10 +34,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
base.UpdateInitialTransforms();
// roughly matches osu-stable
float startRotation = RNG.NextSingle() * 20;
float startRotation = RandomSingle(1) * 20;
double duration = HitObject.TimePreempt + 2000;
ScaleContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
ScalingContainer.RotateTo(startRotation).RotateTo(startRotation + 720, duration);
}
}
}
@@ -1,21 +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 System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
public class DrawableFruit : DrawablePalpableCatchHitObject
public class DrawableFruit : DrawablePalpableCatchHitObject, IHasFruitState
{
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
protected virtual FruitVisualRepresentation GetVisualRepresentation(int indexInBeatmap) => (FruitVisualRepresentation)(indexInBeatmap % 4);
public Bindable<FruitVisualRepresentation> VisualRepresentation { get; } = new Bindable<FruitVisualRepresentation>();
public DrawableFruit()
: this(null)
@@ -30,50 +27,21 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
ScaleContainer.Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
IndexInBeatmap.BindValueChanged(change =>
{
VisualRepresentation.Value = GetVisualRepresentation(change.NewValue);
VisualRepresentation.Value = (FruitVisualRepresentation)(change.NewValue % 4);
}, true);
VisualRepresentation.BindValueChanged(_ => updatePiece());
HyperDash.BindValueChanged(_ => updatePiece(), true);
ScalingContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(CatchSkinComponents.Fruit),
_ => new FruitPiece());
}
private void updatePiece()
protected override void UpdateInitialTransforms()
{
ScaleContainer.Child = new SkinnableDrawable(
new CatchSkinComponent(getComponent(VisualRepresentation.Value)),
_ => new FruitPiece
{
VisualRepresentation = { BindTarget = VisualRepresentation },
HyperDash = { BindTarget = HyperDash },
});
}
base.UpdateInitialTransforms();
private CatchSkinComponents getComponent(FruitVisualRepresentation hitObjectVisualRepresentation)
{
switch (hitObjectVisualRepresentation)
{
case FruitVisualRepresentation.Pear:
return CatchSkinComponents.FruitPear;
case FruitVisualRepresentation.Grape:
return CatchSkinComponents.FruitGrapes;
case FruitVisualRepresentation.Pineapple:
return CatchSkinComponents.FruitApple;
case FruitVisualRepresentation.Raspberry:
return CatchSkinComponents.FruitOrange;
case FruitVisualRepresentation.Banana:
return CatchSkinComponents.FruitBananas;
default:
throw new ArgumentOutOfRangeException(nameof(hitObjectVisualRepresentation), hitObjectVisualRepresentation, null);
}
ScalingContainer.RotateTo((RandomSingle(1) - 0.5f) * 40);
}
}
@@ -83,6 +51,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Grape,
Pineapple,
Raspberry,
Banana // banananananannaanana
}
}
@@ -7,30 +7,36 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osuTK.Graphics;
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 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>
/// 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>
protected virtual float ScaleFactor => 1;
/// <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>
public virtual bool StaysOnPlate => true;
protected readonly Container ScalingContainer;
protected readonly Container ScaleContainer;
public Vector2 DisplaySize => ScalingContainer.Size * ScalingContainer.Scale;
public float DisplayRotation => ScalingContainer.Rotation;
protected DrawablePalpableCatchHitObject([CanBeNull] CatchHitObject h)
: base(h)
@@ -38,30 +44,34 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
Origin = Anchor.Centre;
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2);
AddInternal(ScaleContainer = new Container
AddInternal(ScalingContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(CatchHitObject.OBJECT_RADIUS * 2)
});
}
[BackgroundDependencyLoader]
private void load()
{
XBindable.BindValueChanged(x =>
{
if (!IsOnPlate) X = x.NewValue;
}, true);
OriginalXBindable.BindValueChanged(updateXPosition);
XOffsetBindable.BindValueChanged(updateXPosition, true);
ScaleBindable.BindValueChanged(scale =>
{
ScaleContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
ScalingContainer.Scale = new Vector2(scale.NewValue * ScaleFactor);
Size = DisplaySize;
}, true);
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
}
private void updateXPosition(ValueChangedEvent<float> _)
{
X = OriginalXBindable.Value + XOffsetBindable.Value;
}
protected override void 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; }
}
}
@@ -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; }
}
}
@@ -1,30 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class BananaPiece : PulpFormation
{
public BananaPiece()
{
InternalChildren = new Drawable[]
{
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(SMALL_PULP),
Y = -0.3f
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f),
Y = 0.05f,
},
};
}
}
}
@@ -1,37 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class DropletPiece : CompositeDrawable
{
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
public DropletPiece()
{
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
}
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject)
{
InternalChild = new Pulp
{
RelativeSizeAxes = Axes.Both,
AccentColour = { BindTarget = drawableObject.AccentColour }
};
if (HyperDash.Value)
{
AddInternal(new HyperDropletBorderPiece());
}
}
}
}
@@ -1,79 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
internal class FruitPiece : CompositeDrawable
{
/// <summary>
/// Because we're adding a border around the fruit, we need to scale down some.
/// </summary>
public const float RADIUS_ADJUST = 1.1f;
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
[CanBeNull]
private DrawableCatchHitObject drawableHitObject;
[CanBeNull]
private BorderPiece borderPiece;
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load([CanBeNull] DrawableHitObject drawable)
{
drawableHitObject = (DrawableCatchHitObject)drawable;
AddInternal(getFruitFor(VisualRepresentation.Value));
// if it is not part of a DHO, the border is always invisible.
if (drawableHitObject != null)
AddInternal(borderPiece = new BorderPiece());
if (HyperDash.Value)
AddInternal(new HyperBorderPiece());
}
protected override void Update()
{
if (borderPiece != null && drawableHitObject?.HitObject != null)
borderPiece.Alpha = (float)Math.Clamp((drawableHitObject.HitObject.StartTime - Time.Current) / 500, 0, 1);
}
private Drawable getFruitFor(FruitVisualRepresentation representation)
{
switch (representation)
{
case FruitVisualRepresentation.Pear:
return new PearPiece();
case FruitVisualRepresentation.Grape:
return new GrapePiece();
case FruitVisualRepresentation.Pineapple:
return new PineapplePiece();
case FruitVisualRepresentation.Banana:
return new BananaPiece();
case FruitVisualRepresentation.Raspberry:
return new RaspberryPiece();
}
return Empty();
}
}
}
@@ -1,42 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class GrapePiece : PulpFormation
{
public GrapePiece()
{
InternalChildren = new Drawable[]
{
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(SMALL_PULP),
Y = -0.25f,
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_3),
Position = PositionAt(0, DISTANCE_FROM_CENTRE_3),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_3),
Position = PositionAt(120, DISTANCE_FROM_CENTRE_3),
},
new Pulp
{
Size = new Vector2(LARGE_PULP_3),
AccentColour = { BindTarget = AccentColour },
Position = PositionAt(240, DISTANCE_FROM_CENTRE_3),
},
};
}
}
}
@@ -1,42 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class PearPiece : PulpFormation
{
public PearPiece()
{
InternalChildren = new Drawable[]
{
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(SMALL_PULP),
Y = -0.33f,
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_3),
Position = PositionAt(60, DISTANCE_FROM_CENTRE_3),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_3),
Position = PositionAt(180, DISTANCE_FROM_CENTRE_3),
},
new Pulp
{
Size = new Vector2(LARGE_PULP_3),
AccentColour = { BindTarget = AccentColour },
Position = PositionAt(300, DISTANCE_FROM_CENTRE_3),
},
};
}
}
}
@@ -1,48 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class PineapplePiece : PulpFormation
{
public PineapplePiece()
{
InternalChildren = new Drawable[]
{
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(SMALL_PULP),
Y = -0.3f,
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(45, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(135, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(225, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
Size = new Vector2(LARGE_PULP_4),
AccentColour = { BindTarget = AccentColour },
Position = PositionAt(315, DISTANCE_FROM_CENTRE_4),
},
};
}
}
}
@@ -1,48 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osuTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
{
public class RaspberryPiece : PulpFormation
{
public RaspberryPiece()
{
InternalChildren = new Drawable[]
{
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(SMALL_PULP),
Y = -0.34f,
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(0, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(90, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
AccentColour = { BindTarget = AccentColour },
Size = new Vector2(LARGE_PULP_4),
Position = PositionAt(180, DISTANCE_FROM_CENTRE_4),
},
new Pulp
{
Size = new Vector2(LARGE_PULP_4),
AccentColour = { BindTarget = AccentColour },
Position = PositionAt(270, DISTANCE_FROM_CENTRE_4),
},
};
}
}
}
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new TinyDroplet
{
StartTime = t + lastEvent.Value.Time,
X = X + Path.PositionAt(
X = OriginalX + Path.PositionAt(
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
});
}
@@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Samples = dropletSamples,
StartTime = e.Time,
X = X + Path.PositionAt(e.PathProgress).X,
X = OriginalX + Path.PositionAt(e.PathProgress).X,
});
break;
@@ -104,14 +104,14 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Samples = this.GetNodeSamples(nodeIndex++),
StartTime = e.Time,
X = X + Path.PositionAt(e.PathProgress).X,
X = OriginalX + Path.PositionAt(e.PathProgress).X,
});
break;
}
}
}
public float EndX => X + this.CurvePositionAt(1).X;
public float EndX => OriginalX + this.CurvePositionAt(1).X;
public double Duration
{
@@ -42,9 +42,14 @@ namespace osu.Game.Rulesets.Catch.Replays
void moveToNext(PalpableCatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
float positionChange = Math.Abs(lastPosition - h.EffectiveX);
double timeAvailable = h.StartTime - lastTime;
if (timeAvailable < 0)
{
return;
}
// So we can either make it there without a dash or not.
// If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too)
// The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour.
@@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Catch.Replays
// todo: get correct catcher size, based on difficulty CS.
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.
lastTime = h.StartTime;
@@ -66,12 +71,12 @@ namespace osu.Game.Rulesets.Catch.Replays
if (impossibleJump)
{
addFrame(h.StartTime, h.X);
addFrame(h.StartTime, h.EffectiveX);
}
else if (h.HyperDash)
{
addFrame(h.StartTime - timeAvailable, lastPosition);
addFrame(h.StartTime, h.X);
addFrame(h.StartTime, h.EffectiveX);
}
else if (dashRequired)
{
@@ -80,23 +85,23 @@ namespace osu.Game.Rulesets.Catch.Replays
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
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
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
addFrame(h.StartTime, h.X);
addFrame(h.StartTime, h.EffectiveX);
}
else
{
double timeBefore = positionChange / movement_speed;
addFrame(h.StartTime - timeBefore, lastPosition);
addFrame(h.StartTime, h.X);
addFrame(h.StartTime, h.EffectiveX);
}
lastTime = h.StartTime;
lastPosition = h.X;
lastPosition = h.EffectiveX;
}
foreach (var obj in Beatmap.HitObjects)
@@ -0,0 +1,26 @@
// 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;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BananaPiece : CatchHitObjectPiece
{
protected override BorderPiece BorderPiece { get; }
public BananaPiece()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new BananaPulpFormation
{
AccentColour = { BindTarget = AccentColour },
},
BorderPiece = new BorderPiece(),
};
}
}
}
@@ -0,0 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BananaPulpFormation : PulpFormation
{
public BananaPulpFormation()
{
AddPulp(new Vector2(0, -0.3f), new Vector2(SMALL_PULP));
AddPulp(new Vector2(0, 0.05f), new Vector2(LARGE_PULP_4 * 0.8f, LARGE_PULP_4 * 2.5f));
}
}
}
@@ -3,10 +3,11 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Catch.Objects;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class BorderPiece : Circle
{
@@ -28,4 +29,3 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
}
}
}
@@ -0,0 +1,54 @@
// 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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public abstract class CatchHitObjectPiece : CompositeDrawable
{
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
[Resolved]
protected IHasCatchObjectState ObjectState { get; private set; }
/// <summary>
/// A part of this piece that will be faded out while falling in the playfield.
/// </summary>
[CanBeNull]
protected virtual BorderPiece BorderPiece => null;
/// <summary>
/// A part of this piece that will be only visible when <see cref="HyperDash"/> is true.
/// </summary>
[CanBeNull]
protected virtual HyperBorderPiece HyperBorderPiece => null;
protected override void LoadComplete()
{
base.LoadComplete();
AccentColour.BindTo(ObjectState.AccentColour);
HyperDash.BindTo(ObjectState.HyperDash);
HyperDash.BindValueChanged(hyper =>
{
if (HyperBorderPiece != null)
HyperBorderPiece.Alpha = hyper.NewValue ? 1 : 0;
}, true);
}
protected override void Update()
{
if (BorderPiece != null)
BorderPiece.Alpha = (float)Math.Clamp((ObjectState.HitObject.StartTime - Time.Current) / 500, 0, 1);
}
}
}
@@ -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.Graphics;
using osu.Game.Rulesets.Catch.Objects;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class DropletPiece : CatchHitObjectPiece
{
protected override HyperBorderPiece HyperBorderPiece { get; }
public DropletPiece()
{
Size = new Vector2(CatchHitObject.OBJECT_RADIUS / 2);
InternalChildren = new Drawable[]
{
new Pulp
{
RelativeSizeAxes = Axes.Both,
AccentColour = { BindTarget = AccentColour }
},
HyperBorderPiece = new HyperDropletBorderPiece()
};
}
}
}
@@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
internal class FruitPiece : CatchHitObjectPiece
{
/// <summary>
/// Because we're adding a border around the fruit, we need to scale down some.
/// </summary>
public const float RADIUS_ADJUST = 1.1f;
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
protected override BorderPiece BorderPiece { get; }
protected override HyperBorderPiece HyperBorderPiece { get; }
public FruitPiece()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new FruitPulpFormation
{
AccentColour = { BindTarget = AccentColour },
VisualRepresentation = { BindTarget = VisualRepresentation }
},
BorderPiece = new BorderPiece(),
HyperBorderPiece = new HyperBorderPiece()
};
}
protected override void LoadComplete()
{
base.LoadComplete();
var fruitState = (IHasFruitState)ObjectState;
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
}
}
}
@@ -0,0 +1,59 @@
// 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.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class FruitPulpFormation : PulpFormation
{
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
protected override void LoadComplete()
{
base.LoadComplete();
VisualRepresentation.BindValueChanged(setFormation, true);
}
private void setFormation(ValueChangedEvent<FruitVisualRepresentation> visualRepresentation)
{
Clear();
switch (visualRepresentation.NewValue)
{
case FruitVisualRepresentation.Pear:
AddPulp(new Vector2(0, -0.33f), new Vector2(SMALL_PULP));
AddPulp(PositionAt(60, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
AddPulp(PositionAt(180, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
AddPulp(PositionAt(300, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
break;
case FruitVisualRepresentation.Grape:
AddPulp(new Vector2(0, -0.25f), new Vector2(SMALL_PULP));
AddPulp(PositionAt(0, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
AddPulp(PositionAt(120, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
AddPulp(PositionAt(240, DISTANCE_FROM_CENTRE_3), new Vector2(LARGE_PULP_3));
break;
case FruitVisualRepresentation.Pineapple:
AddPulp(new Vector2(0, -0.3f), new Vector2(SMALL_PULP));
AddPulp(PositionAt(45, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(135, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(225, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(315, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
break;
case FruitVisualRepresentation.Raspberry:
AddPulp(new Vector2(0, -0.34f), new Vector2(SMALL_PULP));
AddPulp(PositionAt(0, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(90, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(180, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
AddPulp(PositionAt(270, DISTANCE_FROM_CENTRE_4), new Vector2(LARGE_PULP_4));
break;
}
}
}
}
@@ -4,7 +4,7 @@
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class HyperBorderPiece : BorderPiece
{
@@ -19,4 +19,3 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
}
}
}
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class HyperDropletBorderPiece : HyperBorderPiece
{
@@ -8,10 +8,12 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public class Pulp : Circle
{
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
public Pulp()
{
RelativePositionAxes = Axes.Both;
@@ -22,8 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.9f);
}
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
protected override void LoadComplete()
{
base.LoadComplete();
@@ -2,19 +2,17 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
namespace osu.Game.Rulesets.Catch.Skinning.Default
{
public abstract class PulpFormation : CompositeDrawable
{
protected readonly IBindable<Color4> AccentColour = new Bindable<Color4>();
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
protected const float LARGE_PULP_3 = 16f * FruitPiece.RADIUS_ADJUST;
protected const float DISTANCE_FROM_CENTRE_3 = 0.15f;
@@ -24,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
protected const float SMALL_PULP = LARGE_PULP_3 / 2;
private int pulpsInUse;
protected PulpFormation()
{
RelativeSizeAxes = Axes.Both;
@@ -33,11 +33,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables.Pieces
distance * MathF.Sin(angle * MathF.PI / 180),
distance * MathF.Cos(angle * MathF.PI / 180));
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject)
protected void Clear()
{
DrawableCatchHitObject drawableCatchObject = (DrawableCatchHitObject)drawableObject;
AccentColour.BindTo(drawableCatchObject.AccentColour);
for (int i = 0; i < pulpsInUse; i++)
InternalChildren[i].Alpha = 0;
pulpsInUse = 0;
}
protected void AddPulp(Vector2 position, Vector2 size)
{
if (pulpsInUse == InternalChildren.Count)
AddInternal(new Pulp { AccentColour = { BindTarget = AccentColour } });
var pulp = InternalChildren[pulpsInUse];
pulp.Position = position;
pulp.Size = size;
pulp.Alpha = 1;
pulpsInUse++;
}
}
}
@@ -1,15 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
using static osu.Game.Skinning.LegacySkinConfiguration;
namespace osu.Game.Rulesets.Catch.Skinning
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
public class CatchLegacySkinTransformer : LegacySkinTransformer
{
@@ -40,20 +38,21 @@ namespace osu.Game.Rulesets.Catch.Skinning
switch (catchSkinComponent.Component)
{
case CatchSkinComponents.FruitApple:
case CatchSkinComponents.FruitBananas:
case CatchSkinComponents.FruitOrange:
case CatchSkinComponents.FruitGrapes:
case CatchSkinComponents.FruitPear:
var lookupName = catchSkinComponent.Component.ToString().Kebaberize();
if (GetTexture(lookupName) != null)
return new LegacyFruitPiece(lookupName);
case CatchSkinComponents.Fruit:
if (GetTexture("fruit-pear") != null)
return new LegacyFruitPiece();
break;
case CatchSkinComponents.Banana:
if (GetTexture("fruit-bananas") != null)
return new LegacyBananaPiece();
break;
case CatchSkinComponents.Droplet:
if (GetTexture("fruit-drop") != null)
return new LegacyFruitPiece("fruit-drop") { Scale = new Vector2(0.8f) };
return new LegacyDropletPiece();
break;
@@ -9,7 +9,7 @@ using osuTK;
using osuTK.Graphics;
using static osu.Game.Skinning.LegacySkinConfiguration;
namespace osu.Game.Rulesets.Catch.Skinning
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
/// <summary>
/// A combo counter implementation that visually behaves almost similar to stable's osu!catch combo counter.
@@ -0,0 +1,45 @@
// 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.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
{
internal class LegacyFruitPiece : LegacyCatchHitObjectPiece
{
public readonly Bindable<FruitVisualRepresentation> VisualRepresentation = new Bindable<FruitVisualRepresentation>();
protected override void LoadComplete()
{
base.LoadComplete();
var fruitState = (IHasFruitState)ObjectState;
VisualRepresentation.BindTo(fruitState.VisualRepresentation);
VisualRepresentation.BindValueChanged(visual => setTexture(visual.NewValue), true);
}
private void setTexture(FruitVisualRepresentation visualRepresentation)
{
switch (visualRepresentation)
{
case FruitVisualRepresentation.Pear:
SetTexture(Skin.GetTexture("fruit-pear"), Skin.GetTexture("fruit-pear-overlay"));
break;
case FruitVisualRepresentation.Grape:
SetTexture(Skin.GetTexture("fruit-grapes"), Skin.GetTexture("fruit-grapes-overlay"));
break;
case FruitVisualRepresentation.Pineapple:
SetTexture(Skin.GetTexture("fruit-apple"), Skin.GetTexture("fruit-apple-overlay"));
break;
case FruitVisualRepresentation.Raspberry:
SetTexture(Skin.GetTexture("fruit-orange"), Skin.GetTexture("fruit-orange-overlay"));
break;
}
}
}
}
@@ -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.Framework.Graphics.Textures;
namespace osu.Game.Rulesets.Catch.Skinning
{
public class LegacyBananaPiece : LegacyCatchHitObjectPiece
{
protected override void LoadComplete()
{
base.LoadComplete();
Texture texture = Skin.GetTexture("fruit-bananas");
Texture overlayTexture = Skin.GetTexture("fruit-bananas-overlay");
SetTexture(texture, overlayTexture);
}
}
}
@@ -0,0 +1,90 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning
{
public abstract class LegacyCatchHitObjectPiece : PoolableDrawable
{
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
public readonly Bindable<bool> HyperDash = new Bindable<bool>();
private readonly Sprite colouredSprite;
private readonly Sprite overlaySprite;
private readonly Sprite hyperSprite;
[Resolved]
protected ISkinSource Skin { get; private set; }
[Resolved]
protected IHasCatchObjectState ObjectState { get; private set; }
protected LegacyCatchHitObjectPiece()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
colouredSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
overlaySprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
hyperSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Depth = 1,
Alpha = 0,
Scale = new Vector2(1.2f),
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
AccentColour.BindTo(ObjectState.AccentColour);
HyperDash.BindTo(ObjectState.HyperDash);
hyperSprite.Colour = Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
Skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
Catcher.DEFAULT_HYPER_DASH_COLOUR;
AccentColour.BindValueChanged(colour =>
{
colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue);
}, true);
HyperDash.BindValueChanged(hyper =>
{
hyperSprite.Alpha = hyper.NewValue ? 0.7f : 0;
}, true);
}
protected void SetTexture(Texture texture, Texture overlayTexture)
{
colouredSprite.Texture = texture;
overlaySprite.Texture = overlayTexture;
hyperSprite.Texture = texture;
}
}
}
@@ -0,0 +1,26 @@
// 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.Textures;
using osuTK;
namespace osu.Game.Rulesets.Catch.Skinning
{
public class LegacyDropletPiece : LegacyCatchHitObjectPiece
{
public LegacyDropletPiece()
{
Scale = new Vector2(0.8f);
}
protected override void LoadComplete()
{
base.LoadComplete();
Texture texture = Skin.GetTexture("fruit-drop");
Texture overlayTexture = Skin.GetTexture("fruit-drop-overlay");
SetTexture(texture, overlayTexture);
}
}
}
@@ -1,83 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning
{
internal class LegacyFruitPiece : CompositeDrawable
{
private readonly string lookupName;
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
private readonly Bindable<bool> hyperDash = new Bindable<bool>();
private Sprite colouredSprite;
public LegacyFruitPiece(string lookupName)
{
this.lookupName = lookupName;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableObject, ISkinSource skin)
{
var drawableCatchObject = (DrawablePalpableCatchHitObject)drawableObject;
accentColour.BindTo(drawableCatchObject.AccentColour);
hyperDash.BindTo(drawableCatchObject.HyperDash);
InternalChildren = new Drawable[]
{
colouredSprite = new Sprite
{
Texture = skin.GetTexture(lookupName),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
Texture = skin.GetTexture($"{lookupName}-overlay"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
if (hyperDash.Value)
{
var hyperDashOverlay = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Depth = 1,
Alpha = 0.7f,
Scale = new Vector2(1.2f),
Texture = skin.GetTexture(lookupName),
Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
Catcher.DEFAULT_HYPER_DASH_COLOUR,
};
AddInternal(hyperDashOverlay);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
}
}
}
+4 -5
View File
@@ -36,21 +36,20 @@ namespace osu.Game.Rulesets.Catch.UI
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
{
var explodingFruitContainer = new Container
var droppedObjectContainer = new Container<CaughtObject>
{
RelativeSizeAxes = Axes.Both,
};
CatcherArea = new CatcherArea(difficulty)
CatcherArea = new CatcherArea(droppedObjectContainer, difficulty)
{
ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
};
InternalChildren = new[]
{
explodingFruitContainer,
droppedObjectContainer,
CatcherArea.MovableCatcher.CreateProxiedContent(),
HitObjectContainer,
CatcherArea,
@@ -82,7 +81,7 @@ namespace osu.Game.Rulesets.Catch.UI
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
}
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.MovableCatcher.CanCatch(obj);
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
@@ -2,10 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
private readonly CatchPlayfield playfield;
public CatchReplayRecorder(Replay target, CatchPlayfield playfield)
public CatchReplayRecorder(Score target, CatchPlayfield playfield)
: base(target)
{
this.playfield = playfield;
+230 -151
View File
@@ -14,10 +14,11 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -47,19 +48,21 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public const double BASE_SPEED = 1.0;
public Container ExplodingFruitTarget;
private Container<DrawableHitObject> caughtFruitContainer { get; } = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
};
[NotNull]
private readonly Container trailsTarget;
private CatcherTrailDisplay trails;
/// <summary>
/// Contains caught objects on the plate.
/// </summary>
private readonly Container<CaughtObject> caughtObjectContainer;
/// <summary>
/// Contains objects dropped from the plate.
/// </summary>
private readonly Container<CaughtObject> droppedObjectTarget;
public CatcherAnimationState CurrentState { get; private set; }
/// <summary>
@@ -92,9 +95,9 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
private readonly float catchWidth;
private CatcherSprite catcherIdle;
private CatcherSprite catcherKiai;
private CatcherSprite catcherFail;
private readonly CatcherSprite catcherIdle;
private readonly CatcherSprite catcherKiai;
private readonly CatcherSprite catcherFail;
private CatcherSprite currentCatcher;
@@ -108,12 +111,17 @@ namespace osu.Game.Rulesets.Catch.UI
private float hyperDashTargetPosition;
private Bindable<bool> hitLighting;
private DrawablePool<HitExplosion> hitExplosionPool;
private Container<HitExplosion> hitExplosionContainer;
private readonly DrawablePool<HitExplosion> hitExplosionPool;
private readonly Container<HitExplosion> hitExplosionContainer;
public Catcher([NotNull] Container trailsTarget, 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.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre;
@@ -122,17 +130,19 @@ namespace osu.Game.Rulesets.Catch.UI
Scale = calculateScale(difficulty);
catchWidth = CalculateCatchWidth(Scale);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
InternalChildren = new Drawable[]
{
hitExplosionPool = new DrawablePool<HitExplosion>(10),
caughtFruitContainer,
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,
Origin = Anchor.BottomCentre,
},
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
{
Anchor = Anchor.TopCentre,
@@ -154,7 +164,12 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.BottomCentre,
},
};
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
trails = new CatcherTrailDisplay(this);
updateCatcher();
@@ -171,68 +186,29 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary>
/// Creates proxied content to be displayed beneath hitobjects.
/// </summary>
public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
public Drawable CreateProxiedContent() => caughtObjectContainer.CreateProxy();
/// <summary>
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
/// </summary>
private static Vector2 calculateScale(BeatmapDifficulty difficulty)
=> new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
/// <summary>
/// Calculates the width of the area used for attempting catches in gameplay.
/// </summary>
/// <param name="scale">The scale of the catcher.</param>
internal static float CalculateCatchWidth(Vector2 scale)
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
internal static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
/// <summary>
/// Calculates the width of the area used for attempting catches in gameplay.
/// </summary>
/// <param name="difficulty">The beatmap difficulty.</param>
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
=> CalculateCatchWidth(calculateScale(difficulty));
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
/// <summary>
/// Add a caught fruit to the catcher's stack.
/// Determine if this catcher can catch a <see cref="CatchHitObject"/> in the current position.
/// </summary>
/// <param name="fruit">The fruit that was caught.</param>
public void PlaceOnPlate(DrawableCatchHitObject fruit)
{
var ourRadius = fruit.DisplayRadius;
float theirRadius = 0;
const float allowance = 10;
while (caughtFruitContainer.Any(f =>
f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{
var diff = (ourRadius + theirRadius) / allowance;
fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
fruit.Y -= RNG.NextSingle() * diff;
}
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
caughtFruitContainer.Add(fruit);
if (hitLighting.Value)
{
HitExplosion hitExplosion = hitExplosionPool.Get();
hitExplosion.X = fruit.X;
hitExplosion.Scale = new Vector2(fruit.HitObject.Scale);
hitExplosion.ObjectColour = fruit.AccentColour.Value;
hitExplosionContainer.Add(hitExplosion);
}
}
/// <summary>
/// Let the catcher attempt to catch a fruit.
/// </summary>
/// <param name="hitObject">The fruit to catch.</param>
/// <returns>Whether the catch is possible.</returns>
public bool AttemptCatch(CatchHitObject hitObject)
public bool CanCatch(CatchHitObject hitObject)
{
if (!(hitObject is PalpableCatchHitObject fruit))
return false;
@@ -240,34 +216,72 @@ namespace osu.Game.Rulesets.Catch.UI
var halfCatchWidth = catchWidth * 0.5f;
// 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 validCatch =
catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatchWidth;
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
catchObjectPosition <= catcherPosition + halfCatchWidth;
}
// only update hyperdash state if we are not catching a tiny droplet.
if (fruit is TinyDroplet) return validCatch;
public void OnNewResult(DrawableCatchHitObject drawableObject, JudgementResult result)
{
var catchResult = (CatchJudgementResult)result;
catchResult.CatcherAnimationState = CurrentState;
catchResult.CatcherHyperDash = HyperDashing;
if (validCatch && fruit.HyperDash)
if (!(drawableObject is DrawablePalpableCatchHitObject palpableObject)) return;
var hitObject = palpableObject.HitObject;
if (result.IsHit)
{
var target = fruit.HyperDashTarget;
var timeDifference = target.StartTime - fruit.StartTime;
double positionDifference = target.X - catcherPosition;
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
if (hitObject is TinyDroplet) return;
if (result.IsHit && hitObject.HyperDash)
{
var target = hitObject.HyperDashTarget;
var timeDifference = target.StartTime - hitObject.StartTime;
double positionDifference = target.EffectiveX - X;
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
SetHyperDashState();
if (validCatch)
updateState(fruit.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
else if (!(fruit is Banana))
if (result.IsHit)
updateState(hitObject.Kiai ? CatcherAnimationState.Kiai : CatcherAnimationState.Idle);
else if (!(hitObject is Banana))
updateState(CatcherAnimationState.Fail);
}
return validCatch;
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
{
var catchResult = (CatchJudgementResult)result;
if (CurrentState != catchResult.CatcherAnimationState)
updateState(catchResult.CatcherAnimationState);
if (HyperDashing != catchResult.CatcherHyperDash)
{
if (catchResult.CatcherHyperDash)
SetHyperDashState(2);
else
SetHyperDashState();
}
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject);
hitExplosionContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject);
}
/// <summary>
@@ -301,24 +315,17 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
private void runHyperDashStateTransition(bool hyperDashing)
public void UpdatePosition(float position)
{
updateTrailVisibility();
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
if (hyperDashing)
{
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
else
{
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
if (position == X)
return;
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
X = position;
}
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
public bool OnPressed(CatchAction action)
{
switch (action)
@@ -357,56 +364,34 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
public void UpdatePosition(float position)
{
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
if (position == X)
return;
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
X = position;
}
/// <summary>
/// Drop any fruit off the plate.
/// </summary>
public void Drop()
{
foreach (var f in caughtFruitContainer.ToArray())
Drop(f);
}
public void Drop() => clearPlate(DroppedObjectAnimation.Drop);
/// <summary>
/// Explode any fruit off the plate.
/// Explode all fruit off the plate.
/// </summary>
public void Explode()
{
foreach (var f in caughtFruitContainer.ToArray())
Explode(f);
}
public void Explode() => clearPlate(DroppedObjectAnimation.Explode);
public void Drop(DrawableHitObject fruit)
private void runHyperDashStateTransition(bool hyperDashing)
{
removeFromPlateWithTransform(fruit, f =>
updateTrailVisibility();
if (hyperDashing)
{
f.MoveToY(f.Y + 75, 750, Easing.InSine);
f.FadeOut(750);
});
}
public void Explode(DrawableHitObject fruit)
{
var originalX = fruit.X * Scale.X;
removeFromPlateWithTransform(fruit, f =>
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
else
{
f.MoveToY(f.Y - 50, 250, Easing.OutSine).Then().MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
});
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
}
}
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
@@ -479,33 +464,127 @@ namespace osu.Game.Rulesets.Catch.UI
updateCatcher();
}
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
private void placeCaughtObject(DrawablePalpableCatchHitObject drawableObject, Vector2 position)
{
if (ExplodingFruitTarget != null)
var caughtObject = getCaughtObject(drawableObject.HitObject);
if (caughtObject == null) return;
caughtObject.CopyStateFrom(drawableObject);
caughtObject.Anchor = Anchor.TopCentre;
caughtObject.Position = position;
caughtObject.Scale /= 2;
caughtObjectContainer.Add(caughtObject);
if (!caughtObject.StaysOnPlate)
removeFromPlate(caughtObject, DroppedObjectAnimation.Explode);
}
private Vector2 computePositionInStack(Vector2 position, float displayRadius)
{
const float radius_div_2 = CatchHitObject.OBJECT_RADIUS / 2;
const float allowance = 10;
while (caughtObjectContainer.Any(f => Vector2Extensions.Distance(f.Position, position) < (displayRadius + radius_div_2) / (allowance / 2)))
{
fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
float diff = (displayRadius + radius_div_2) / allowance;
if (!caughtFruitContainer.Remove(fruit))
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
return;
ExplodingFruitTarget.Add(fruit);
position.X += (RNG.NextSingle() - 0.5f) * diff * 2;
position.Y -= RNG.NextSingle() * diff;
}
var actionTime = Clock.CurrentTime;
position.X = Math.Clamp(position.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
fruit.ApplyCustomUpdateState += onFruitOnApplyCustomUpdateState;
onFruitOnApplyCustomUpdateState(fruit, fruit.State.Value);
return position;
}
void onFruitOnApplyCustomUpdateState(DrawableHitObject o, ArmedState state)
private void addLighting(CatchHitObject hitObject, float x, Color4 colour)
{
HitExplosion hitExplosion = hitExplosionPool.Get();
hitExplosion.HitObject = hitObject;
hitExplosion.X = x;
hitExplosion.Scale = new Vector2(hitObject.Scale);
hitExplosion.ObjectColour = colour;
hitExplosionContainer.Add(hitExplosion);
}
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
{
switch (source)
{
using (fruit.BeginAbsoluteSequence(actionTime))
action(fruit);
case Fruit _:
return caughtFruitPool.Get();
fruit.Expire();
case Banana _:
return caughtBananaPool.Get();
case Droplet _:
return caughtDropletPool.Get();
default:
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)
{
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
caughtObjectContainer.Clear(false);
droppedObjectTarget.AddRange(droppedObjects);
foreach (var droppedObject in droppedObjects)
applyDropAnimation(droppedObject, animation);
}
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
{
var droppedObject = getDroppedObject(caughtObject);
caughtObjectContainer.Remove(caughtObject);
droppedObjectTarget.Add(droppedObject);
applyDropAnimation(droppedObject, animation);
}
private void applyDropAnimation(Drawable d, DroppedObjectAnimation animation)
{
switch (animation)
{
case DroppedObjectAnimation.Drop:
d.MoveToY(d.Y + 75, 750, Easing.InSine);
d.FadeOut(750);
break;
case DroppedObjectAnimation.Explode:
var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X;
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
d.MoveToX(d.X + originalX * 6, 1000);
d.FadeOut(750);
break;
}
d.Expire();
}
private enum DroppedObjectAnimation
{
Drop,
Explode
}
}
}
+8 -79
View File
@@ -1,12 +1,10 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
@@ -23,14 +21,7 @@ namespace osu.Game.Rulesets.Catch.UI
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
public Container ExplodingFruitTarget
{
set => MovableCatcher.ExplodingFruitTarget = value;
}
private DrawableCatchHitObject lastPlateableFruit;
public CatcherArea(BeatmapDifficulty difficulty = null)
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
{
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
Children = new Drawable[]
@@ -44,56 +35,21 @@ namespace osu.Game.Rulesets.Catch.UI
Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X
},
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
MovableCatcher = new Catcher(this, droppedObjectContainer, difficulty) { X = CatchPlayfield.CENTER_X },
};
}
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
MovableCatcher.OnNewResult(hitObject, result);
if (!result.Type.IsScorable())
return;
void runAfterLoaded(Action action)
{
if (lastPlateableFruit == null)
return;
// this is required to make this run after the last caught fruit runs updateState() at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
else
lastPlateableFruit.OnLoadComplete += _ => action();
}
if (result.IsHit && hitObject is DrawablePalpableCatchHitObject fruit)
{
// create a new (cloned) fruit to stay on the plate. the original is faded out immediately.
var caughtFruit = createCaughtFruit(fruit);
if (caughtFruit == null) return;
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(hitObject.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
caughtFruit.IsOnPlate = true;
caughtFruit.Anchor = Anchor.TopCentre;
caughtFruit.Origin = Anchor.Centre;
caughtFruit.Scale *= 0.5f;
caughtFruit.LifetimeStart = caughtFruit.HitObject.StartTime;
caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.PlaceOnPlate(caughtFruit);
lastPlateableFruit = caughtFruit;
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
}
if (hitObject.HitObject.LastInCombo)
{
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
runAfterLoaded(() => MovableCatcher.Explode());
MovableCatcher.Explode();
else
MovableCatcher.Drop();
}
@@ -101,16 +57,10 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.OnNewResult(hitObject, result);
}
public void OnRevertResult(DrawableCatchHitObject fruit, JudgementResult result)
=> comboDisplay.OnRevertResult(fruit, result);
public void OnReleased(CatchAction action)
public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
}
public bool AttemptCatch(CatchHitObject obj)
{
return MovableCatcher.AttemptCatch(obj);
comboDisplay.OnRevertResult(hitObject, result);
MovableCatcher.OnRevertResult(hitObject, result);
}
protected override void UpdateAfterChildren()
@@ -124,26 +74,5 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.X = MovableCatcher.X;
}
private DrawableCatchHitObject createCaughtFruit(DrawablePalpableCatchHitObject hitObject)
{
switch (hitObject.HitObject)
{
case Banana banana:
return new DrawableBanana(banana);
case Fruit fruit:
return new DrawableFruit(fruit);
case TinyDroplet tiny:
return new DrawableTinyDroplet(tiny);
case Droplet droplet:
return new DrawableDroplet(droplet);
default:
return null;
}
}
}
}
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
@@ -20,6 +21,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
private readonly Catcher catcher;
private readonly DrawablePool<CatcherTrailSprite> trailPool;
private readonly Container<CatcherTrailSprite> dashTrails;
private readonly Container<CatcherTrailSprite> hyperDashTrails;
private readonly Container<CatcherTrailSprite> endGlowSprites;
@@ -80,8 +83,9 @@ namespace osu.Game.Rulesets.Catch.UI
RelativeSizeAxes = Axes.Both;
InternalChildren = new[]
InternalChildren = new Drawable[]
{
trailPool = new DrawablePool<CatcherTrailSprite>(30),
dashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both },
hyperDashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
endGlowSprites = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
@@ -118,14 +122,14 @@ namespace osu.Game.Rulesets.Catch.UI
{
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
var sprite = new CatcherTrailSprite(texture)
{
Anchor = catcher.Anchor,
Scale = catcher.Scale,
Blending = BlendingParameters.Additive,
RelativePositionAxes = catcher.RelativePositionAxes,
Position = catcher.Position
};
CatcherTrailSprite sprite = trailPool.Get();
sprite.Texture = texture;
sprite.Anchor = catcher.Anchor;
sprite.Scale = catcher.Scale;
sprite.Blending = BlendingParameters.Additive;
sprite.RelativePositionAxes = catcher.RelativePositionAxes;
sprite.Position = catcher.Position;
target.Add(sprite);
@@ -1,22 +1,40 @@
// 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;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherTrailSprite : Sprite
public class CatcherTrailSprite : PoolableDrawable
{
public CatcherTrailSprite(Texture texture)
public Texture Texture
{
Texture = texture;
set => sprite.Texture = value;
}
private readonly Sprite sprite;
public CatcherTrailSprite()
{
InternalChild = sprite = new Sprite
{
RelativeSizeAxes = Axes.Both
};
Size = new Vector2(CatcherArea.CATCHER_SIZE);
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
}
protected override void FreeAfterUse()
{
ClearTransforms();
base.FreeAfterUse();
}
}
}
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Scoring;
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 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);
@@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Objects;
using osuTK;
using osuTK.Graphics;
@@ -15,6 +16,7 @@ namespace osu.Game.Rulesets.Catch.UI
public class HitExplosion : PoolableDrawable
{
private Color4 objectColour;
public CatchHitObject HitObject;
public Color4 ObjectColour
{
@@ -5,6 +5,13 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>catch the fruit. to the beat.</Description>
</PropertyGroup>
<PropertyGroup Label="Nuget">
<Title>osu!catch (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Catch</PackageId>
<IsPackable>true</IsPackable>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
</ItemGroup>
+2 -2
View File
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"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}",
"preLaunchTask": "Build (Release)",
@@ -15,7 +15,7 @@ using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
@@ -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;
}
});
}
}
@@ -10,7 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.Skinning.Default;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -355,7 +355,11 @@ namespace osu.Game.Rulesets.Mania.Tests
protected override bool PauseOnFocusLost => false;
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;
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" />
<ItemGroup Label="Package References">
<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="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />

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