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

Compare commits

..

1361 Commits

586 changed files with 14101 additions and 4563 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
+2 -3
View File
@@ -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.1212.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.127.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()
+2 -2
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>
@@ -25,7 +25,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<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" />
+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;
}
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void attemptCatch(Fruit fruit)
{
fruit.X += catcher.X;
fruit.X = fruit.OriginalX + catcher.X;
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
{
CircleSize = circleSize
@@ -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)
@@ -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);
@@ -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,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>();
@@ -113,10 +120,5 @@ namespace osu.Game.Rulesets.Catch.Objects
}
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
protected CatchHitObject()
{
XBindable.BindValueChanged(x => originalX = x.NewValue - xOffset);
}
}
}
@@ -15,11 +15,12 @@ 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;
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
protected override float SamplePlaybackPosition => HitObject.EffectiveX / CatchPlayfield.WIDTH;
public int RandomSeed => HitObject?.RandomSeed ?? 0;
@@ -38,14 +39,16 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
{
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;
@@ -55,10 +55,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
[BackgroundDependencyLoader]
private void load()
{
XBindable.BindValueChanged(x =>
{
X = x.NewValue;
}, true);
OriginalXBindable.BindValueChanged(updateXPosition);
XOffsetBindable.BindValueChanged(updateXPosition, true);
ScaleBindable.BindValueChanged(scale =>
{
@@ -69,6 +67,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
IndexInBeatmap.BindValueChanged(_ => UpdateComboColour());
}
private void updateXPosition(ValueChangedEvent<float> _)
{
X = OriginalXBindable.Value + XOffsetBindable.Value;
}
protected override void OnApply()
{
base.OnApply();
@@ -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,7 +42,7 @@ 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;
// So we can either make it there without a dash or not.
@@ -56,7 +56,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 +66,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 +80,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)
@@ -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;
+3 -3
View File
@@ -216,7 +216,7 @@ 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;
return catchObjectPosition >= catcherPosition - halfCatchWidth &&
@@ -250,10 +250,10 @@ namespace osu.Game.Rulesets.Catch.UI
{
var target = hitObject.HyperDashTarget;
var timeDifference = target.StartTime - hitObject.StartTime;
double positionDifference = target.X - X;
double positionDifference = target.EffectiveX - X;
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
SetHyperDashState(Math.Abs(velocity), target.X);
SetHyperDashState(Math.Abs(velocity), target.EffectiveX);
}
else
SetHyperDashState();
@@ -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);
@@ -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)",
@@ -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" />
@@ -0,0 +1,89 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public class LegacyManiaJudgementPiece : CompositeDrawable, IAnimatableJudgement
{
private readonly HitResult result;
private readonly Drawable animation;
public LegacyManiaJudgementPiece(HitResult result, Drawable animation)
{
this.result = result;
this.animation = animation;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
float? scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value;
if (scorePosition != null)
scorePosition -= Stage.HIT_TARGET_POSITION + 150;
Y = scorePosition ?? 0;
if (animation != null)
{
InternalChild = animation.With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
});
}
}
public void PlayAnimation()
{
if (animation == null)
return;
(animation as IFramedAnimation)?.GotoFrame(0);
this.FadeInFromZero(20, Easing.Out)
.Then().Delay(160)
.FadeOutFromOne(40, Easing.In);
switch (result)
{
case HitResult.None:
break;
case HitResult.Miss:
animation.ScaleTo(1.2f).Then().ScaleTo(1, 100, Easing.Out);
animation.RotateTo(0);
animation.RotateTo(RNG.NextSingle(-5.73f, 5.73f), 100, Easing.Out);
break;
default:
animation.ScaleTo(0.8f)
.Then().ScaleTo(1, 40)
// this is actually correct to match stable; there were overlapping transforms.
.Then().ScaleTo(0.85f)
.Then().ScaleTo(0.7f, 40)
.Then().Delay(100)
.Then().ScaleTo(0.4f, 40, Easing.In);
break;
}
}
public Drawable GetAboveHitObjectsProxiedContent() => null;
}
}
@@ -136,7 +136,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
string filename = this.GetManiaSkinConfig<string>(hitresult_mapping[result])?.Value
?? default_hitresult_skin_filenames[result];
return this.GetAnimation(filename, true, true);
var animation = this.GetAnimation(filename, true, true);
return animation == null ? null : new LegacyManiaJudgementPiece(result, animation);
}
public override SampleChannel GetSample(ISampleInfo sampleInfo)
@@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -20,37 +19,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
}
protected override void ApplyMissAnimations()
{
if (!(JudgementBody.Drawable is DefaultManiaJudgementPiece))
{
// this is temporary logic until mania's skin transformer returns IAnimatableJudgements
JudgementBody.ScaleTo(1.6f);
JudgementBody.ScaleTo(1, 100, Easing.In);
JudgementBody.MoveTo(Vector2.Zero);
JudgementBody.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint);
JudgementBody.RotateTo(0);
JudgementBody.RotateTo(40, 800, Easing.InQuint);
JudgementBody.FadeOutFromOne(800);
LifetimeEnd = JudgementBody.LatestTransformEndTime;
}
base.ApplyMissAnimations();
}
protected override void ApplyHitAnimations()
{
JudgementBody.ScaleTo(0.8f);
JudgementBody.ScaleTo(1, 250, Easing.OutElastic);
JudgementBody.Delay(50)
.ScaleTo(0.75f, 250)
.FadeOut(200);
}
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
private class DefaultManiaJudgementPiece : DefaultJudgementPiece
@@ -66,6 +34,27 @@ namespace osu.Game.Rulesets.Mania.UI
JudgementText.Font = JudgementText.Font.With(size: 25);
}
public override void PlayAnimation()
{
base.PlayAnimation();
switch (Result)
{
case HitResult.None:
case HitResult.Miss:
break;
default:
this.ScaleTo(0.8f);
this.ScaleTo(1, 250, Easing.OutElastic);
this.Delay(50)
.ScaleTo(0.75f, 250)
.FadeOut(200);
break;
}
}
}
}
}
@@ -23,6 +23,7 @@ using osu.Game.Rulesets.Objects;
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.Mania.UI
{
@@ -132,6 +133,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new ManiaReplayRecorder(score);
}
}
@@ -2,18 +2,18 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaReplayRecorder : ReplayRecorder<ManiaAction>
{
public ManiaReplayRecorder(Replay replay)
: base(replay)
public ManiaReplayRecorder(Score score)
: base(score)
{
}
+1 -1
View File
@@ -106,7 +106,7 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Y = HIT_TARGET_POSITION + 150,
Y = HIT_TARGET_POSITION + 150
},
topLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
}
@@ -5,6 +5,13 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>smash the keys. to the beat.</Description>
</PropertyGroup>
<PropertyGroup Label="Nuget">
<Title>osu!mania (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Mania</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.Osu.Tests.dll"
"${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
@@ -1,13 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -18,8 +22,23 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
public void TestNoAdjustment() => CreateModTest(new ModTestData
{
Mod = new OsuModDifficultyAdjust(),
Beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 8
}
},
HitObjects = new List<HitObject>
{
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 }
}
},
Autoplay = true,
PassCondition = checkSomeHit
PassCondition = () => checkSomeHit() && checkObjectsScale(0.29f)
});
[Test]
@@ -1,103 +1,91 @@
// 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.Framework.Audio;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneLegacyBeatmapSkin : ScreenTestScene
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 OsuCustomSkinWorkingBeatmap(audio, true);
base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
}
[TestCase(true)]
[TestCase(false)]
public void TestBeatmapComboColours(bool customSkinColoursPresent)
public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
{
ExposedPlayer player = null;
AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
base.TestBeatmapComboColoursOverride(useBeatmapSkin);
AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
}
[Test]
public void TestBeatmapNoComboColours()
[TestCase(true)]
[TestCase(false)]
public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
{
ExposedPlayer player = null;
AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, true);
base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
}
[Test]
public void TestBeatmapNoComboColoursSkinOverride()
[TestCase(true, true)]
[TestCase(false, true)]
[TestCase(true, false)]
[TestCase(false, false)]
public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
{
ExposedPlayer player = null;
AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
}
private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
[TestCase(true, true)]
[TestCase(false, true)]
[TestCase(true, false)]
[TestCase(false, false)]
public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
{
ExposedPlayer player;
Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
LoadScreen(player = new ExposedPlayer(userHasCustomColours));
return player;
TestBeatmap = new OsuCustomSkinWorkingBeatmap(audio, false);
base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
}
private class ExposedPlayer : Player
private class OsuCustomSkinWorkingBeatmap : CustomSkinWorkingBeatmap
{
private readonly bool userHasCustomColours;
public ExposedPlayer(bool userHasCustomColours)
: base(false, false)
public OsuCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
: base(createBeatmap(), audio, hasColours)
{
this.userHasCustomColours = userHasCustomColours;
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<ISkinSource>(new TestSkin(userHasCustomColours));
return dependencies;
}
public IReadOnlyList<Color4> UsableComboColours =>
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
.First()
.GetConfig<GlobalSkinColours, IReadOnlyList<Color4>>(GlobalSkinColours.ComboColours)?.Value;
}
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{
private readonly bool hasColours;
public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
: base(new Beatmap
private static IBeatmap createBeatmap() =>
new Beatmap
{
BeatmapInfo =
{
@@ -105,50 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Ruleset = new OsuRuleset().RulesetInfo,
},
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
}, null, null, audio)
{
this.hasColours = hasColours;
}
protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
}
private class TestBeatmapSkin : LegacyBeatmapSkin
{
public static Color4[] Colours { get; } =
{
new Color4(50, 100, 150, 255),
new Color4(40, 80, 120, 255),
};
public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
: base(beatmap, new ResourceStore<byte[]>(), null)
{
if (hasColours)
Configuration.AddComboColours(Colours);
}
}
private class TestSkin : LegacySkin, ISkinSource
{
public static Color4[] Colours { get; } =
{
new Color4(150, 100, 50, 255),
new Color4(20, 20, 20, 255),
};
public TestSkin(bool hasCustomColours)
: base(new SkinInfo(), null, null, string.Empty)
{
if (hasCustomColours)
Configuration.AddComboColours(Colours);
}
public event Action SourceChanged
{
add { }
remove { }
}
};
}
}
}
@@ -439,7 +439,11 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)
: base(score, false, false)
: base(score, new PlayerConfiguration
{
AllowPause = false,
ShowResults = false,
})
{
}
}
@@ -52,6 +52,31 @@ namespace osu.Game.Rulesets.Osu.Tests
checkNextHitObject(null);
}
[Test]
public void TestBeatmapColourDefault()
{
AddStep("enable user provider", () => testUserSkin.Enabled = true);
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true));
checkNextHitObject("beatmap");
AddStep("enable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, true));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false));
checkNextHitObject("beatmap");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false));
AddStep("enable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, true));
checkNextHitObject("user");
AddStep("disable beatmap skin", () => LocalConfig.Set<bool>(OsuSetting.BeatmapSkins, false));
AddStep("disable beatmap colours", () => LocalConfig.Set<bool>(OsuSetting.BeatmapColours, false));
checkNextHitObject("user");
AddStep("disable user provider", () => testUserSkin.Enabled = false);
checkNextHitObject(null);
}
private void checkNextHitObject(string skin) =>
AddUntilStep($"check skin from {skin}", () =>
{
@@ -378,7 +378,11 @@ namespace osu.Game.Rulesets.Osu.Tests
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)
: base(score, false, false)
: base(score, new PlayerConfiguration
{
AllowPause = false,
ShowResults = false,
})
{
}
}
@@ -1,9 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
@@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase(true)]
public void TestLongSpinner(bool autoplay)
{
AddStep("Very short spinner", () => SetContents(() => testSingle(5, autoplay, 2000)));
AddStep("Very long spinner", () => SetContents(() => testSingle(5, autoplay, 4000)));
AddUntilStep("Wait for completion", () => drawableSpinner.Result.HasResult);
AddUntilStep("Check correct progress", () => drawableSpinner.Progress == (autoplay ? 1 : 0));
}
@@ -55,7 +57,11 @@ namespace osu.Game.Rulesets.Osu.Tests
var spinner = new Spinner
{
StartTime = Time.Current + delay,
EndTime = Time.Current + delay + length
EndTime = Time.Current + delay + length,
Samples = new List<HitSampleInfo>
{
new HitSampleInfo("hitnormal")
}
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
@@ -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.Osu\osu.Game.Rulesets.Osu.csproj" />
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (mods.Any(m => m is OsuModNoFail))
multiplier *= 0.90;
multiplier *= Math.Max(0.90, 1.0 - 0.02 * countMiss);
if (mods.Any(m => m is OsuModSpunOut))
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
@@ -92,8 +92,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
aimValue *= Math.Pow(0.97, countMiss);
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (countMiss > 0)
aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss);
// Combo scaling
if (Attributes.MaxCombo > 0)
@@ -103,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (Attributes.ApproachRate > 10.33)
approachRateFactor += 0.4 * (Attributes.ApproachRate - 10.33);
else if (Attributes.ApproachRate < 8.0)
approachRateFactor += 0.1 * (8.0 - Attributes.ApproachRate);
approachRateFactor += 0.01 * (8.0 - Attributes.ApproachRate);
aimValue *= 1.0 + Math.Min(approachRateFactor, approachRateFactor * (totalHits / 1000.0));
@@ -138,8 +139,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
speedValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
speedValue *= Math.Pow(0.97, countMiss);
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
if (countMiss > 0)
speedValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875));
// Combo scaling
if (Attributes.MaxCombo > 0)
@@ -154,10 +156,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModHidden))
speedValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
// Scale the speed value with accuracy _slightly_
speedValue *= 0.02 + accuracy;
// It is important to also consider accuracy difficulty when doing that
speedValue *= 0.96 + Math.Pow(Attributes.OverallDifficulty, 2) / 1600;
// Scale the speed value with accuracy and OD
speedValue *= (0.95 + Math.Pow(Attributes.OverallDifficulty, 2) / 750) * Math.Pow(accuracy, (14.5 - Math.Max(Attributes.OverallDifficulty, 8)) / 2);
// Scale the speed value with # of 50s to punish doubletapping.
speedValue *= Math.Pow(0.98, countMeh < totalHits / 500.0 ? 0 : countMeh - totalHits / 500.0);
return speedValue;
}
@@ -30,6 +30,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
public override Quad SelectionQuad => CirclePiece.ScreenSpaceDrawQuad;
}
}
@@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
private readonly IBindable<int> pathVersion = new Bindable<int>();
@@ -157,10 +157,16 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var h in hitObjects)
{
h.Position = new Vector2(
quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X),
quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y)
);
var newPosition = h.Position;
// guard against no-ops and NaN.
if (scale.X != 0 && quad.Width > 0)
newPosition.X = quad.TopLeft.X + (h.X - quad.TopLeft.X) / quad.Width * (quad.Width + scale.X);
if (scale.Y != 0 && quad.Height > 0)
newPosition.Y = quad.TopLeft.Y + (h.Y - quad.TopLeft.Y) / quad.Height * (quad.Height + scale.Y);
h.Position = newPosition;
}
}
@@ -230,7 +236,20 @@ namespace osu.Game.Rulesets.Osu.Edit
/// </summary>
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
getSurroundingQuad(hitObjects.SelectMany(h => new[] { h.Position, h.EndPosition }));
getSurroundingQuad(hitObjects.SelectMany(h =>
{
if (h is IHasPath path)
{
return new[]
{
h.Position,
// can't use EndPosition for reverse slider cases.
h.Position + path.Path.PositionAt(1)
};
}
return new[] { h.Position };
}));
/// <summary>
/// Returns a gamefield-space quad surrounding the provided points.
@@ -59,8 +59,8 @@ namespace osu.Game.Rulesets.Osu.Mods
{
base.ApplySettings(difficulty);
difficulty.CircleSize = CircleSize.Value;
difficulty.ApproachRate = ApproachRate.Value;
ApplySetting(CircleSize, cs => difficulty.CircleSize = cs);
ApplySetting(ApproachRate, ar => difficulty.ApproachRate = ar);
}
}
}
@@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods
var destination = e.MousePosition;
FlashlightPosition = Interpolation.ValueAt(
Math.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out);
Math.Min(Math.Abs(Clock.ElapsedFrameTime), follow_delay), position, destination, 0, follow_delay, Easing.Out);
return base.OnMouseMove(e);
}
@@ -130,12 +130,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
if (tracking.NewValue)
{
spinningSample?.Play();
spinningSample?.VolumeTo(1, 200);
spinningSample?.Play(!spinningSample.IsPlaying);
spinningSample?.VolumeTo(1, 300);
}
else
{
spinningSample?.VolumeTo(0, 200).Finally(_ => spinningSample.Stop());
spinningSample?.VolumeTo(0, 300).OnComplete(_ => spinningSample.Stop());
}
}
@@ -20,17 +20,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable<float> cursorSize;
public LegacyCursorTrail()
{
Blending = BlendingParameters.Additive;
}
[BackgroundDependencyLoader]
private void load(ISkinSource skin, OsuConfigManager config)
{
Texture = skin.GetTexture("cursortrail");
disjointTrail = skin.GetTexture("cursormiddle") == null;
Blending = !disjointTrail ? BlendingParameters.Additive : BlendingParameters.Inherit;
if (Texture != null)
{
// stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation.
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osuTK;
@@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new OsuReplayRecorder(score);
public override double GameplayStartTime
{
@@ -2,18 +2,18 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuReplayRecorder : ReplayRecorder<OsuAction>
{
public OsuReplayRecorder(Replay replay)
: base(replay)
public OsuReplayRecorder(Score score)
: base(score)
{
}
@@ -5,6 +5,13 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>click the circles. to the beat.</Description>
</PropertyGroup>
<PropertyGroup Label="Nuget">
<Title>osu! (ruleset)</Title>
<PackageId>ppy.osu.Game.Rulesets.Osu</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.Taiko.Tests.dll"
"${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll"
"${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Taiko.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
@@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class DrawableTaikoRulesetTestScene : OsuTestScene
{
protected const int DEFAULT_PLAYFIELD_CONTAINER_HEIGHT = 768;
protected DrawableTaikoRuleset DrawableRuleset { get; private set; }
protected Container PlayfieldContainer { get; private set; }
@@ -44,10 +46,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
Add(PlayfieldContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = 768,
Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT,
Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) }
});
}
@@ -13,12 +13,15 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
public readonly HitResult Type;
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great)
public DrawableTestHit(Hit hit, HitResult type = HitResult.Great, bool kiai = false)
: base(hit)
{
Type = type;
HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var controlPoints = new ControlPointInfo();
controlPoints.Add(0, new EffectControlPoint { KiaiMode = kiai });
HitObject.ApplyDefaults(controlPoints, new BeatmapDifficulty());
}
protected override void UpdateInitialTransforms()
@@ -0,0 +1,58 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public abstract class HitObjectApplicationTestScene : OsuTestScene
{
[Cached(typeof(IScrollingInfo))]
private ScrollingTestContainer.TestScrollingInfo info = new ScrollingTestContainer.TestScrollingInfo
{
Direction = { Value = ScrollingDirection.Left },
TimeRange = { Value = 1000 },
};
private ScrollingHitObjectContainer hitObjectContainer;
[BackgroundDependencyLoader]
private void load()
{
Child = hitObjectContainer = new ScrollingHitObjectContainer
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(new StopwatchClock())
};
}
[SetUpSteps]
public void SetUp()
=> AddStep("clear SHOC", () => hitObjectContainer.Clear(false));
protected void AddHitObject(DrawableHitObject hitObject)
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
protected void RemoveHitObject(DrawableHitObject hitObject)
=> AddStep("remove from SHOC", () => hitObjectContainer.Remove(hitObject));
protected TObject PrepareObject<TObject>(TObject hitObject)
where TObject : TaikoHitObject
{
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return hitObject;
}
}
}
@@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
}
};
hoc.Add(new DrawableBarLineMajor(createBarLineAtCurrentTime(true))
hoc.Add(new DrawableBarLine(createBarLineAtCurrentTime(true))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
createDrawableRuleset();
assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle);
assertStateAfterResult(new JudgementResult(new Hit.StrongNestedHit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle);
}
[Test]
@@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[TestCase("sample-to-type-conversions")]
[TestCase("slider-conversion-v6")]
[TestCase("slider-conversion-v14")]
[TestCase("slider-generating-drumroll-2")]
public void Test(string name) => base.Test(name);
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
@@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
IsCentre = (hitObject as Hit)?.Type == HitType.Centre,
IsDrumRoll = hitObject is DrumRoll,
IsSwell = hitObject is Swell,
IsStrong = ((TaikoHitObject)hitObject).IsStrong
IsStrong = (hitObject as TaikoStrongableHitObject)?.IsStrong == true
};
}
@@ -0,0 +1,33 @@
// 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.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneBarLineApplication : HitObjectApplicationTestScene
{
[Test]
public void TestApplyNewBarLine()
{
DrawableBarLine barLine = new DrawableBarLine();
AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
{
StartTime = 400,
Major = true
}), null));
AddHitObject(barLine);
RemoveHitObject(barLine);
AddStep("apply new bar line", () => barLine.Apply(PrepareObject(new BarLine
{
StartTime = 200,
Major = false
}), null));
AddHitObject(barLine);
}
}
}
@@ -0,0 +1,39 @@
// 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.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneDrumRollApplication : HitObjectApplicationTestScene
{
[Test]
public void TestApplyNewDrumRoll()
{
var drumRoll = new DrawableDrumRoll();
AddStep("apply new drum roll", () => drumRoll.Apply(PrepareObject(new DrumRoll
{
StartTime = 300,
Duration = 500,
IsStrong = false,
TickRate = 2
}), null));
AddHitObject(drumRoll);
RemoveHitObject(drumRoll);
AddStep("apply new drum roll", () => drumRoll.Apply(PrepareObject(new DrumRoll
{
StartTime = 150,
Duration = 400,
IsStrong = true,
TickRate = 16
}), null));
AddHitObject(drumRoll);
}
}
}
@@ -0,0 +1,37 @@
// 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.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneHitApplication : HitObjectApplicationTestScene
{
[Test]
public void TestApplyNewHit()
{
var hit = new DrawableHit();
AddStep("apply new hit", () => hit.Apply(PrepareObject(new Hit
{
Type = HitType.Rim,
IsStrong = false,
StartTime = 300
}), null));
AddHitObject(hit);
RemoveHitObject(hit);
AddStep("apply new hit", () => hit.Apply(PrepareObject(new Hit
{
Type = HitType.Centre,
IsStrong = true,
StartTime = 500
}), null));
AddHitObject(hit);
}
}
}
+38 -28
View File
@@ -2,14 +2,16 @@
// 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.Graphics;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects;
@@ -97,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
break;
case 6:
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500);
PlayfieldContainer.Delay(delay).ResizeTo(new Vector2(1, DEFAULT_PLAYFIELD_CONTAINER_HEIGHT), 500);
break;
}
}
@@ -106,13 +108,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
var cpi = new ControlPointInfo();
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h);
@@ -123,32 +120,38 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great;
var cpi = new ControlPointInfo();
cpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
Hit hit = new Hit();
hit.ApplyDefaults(cpi, new BeatmapDifficulty());
var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
Hit hit = new Hit
{
IsStrong = true,
Samples = createSamples(strong: true)
};
var h = new DrawableTestHit(hit, kiai: kiai) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) };
DrawableRuleset.Playfield.Add(h);
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = hitResult });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h.NestedHitObjects.Single(), new JudgementResult(new HitObject(), new TaikoStrongJudgement()) { Type = HitResult.Great });
}
private void addMissJudgement()
{
DrawableTestHit h;
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit(), HitResult.Miss));
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new HitObject(), new TaikoJudgement()) { Type = HitResult.Miss });
DrawableRuleset.Playfield.Add(h = new DrawableTestHit(new Hit { StartTime = DrawableRuleset.Playfield.Time.Current }, HitResult.Miss)
{
Alpha = 0
});
((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(h.HitObject, new TaikoJudgement()) { Type = HitResult.Miss });
}
private void addBarLine(bool major, double delay = scroll_time)
{
BarLine bl = new BarLine { StartTime = DrawableRuleset.Playfield.Time.Current + delay };
BarLine bl = new BarLine
{
StartTime = DrawableRuleset.Playfield.Time.Current + delay,
Major = major
};
DrawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
DrawableRuleset.Playfield.Add(bl);
}
private void addSwell(double duration = default_duration)
@@ -173,6 +176,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong,
Samples = createSamples(strong: strong),
Duration = duration,
TickRate = 8,
};
@@ -190,7 +194,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit h = new Hit
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong
IsStrong = strong,
Samples = createSamples(HitType.Centre, strong)
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@@ -203,7 +208,8 @@ namespace osu.Game.Rulesets.Taiko.Tests
Hit h = new Hit
{
StartTime = DrawableRuleset.Playfield.Time.Current + scroll_time,
IsStrong = strong
IsStrong = strong,
Samples = createSamples(HitType.Rim, strong)
};
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
@@ -211,14 +217,18 @@ namespace osu.Game.Rulesets.Taiko.Tests
DrawableRuleset.Playfield.Add(new DrawableHit(h));
}
private class TestStrongNestedHit : DrawableStrongNestedHit
// TODO: can be removed if a better way of handling colour/strong type and samples is developed
private IList<HitSampleInfo> createSamples(HitType? hitType = null, bool strong = false)
{
public TestStrongNestedHit(DrawableHitObject mainObject)
: base(new StrongHitObject { StartTime = mainObject.HitObject.StartTime }, mainObject)
{
}
var samples = new List<HitSampleInfo>();
public override bool OnPressed(TaikoAction action) => false;
if (hitType == HitType.Rim)
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
if (strong)
samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
return samples;
}
}
}
@@ -1,6 +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.
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Testing;
using osu.Game.Audio;
@@ -18,24 +19,33 @@ namespace osu.Game.Rulesets.Taiko.Tests
public override void SetUpSteps()
{
base.SetUpSteps();
AddAssert("has correct samples", () =>
var expectedSampleNames = new[]
{
var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType<DrawableHit>().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
string.Empty,
string.Empty,
string.Empty,
string.Empty,
HitSampleInfo.HIT_FINISH,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
};
var actualSampleNames = new List<string>();
var expected = new[]
{
string.Empty,
string.Empty,
string.Empty,
string.Empty,
HitSampleInfo.HIT_FINISH,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
HitSampleInfo.HIT_WHISTLE,
};
// due to pooling we can't access all samples right away due to object re-use,
// so we need to collect as we go.
AddStep("collect sample names", () => Player.DrawableRuleset.Playfield.NewResult += (dho, _) =>
{
if (!(dho is DrawableHit h))
return;
return names.SequenceEqual(expected);
actualSampleNames.Add(string.Join(',', h.GetSamples().Select(s => s.Name)));
});
AddUntilStep("all samples collected", () => actualSampleNames.Count == expectedSampleNames.Length);
AddAssert("samples are correct", () => actualSampleNames.SequenceEqual(expectedSampleNames));
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
@@ -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.Taiko\osu.Game.Rulesets.Taiko.csproj" />
@@ -65,8 +65,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
{
TaikoHitObject first = x.First();
if (x.Skip(1).Any() && first.CanBeStrong)
first.IsStrong = true;
if (x.Skip(1).Any() && first is TaikoStrongableHitObject strong)
strong.IsStrong = true;
return first;
}).ToList();
}
@@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
}
}
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out double taikoDuration, out double tickSpacing)
private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out int taikoDuration, out double tickSpacing)
{
// DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS.
// Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable.
@@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll.
double taikoVelocity = sliderScoringPointDistance * beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate;
taikoDuration = distance / taikoVelocity * beatLength;
taikoDuration = (int)(distance / taikoVelocity * beatLength);
if (isForCurrentRuleset)
{
@@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
beatLength = timingPoint.BeatLength;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, (double)taikoDuration / spans);
return tickSpacing > 0
&& distance / osuVelocity * 1000 < 2 * beatLength;
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
base.UpdateTernaryStates();
selectionRimState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<Hit>(), h => h.Type == HitType.Rim);
selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<TaikoHitObject>(), h => h.IsStrong);
selectionStrongState.Value = GetStateFromSelection(EditorBeatmap.SelectedHitObjects.OfType<TaikoStrongableHitObject>(), h => h.IsStrong);
}
}
}
@@ -1,6 +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.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
@@ -8,5 +9,16 @@ namespace osu.Game.Rulesets.Taiko.Mods
public class TaikoModEasy : ModEasy
{
public override string Description => @"Beats move slower, and less accuracy required!";
/// <summary>
/// Multiplier factor added to the scrolling speed.
/// </summary>
private const double slider_multiplier = 0.8;
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
base.ApplyToDifficulty(difficulty);
difficulty.SliderMultiplier *= slider_multiplier;
}
}
}
@@ -1,6 +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.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
@@ -9,5 +10,21 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public override double ScoreMultiplier => 1.06;
public override bool Ranked => true;
/// <summary>
/// Multiplier factor added to the scrolling speed.
/// </summary>
/// <remarks>
/// This factor is made up of two parts: the base part (1.4) and the aspect ratio adjustment (4/3).
/// Stable applies the latter by dividing the width of the user's display by the width of a display with the same height, but 4:3 aspect ratio.
/// TODO: Revisit if taiko playfield ever changes away from a hard-coded 16:9 (see https://github.com/ppy/osu/issues/5685).
/// </remarks>
private const double slider_multiplier = 1.4 * 4 / 3;
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
{
base.ApplyToDifficulty(difficulty);
difficulty.SliderMultiplier *= slider_multiplier;
}
}
}
+8 -1
View File
@@ -1,6 +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.
using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
public class BarLine : TaikoHitObject, IBarLine
{
public bool Major { get; set; }
public bool Major
{
get => MajorBindable.Value;
set => MajorBindable.Value = value;
}
public readonly Bindable<bool> MajorBindable = new BindableBool();
public override Judgement CreateJudgement() => new IgnoreJudgement();
}
@@ -1,7 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osuTK;
@@ -15,49 +19,123 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary>
public class DrawableBarLine : DrawableHitObject<HitObject>
{
public new BarLine HitObject => (BarLine)base.HitObject;
/// <summary>
/// The width of the line tracker.
/// </summary>
private const float tracker_width = 2f;
/// <summary>
/// Fade out time calibrated to a pre-empt of 1000ms.
/// The vertical offset of the triangles from the line tracker.
/// </summary>
private const float base_fadeout_time = 100f;
private const float triangle_offset = 10f;
/// <summary>
/// The size of the triangles.
/// </summary>
private const float triangle_size = 20f;
/// <summary>
/// The visual line tracker.
/// </summary>
protected SkinnableDrawable Line;
private SkinnableDrawable line;
/// <summary>
/// The bar line.
/// Container with triangles. Only visible for major lines.
/// </summary>
protected readonly BarLine BarLine;
private Container triangleContainer;
public DrawableBarLine(BarLine barLine)
private readonly Bindable<bool> major = new Bindable<bool>();
public DrawableBarLine()
: this(null)
{
}
public DrawableBarLine([CanBeNull] BarLine barLine)
: base(barLine)
{
BarLine = barLine;
}
[BackgroundDependencyLoader]
private void load()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Y;
Width = tracker_width;
AddInternal(Line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
AddRangeInternal(new Drawable[]
{
RelativeSizeAxes = Axes.Both,
EdgeSmoothness = new Vector2(0.5f, 0),
})
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0.75f,
line = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.BarLine), _ => new Box
{
RelativeSizeAxes = Axes.Both,
EdgeSmoothness = new Vector2(0.5f, 0),
})
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new EquilateralTriangle
{
Name = "Top",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, -triangle_offset),
Size = new Vector2(-triangle_size),
EdgeSmoothness = new Vector2(1),
},
new EquilateralTriangle
{
Name = "Bottom",
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, triangle_offset),
Size = new Vector2(triangle_size),
EdgeSmoothness = new Vector2(1),
}
}
}
});
}
protected override void UpdateHitStateTransforms(ArmedState state) => this.FadeOut(150);
protected override void LoadComplete()
{
base.LoadComplete();
major.BindValueChanged(updateMajor, true);
}
private void updateMajor(ValueChangedEvent<bool> major)
{
line.Alpha = major.NewValue ? 1f : 0.75f;
triangleContainer.Alpha = major.NewValue ? 1 : 0;
}
protected override void OnApply()
{
base.OnApply();
major.BindTo(HitObject.MajorBindable);
}
protected override void OnFree()
{
base.OnFree();
major.UnbindFrom(HitObject.MajorBindable);
}
protected override void UpdateHitStateTransforms(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime))
this.FadeOutFromOne(150).Expire();
}
}
}
@@ -1,67 +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 osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Shapes;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableBarLineMajor : DrawableBarLine
{
/// <summary>
/// The vertical offset of the triangles from the line tracker.
/// </summary>
private const float triangle_offfset = 10f;
/// <summary>
/// The size of the triangles.
/// </summary>
private const float triangle_size = 20f;
private readonly Container triangleContainer;
public DrawableBarLineMajor(BarLine barLine)
: base(barLine)
{
AddInternal(triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Children = new[]
{
new EquilateralTriangle
{
Name = "Top",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, -triangle_offfset),
Size = new Vector2(-triangle_size),
EdgeSmoothness = new Vector2(1),
},
new EquilateralTriangle
{
Name = "Bottom",
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre,
Position = new Vector2(0, triangle_offfset),
Size = new Vector2(triangle_size),
EdgeSmoothness = new Vector2(1),
}
}
});
Line.Alpha = 1f;
}
protected override void LoadComplete()
{
base.LoadComplete();
using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime))
triangleContainer.FadeOut(150);
}
}
}
@@ -3,6 +3,7 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Utils;
using osu.Game.Graphics;
@@ -19,7 +20,7 @@ using osuTK;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableDrumRoll : DrawableTaikoHitObject<DrumRoll>
public class DrawableDrumRoll : DrawableTaikoStrongableHitObject<DrumRoll, DrumRoll.StrongNestedHit>
{
/// <summary>
/// Number of rolling hits required to reach the dark/final colour.
@@ -31,15 +32,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary>
private int rollingHits;
private Container tickContainer;
private readonly Container tickContainer;
private Color4 colourIdle;
private Color4 colourEngaged;
public DrawableDrumRoll(DrumRoll drumRoll)
public DrawableDrumRoll()
: this(null)
{
}
public DrawableDrumRoll([CanBeNull] DrumRoll drumRoll)
: base(drumRoll)
{
RelativeSizeAxes = Axes.Y;
Content.Add(tickContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue
});
}
[BackgroundDependencyLoader]
@@ -47,12 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
colourIdle = colours.YellowDark;
colourEngaged = colours.YellowDarker;
Content.Add(tickContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue
});
}
protected override void LoadComplete()
@@ -68,6 +74,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
updateColour();
}
protected override void OnFree()
{
base.OnFree();
rollingHits = 0;
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
@@ -83,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
tickContainer.Clear();
tickContainer.Clear(false);
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
@@ -114,7 +126,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
rollingHits = Math.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
updateColour();
updateColour(100);
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -154,27 +166,34 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Content.X = DrawHeight / 2;
}
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
protected override DrawableStrongNestedHit CreateStrongNestedHit(DrumRoll.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
private void updateColour()
private void updateColour(double fadeDuration = 0)
{
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, 100);
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
}
private class StrongNestedHit : DrawableStrongNestedHit
public class StrongNestedHit : DrawableStrongNestedHit
{
public StrongNestedHit(StrongHitObject strong, DrawableDrumRoll drumRoll)
: base(strong, drumRoll)
public new DrawableDrumRoll ParentHitObject => (DrawableDrumRoll)base.ParentHitObject;
public StrongNestedHit()
: this(null)
{
}
public StrongNestedHit([CanBeNull] DrumRoll.StrongNestedHit nestedHit)
: base(nestedHit)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Judged)
if (!ParentHitObject.Judged)
return;
ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override bool OnPressed(TaikoAction action) => false;
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Skinning.Default;
@@ -9,14 +10,19 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableDrumRollTick : DrawableTaikoHitObject<DrumRollTick>
public class DrawableDrumRollTick : DrawableTaikoStrongableHitObject<DrumRollTick, DrumRollTick.StrongNestedHit>
{
/// <summary>
/// The hit type corresponding to the <see cref="TaikoAction"/> that the user pressed to hit this <see cref="DrawableDrumRollTick"/>.
/// </summary>
public HitType JudgementType;
public DrawableDrumRollTick(DrumRollTick tick)
public DrawableDrumRollTick()
: this(null)
{
}
public DrawableDrumRollTick([CanBeNull] DrumRollTick tick)
: base(tick)
{
FillMode = FillMode.Fit;
@@ -61,21 +67,28 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return UpdateResult(true);
}
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
protected override DrawableStrongNestedHit CreateStrongNestedHit(DrumRollTick.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
private class StrongNestedHit : DrawableStrongNestedHit
public class StrongNestedHit : DrawableStrongNestedHit
{
public StrongNestedHit(StrongHitObject strong, DrawableDrumRollTick tick)
: base(strong, tick)
public new DrawableDrumRollTick ParentHitObject => (DrawableDrumRollTick)base.ParentHitObject;
public StrongNestedHit()
: this(null)
{
}
public StrongNestedHit([CanBeNull] DrumRollTick.StrongNestedHit nestedHit)
: base(nestedHit)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Judged)
if (!ParentHitObject.Judged)
return;
ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
ApplyResult(r => r.Type = ParentHitObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
}
public override bool OnPressed(TaikoAction action) => false;
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Audio;
@@ -16,7 +16,7 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableHit : DrawableTaikoHitObject<Hit>
public class DrawableHit : DrawableTaikoStrongableHitObject<Hit, Hit.StrongNestedHit>
{
/// <summary>
/// A list of keys which can result in hits for this HitObject.
@@ -36,29 +36,51 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool pressHandledThisFrame;
private readonly Bindable<HitType> type;
private readonly Bindable<HitType> type = new Bindable<HitType>();
public DrawableHit(Hit hit)
: base(hit)
public DrawableHit()
: this(null)
{
type = HitObject.TypeBindable.GetBoundCopy();
FillMode = FillMode.Fit;
updateActionsFromType();
}
[BackgroundDependencyLoader]
private void load()
public DrawableHit([CanBeNull] Hit hit)
: base(hit)
{
FillMode = FillMode.Fit;
}
protected override void OnApply()
{
type.BindTo(HitObject.TypeBindable);
type.BindValueChanged(_ =>
{
updateActionsFromType();
// will overwrite samples, should only be called on change.
// will overwrite samples, should only be called on subsequent changes
// after the initial application.
updateSamplesFromTypeChange();
RecreatePieces();
});
// action update also has to happen immediately on application.
updateActionsFromType();
base.OnApply();
}
protected override void OnFree()
{
base.OnFree();
type.UnbindFrom(HitObject.TypeBindable);
type.UnbindEvents();
UnproxyContent();
HitActions = null;
HitAction = null;
validActionPressed = pressHandledThisFrame = false;
}
private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
@@ -228,32 +250,37 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
protected override DrawableStrongNestedHit CreateStrongHit(StrongHitObject hitObject) => new StrongNestedHit(hitObject, this);
protected override DrawableStrongNestedHit CreateStrongNestedHit(Hit.StrongNestedHit hitObject) => new StrongNestedHit(hitObject);
private class StrongNestedHit : DrawableStrongNestedHit
public class StrongNestedHit : DrawableStrongNestedHit
{
public new DrawableHit ParentHitObject => (DrawableHit)base.ParentHitObject;
/// <summary>
/// The lenience for the second key press.
/// This does not adjust by map difficulty in ScoreV2 yet.
/// </summary>
private const double second_hit_window = 30;
public new DrawableHit MainObject => (DrawableHit)base.MainObject;
public StrongNestedHit()
: this(null)
{
}
public StrongNestedHit(StrongHitObject strong, DrawableHit hit)
: base(strong, hit)
public StrongNestedHit([CanBeNull] Hit.StrongNestedHit nestedHit)
: base(nestedHit)
{
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (!MainObject.Result.HasResult)
if (!ParentHitObject.Result.HasResult)
{
base.CheckForResult(userTriggered, timeOffset);
return;
}
if (!MainObject.Result.IsHit)
if (!ParentHitObject.Result.IsHit)
{
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
@@ -261,27 +288,27 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
if (!userTriggered)
{
if (timeOffset - MainObject.Result.TimeOffset > second_hit_window)
if (timeOffset - ParentHitObject.Result.TimeOffset > second_hit_window)
ApplyResult(r => r.Type = r.Judgement.MinResult);
return;
}
if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window)
if (Math.Abs(timeOffset - ParentHitObject.Result.TimeOffset) <= second_hit_window)
ApplyResult(r => r.Type = r.Judgement.MaxResult);
}
public override bool OnPressed(TaikoAction action)
{
// Don't process actions until the main hitobject is hit
if (!MainObject.IsHit)
if (!ParentHitObject.IsHit)
return false;
// Don't process actions if the pressed button was released
if (MainObject.HitAction == null)
if (ParentHitObject.HitAction == null)
return false;
// Don't handle invalid hit action presses
if (!MainObject.HitActions.Contains(action))
if (!ParentHitObject.HitActions.Contains(action))
return false;
return UpdateResult(true);
@@ -1,22 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Objects.Drawables;
using JetBrains.Annotations;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
/// <summary>
/// Used as a nested hitobject to provide <see cref="TaikoStrongJudgement"/>s for <see cref="DrawableTaikoHitObject"/>s.
/// Used as a nested hitobject to provide <see cref="TaikoStrongJudgement"/>s for <see cref="DrawableTaikoStrongableHitObject{TObject,TStrongNestedObject}"/>s.
/// </summary>
public abstract class DrawableStrongNestedHit : DrawableTaikoHitObject
{
public readonly DrawableHitObject MainObject;
public new DrawableTaikoHitObject ParentHitObject => (DrawableTaikoHitObject)base.ParentHitObject;
protected DrawableStrongNestedHit(StrongHitObject strong, DrawableHitObject mainObject)
: base(strong)
protected DrawableStrongNestedHit([CanBeNull] StrongNestedHitObject nestedHit)
: base(nestedHit)
{
MainObject = mainObject;
}
}
}
@@ -3,6 +3,7 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -35,7 +36,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
public DrawableSwell(Swell swell)
public DrawableSwell()
: this(null)
{
}
public DrawableSwell([CanBeNull] Swell swell)
: base(swell)
{
FillMode = FillMode.Fit;
@@ -123,12 +129,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Centre,
});
protected override void LoadComplete()
protected override void OnFree()
{
base.LoadComplete();
base.OnFree();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
UnproxyContent();
lastWasCentre = null;
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
@@ -146,7 +153,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
ticks.Clear();
ticks.Clear(false);
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
@@ -168,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
foreach (var t in ticks)
{
if (!t.IsHit)
if (!t.Result.HasResult)
{
nextTick = t;
break;
@@ -208,7 +215,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
continue;
}
tick.TriggerResult(false);
if (!tick.Result.HasResult)
tick.TriggerResult(false);
}
ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult);
@@ -1,6 +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.
using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Taiko.Skinning.Default;
using osu.Game.Skinning;
@@ -11,7 +12,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public override bool DisplayResult => false;
public DrawableSwellTick(SwellTick hitObject)
public DrawableSwellTick()
: this(null)
{
}
public DrawableSwellTick([CanBeNull] SwellTick hitObject)
: base(hitObject)
{
}

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